begin adding packet capture functionality

This commit is contained in:
soypat
2025-06-01 13:04:43 -03:00
parent a29bad9ac9
commit 55bc765d92
4 changed files with 428 additions and 2 deletions
+1
View File
@@ -14,6 +14,7 @@ var (
errARPBufferFull = errors.New("ARP client need handling:too many ops pending")
errShortARP = errors.New("packet too short to be ARP")
errARPUnsupported = errors.New("ARP not supprortedf")
errLargeSizes = errors.New("size of ARP protocol+hardware is unusually large")
)
// Operation represents the type of ARP packet, either request or reply/response.
+3 -1
View File
@@ -138,9 +138,11 @@ func (afrm Frame) SwapTargetSender() {
func (afrm Frame) ValidateSize(v *lneto.Validator) {
_, hlen := afrm.Hardware()
_, ilen := afrm.Protocol()
minLen := 8 + 2*(hlen+ilen)
minLen := 8 + 2*(int(hlen)+int(ilen))
if len(afrm.buf) < int(minLen) {
v.AddError(errShortARP)
} else if minLen > 255 {
v.AddError(errLargeSizes) // We don't want a uint8 overflow somewhere. This is probably a maliciously crafted packet.
}
}
+423
View File
@@ -0,0 +1,423 @@
package ltesto
import (
"errors"
"github.com/soypat/lneto"
"github.com/soypat/lneto/arp"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/http/httpraw"
"github.com/soypat/lneto/ipv4"
"github.com/soypat/lneto/tcp"
)
type PacketBreakdown struct {
hdr httpraw.Header
vld lneto.Validator
}
func (pc *PacketBreakdown) CaptureEthernet(dst []FrameInfo, pkt []byte, bitOffset int) ([]FrameInfo, error) {
if bitOffset%8 != 0 {
return dst, errors.New("Ethernet must be parsed at byte boundary")
}
efrm, err := ethernet.NewFrame(pkt[bitOffset/8:])
if err != nil {
return dst, err
}
efrm.ValidateSize(pc.validator())
if pc.validator().HasError() {
return dst, pc.validator().Err()
}
finfo := FrameInfo{
Protocol: "Ethernet",
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseEthernetFields[:]...)
etype := efrm.EtherTypeOrSize()
end := 14*octet + bitOffset
if etype.IsSize() {
finfo.Fields[len(finfo.Fields)-1].Class = classSize
dst = append(dst, finfo)
dst = append(dst, remainingFrameInfo("Ethernet payload", classPayload, end, octet*len(pkt)))
return dst, nil
}
dst = append(dst, finfo)
if efrm.IsVLAN() {
finfo.Fields = append(finfo.Fields, FrameField{Name: "VLAN Tag", Class: classType, FrameBitOffset: end, BitLength: 2 * octet})
dst = append(dst, remainingFrameInfo("Ethernet VLAN", classPayload, end+2*octet, octet*len(pkt)))
return dst, nil
}
switch etype {
case ethernet.TypeARP:
case ethernet.TypeIPv4:
dst, err = pc.CaptureIPv4(dst, pkt, end)
}
return dst, err
}
func (pc *PacketBreakdown) CaptureARP(dst []FrameInfo, pkt []byte, bitOffset int) ([]FrameInfo, error) {
if bitOffset%8 != 0 {
return dst, errors.New("ARP must be parsed at byte boundary")
}
afrm, err := arp.NewFrame(pkt[bitOffset/8:])
if err != nil {
return dst, err
}
afrm.ValidateSize(pc.validator())
if pc.validator().HasError() {
return dst, pc.validator().Err()
}
finfo := FrameInfo{
Protocol: ethernet.TypeARP,
PacketBitOffset: bitOffset,
}
const varstart = 8 * octet
finfo.Fields = append(finfo.Fields, baseARPFields[:]...)
_, hlen := afrm.Hardware()
_, plen := afrm.Protocol()
finfo.Fields = append(finfo.Fields,
FrameField{
Name: "Sender hardware address",
Class: classSrc,
FrameBitOffset: varstart,
BitLength: int(hlen) * octet,
},
FrameField{
Name: "Sender protocol address",
Class: classSrc,
FrameBitOffset: int(hlen)*octet + varstart,
BitLength: int(plen) * octet,
},
FrameField{
Name: "Target hardware address",
Class: classSrc,
FrameBitOffset: int(hlen+plen)*octet + varstart,
BitLength: int(hlen) * octet,
},
FrameField{
Name: "Target protocol address",
Class: classSrc,
FrameBitOffset: (2*int(hlen)+int(plen))*octet + varstart,
BitLength: int(plen) * octet,
},
)
dst = append(dst, finfo)
return dst, nil
}
func (pc *PacketBreakdown) CaptureIPv4(dst []FrameInfo, pkt []byte, bitOffset int) ([]FrameInfo, error) {
if bitOffset%8 != 0 {
return dst, errors.New("IPv4 must be parsed at byte boundary")
}
ifrm4, err := ipv4.NewFrame(pkt[bitOffset/8:])
if err != nil {
return dst, err
}
ifrm4.ValidateSize(pc.validator())
if pc.validator().HasError() {
return dst, pc.validator().Err()
}
finfo := FrameInfo{
Protocol: ethernet.TypeIPv4,
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseIPv4Fields[:]...)
options := ifrm4.Options()
finfo.Fields = append(finfo.Fields, FrameField{
Class: classOptions,
FrameBitOffset: 20 * octet,
BitLength: octet * len(options),
})
proto := ifrm4.Protocol()
dst = append(dst, finfo)
end := bitOffset + octet*ifrm4.HeaderLength()
switch proto {
case lneto.IPProtoTCP:
dst, err = pc.CaptureTCP(dst, pkt, end)
default:
dst = append(dst, remainingFrameInfo(proto, 0, end, octet*len(pkt)))
}
return dst, err
}
func (pc *PacketBreakdown) CaptureTCP(dst []FrameInfo, pkt []byte, bitOffset int) ([]FrameInfo, error) {
if bitOffset%8 != 0 {
return dst, errors.New("TCP must be parsed at byte boundary")
}
tfrm, err := tcp.NewFrame(pkt[bitOffset/8:])
if err != nil {
return dst, err
}
tfrm.ValidateSize(pc.validator())
if pc.validator().HasError() {
return dst, pc.validator().Err()
}
end := bitOffset + octet*tfrm.HeaderLength()
finfo := FrameInfo{
Protocol: lneto.IPProtoTCP,
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseTCPFields[:]...)
options := tfrm.Options()
finfo.Fields = append(finfo.Fields, FrameField{
Class: classOptions,
FrameBitOffset: 20 * octet,
BitLength: octet * len(options),
})
dst = append(dst, finfo)
payload := tfrm.Payload()
if len(payload) > 0 {
dst, err = pc.CaptureHTTP(dst, pkt, end)
if err != nil {
dst = append(dst, remainingFrameInfo(nil, classPayload, end, len(pkt)))
}
}
return dst, nil
}
func (pc *PacketBreakdown) CaptureHTTP(dst []FrameInfo, pkt []byte, bitOffset int) ([]FrameInfo, error) {
if bitOffset%8 != 0 {
return nil, errors.New("HTTP must be parsed at byte boundary")
}
const asResponse = true
const asRequest = false
httpData := pkt[bitOffset/8:]
pc.hdr.Reset(httpData)
err := pc.hdr.Parse(asResponse)
if err == nil {
dst = append(dst, remainingFrameInfo("HTTP Response", classText, bitOffset, len(pkt)))
return dst, nil
}
pc.hdr.Reset(httpData)
err = pc.hdr.Parse(asRequest)
if err == nil {
dst = append(dst, remainingFrameInfo(string(pc.hdr.Protocol()), classText, bitOffset, len(pkt)))
return dst, nil
}
return dst, err
}
func (pc *PacketBreakdown) validator() *lneto.Validator {
return &pc.vld
}
type FrameField struct {
Name string
Class FieldClass
FrameBitOffset int
BitLength int
SubFields []FrameField
}
type FrameInfo struct {
Protocol any
Fields []FrameField
PacketBitOffset int
}
type FieldClass uint16
const (
_ FieldClass = iota
classSrc // Source
classDst // Destination
classProto // Protocol
classType // Type
classSize // Field Size
classFlags // Flags
classID // Identification
classChecksum // Checksum
classOptions // Options
classPayload // Payload
classText // Text
)
const octet = 8
var baseEthernetFields = [...]FrameField{
{
Class: classDst,
FrameBitOffset: 0,
BitLength: 6 * octet,
},
{
Class: classSrc,
FrameBitOffset: 6 * octet,
BitLength: 6 * octet,
},
{
Class: classProto,
FrameBitOffset: 12 * octet,
BitLength: 2 * octet,
},
}
var baseARPFields = [...]FrameField{
{
Name: "Hardware type",
Class: classType,
FrameBitOffset: 0,
BitLength: 2 * octet,
},
{
Name: "Protocol type",
Class: classType,
FrameBitOffset: 2 * octet,
BitLength: 2 * octet,
},
{
Name: "Hardware size",
Class: classSize,
FrameBitOffset: 4 * octet,
BitLength: 1 * octet,
},
{
Name: "Protocol size",
Class: classSize,
FrameBitOffset: 5 * octet,
BitLength: 1 * octet,
},
{
Name: "Opcode",
Class: classType,
FrameBitOffset: 6 * octet,
BitLength: 2 * octet,
},
}
var baseIPv4Fields = [...]FrameField{
{
Name: "Version",
Class: classType,
FrameBitOffset: 0,
BitLength: 4,
},
{
Name: "Header Length",
Class: classSize,
FrameBitOffset: 4,
BitLength: 4,
},
{
Name: "Differentiated services",
Class: classFlags,
FrameBitOffset: 1 * octet,
BitLength: 1 * octet,
},
{
Name: "Total Length",
Class: classSize,
FrameBitOffset: 2 * octet,
BitLength: 2 * octet,
},
{
Class: classID,
FrameBitOffset: 4 * octet,
BitLength: 2 * octet,
},
{
Class: classID,
FrameBitOffset: 4 * octet,
BitLength: 2 * octet,
},
{
Class: classFlags,
FrameBitOffset: 6 * octet,
BitLength: 2 * octet,
},
{
Name: "Time to live",
FrameBitOffset: 8 * octet,
BitLength: 1 * octet,
},
{
Class: classProto,
FrameBitOffset: 9 * octet,
BitLength: 1 * octet,
},
{
Class: classChecksum,
FrameBitOffset: 10 * octet,
BitLength: 2 * octet,
},
{
Class: classSrc,
FrameBitOffset: 12 * octet,
BitLength: 4 * octet,
},
{
Class: classDst,
FrameBitOffset: 16 * octet,
BitLength: 4 * octet,
},
}
var baseTCPFields = [...]FrameField{
{
Name: "Source port",
Class: classSrc,
FrameBitOffset: 0,
BitLength: 2 * octet,
},
{
Name: "Destination port",
Class: classSrc,
FrameBitOffset: 2 * octet,
BitLength: 2 * octet,
},
{
Name: "Sequence number",
Class: classID,
FrameBitOffset: 4 * octet,
BitLength: 4 * octet,
},
{
Name: "Acknowledgement number",
Class: classID,
FrameBitOffset: 8 * octet,
BitLength: 4 * octet,
},
{
Name: "Header length",
Class: classID,
FrameBitOffset: 12 * octet,
BitLength: 4,
},
{
Class: classFlags,
FrameBitOffset: 12*octet + 4,
BitLength: 12,
},
{
Name: "Window",
Class: classSize,
FrameBitOffset: 14 * octet,
BitLength: 2 * octet,
},
{
Class: classChecksum,
FrameBitOffset: 16 * octet,
BitLength: 2 * octet,
},
{
Name: "Urgent pointer",
Class: 0,
FrameBitOffset: 18 * octet,
BitLength: 2 * octet,
},
}
func remainingFrameInfo(proto any, class FieldClass, pktBitOffset, pktBitLen int) FrameInfo {
return FrameInfo{
Protocol: "Ethernet data payload",
PacketBitOffset: pktBitOffset,
Fields: []FrameField{
{
Class: class,
BitLength: pktBitLen - pktBitOffset,
}},
}
}
+1 -1
View File
@@ -97,7 +97,7 @@ func (tfrm Frame) SetOffsetAndFlags(offset uint8, flags Flags) {
// HeaderLength uses Offset field to calculate the total length of
// the TCP header including options. Performs no validation.
func (tfrm Frame) HeaderLength() (tcpWords int) {
func (tfrm Frame) HeaderLength() (lengthInBytes int) {
offset, _ := tfrm.OffsetAndFlags()
return 4 * int(offset)
}