From 4476c7bae37f3bb56b4b7e57e31ea6affb1e06c1 Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 24 Dec 2024 16:17:48 -0300 Subject: [PATCH] generalize ARP to any protocol --- lneto/definitions.go | 10 ++++- lneto/frames.go | 104 +++++++++++++++++++++++++++++++------------ lneto/stringers.go | 21 ++++++++- lneto/validation.go | 14 +++++- 4 files changed, 117 insertions(+), 32 deletions(-) diff --git a/lneto/definitions.go b/lneto/definitions.go index b0af83f..8e34fec 100644 --- a/lneto/definitions.go +++ b/lneto/definitions.go @@ -1,6 +1,6 @@ package lneto -//go:generate stringer -type=EtherType,IPProto -linecomment -output stringers.go . +//go:generate stringer -type=EtherType,IPProto,ARPOp -linecomment -output stringers.go . type EtherType uint16 @@ -245,3 +245,11 @@ const ( IPProtoAGGFRAG IPProto = 144 // AGGFRAG Encapsulation payload for ESP IPProtoNSH IPProto = 145 // Network Service Header ) + +// ARPOp represents the type of ARP packet, either request or reply/response. +type ARPOp uint8 + +const ( + ARPRequest ARPOp = 1 // request + ARPReply ARPOp = 2 // reply +) diff --git a/lneto/frames.go b/lneto/frames.go index 86795dd..07b3bee 100644 --- a/lneto/frames.go +++ b/lneto/frames.go @@ -9,8 +9,8 @@ import ( func NewEthFrame(buf []byte) EthFrame { return EthFrame{buf: mustBufferFrameLen(buf, sizeHeaderEthNoVLAN, "Ethernet")} } -func NewARPv4Frame(buf []byte) ARPv4Frame { - return ARPv4Frame{buf: mustBufferFrameLen(buf, sizeHeaderARPv4, "ARPv4")} +func NewARPFrame(buf []byte) ARPFrame { + return ARPFrame{buf: mustBufferFrameLen(buf, sizeHeaderARPv4, "ARPv4")} } func NewIPv4Frame(buf []byte) IPv4Frame { return IPv4Frame{buf: mustBufferFrameLen(buf, sizeHeaderIPv4, "IPv4")} @@ -82,55 +82,84 @@ func (efrm EthFrame) IsVLAN() bool { return efrm.EtherTypeOrSize() == EtherTypeVLAN } -type ARPv4Frame struct { +type ARPFrame struct { buf []byte } // RawData returns the underlying slice with which the frame was created. -func (afrm ARPv4Frame) RawData() []byte { return afrm.buf } +func (afrm ARPFrame) RawData() []byte { return afrm.buf } // HardwareType specifies the network link protocol type. Example: Ethernet is 1. -func (afrm ARPv4Frame) Hardware() (Type uint16, length uint8) { +func (afrm ARPFrame) Hardware() (Type uint16, length uint8) { Type = binary.BigEndian.Uint16(afrm.buf[0:2]) length = afrm.buf[4] return Type, length } -func (afrm ARPv4Frame) SetHardware(Type uint16, length uint8) { +// SetHardware sets the networl link protocol type. See [ARPFrame.SetHardware]. +func (afrm ARPFrame) SetHardware(Type uint16, length uint8) { binary.BigEndian.PutUint16(afrm.buf[0:2], Type) afrm.buf[4] = length } -func (afrm ARPv4Frame) Protocol() (Type uint16, length uint8) { - Type = binary.BigEndian.Uint16(afrm.buf[2:4]) +// Protocol returns the internet protocol type and length. See [EtherType]. +func (afrm ARPFrame) Protocol() (Type EtherType, length uint8) { + Type = EtherType(binary.BigEndian.Uint16(afrm.buf[2:4])) length = afrm.buf[5] return Type, length } -func (afrm ARPv4Frame) SetProtocol(Type uint16, length uint8) { - binary.BigEndian.PutUint16(afrm.buf[2:4], Type) +// SetProtocol sets the protocol type and length fields of the ARP frame. See [ARPFrame.Protocol] and [EtherType]. +func (afrm ARPFrame) SetProtocol(Type EtherType, length uint8) { + binary.BigEndian.PutUint16(afrm.buf[2:4], uint16(Type)) afrm.buf[5] = length } -func (afrm ARPv4Frame) SetOperation(b uint8) { afrm.buf[6] = b } +// Operation returns the ARP header operation field. See [ARPOp]. +func (afrm ARPFrame) Operation() ARPOp { return ARPOp(afrm.buf[6]) } -func (afrm ARPv4Frame) IsOperationRequest() bool { return afrm.buf[6] == 1 } -func (afrm ARPv4Frame) IsOperationReply() bool { return afrm.buf[6] == 2 } +// SetOperation sets the ARP header operation field. See [ARPOp]. +func (afrm ARPFrame) SetOperation(b ARPOp) { afrm.buf[6] = uint8(b) } -// Sender returns the MAC (hardware) and IP (protocol) addresses of sender of ARP packet. -// In an ARP request MAC is used to indicate -// the address of the host sending the request. In an ARP reply MAC is +// Sender returns the hardware (MAC) and protocol addresses of sender of ARP packet. +// In an ARP request MAC address is used to indicate +// the address of the host sending the request. In an ARP reply MAC address is // used to indicate the address of the host that the request was looking for. -func (afrm ARPv4Frame) Sender() (hardwareAddr *[6]byte, proto *[4]byte) { +func (afrm ARPFrame) Sender() (hardwareAddr []byte, proto []byte) { + _, hlen := afrm.Hardware() + _, ilen := afrm.Protocol() + return afrm.buf[8 : 8+hlen], afrm.buf[8+hlen : 8+hlen+ilen] +} + +// Target returns the hardware (MAC) and protocol addresses of target of ARP packet. +// In an ARP request MAC target is ignored. In ARP reply MAC is used to indicate the address of host that originated request. +func (afrm ARPFrame) Target() (hardwareAddr []byte, proto []byte) { + _, hlen := afrm.Hardware() + _, ilen := afrm.Protocol() + toff := 8 + hlen + ilen + return afrm.buf[toff : toff+hlen], afrm.buf[toff+hlen : toff+hlen+ilen] +} + +// Sender4 returns the IPv4 sender addresses. See [ARPFrame.Sender]. +func (afrm ARPFrame) Sender4() (hardwareAddr *[6]byte, proto *[4]byte) { return (*[6]byte)(afrm.buf[8:14]), (*[4]byte)(afrm.buf[14:18]) } -// Target returns the MAC (hardware) and IP (protocol) addresses of target of ARP packet. -// In an ARP request MAC target is ignored. In ARP reply MAC is used to indicate the address of host that originated request. -func (afrm ARPv4Frame) Target() (hardwareAddr *[6]byte, proto *[4]byte) { +// Target4 returns the IPv4 target addresses. See [ARPFrame.Sender]. +func (afrm ARPFrame) Target4() (hardwareAddr *[6]byte, proto *[4]byte) { return (*[6]byte)(afrm.buf[18:24]), (*[4]byte)(afrm.buf[24:28]) } +// Sender6 returns the IPv6 sender addresses. See [ARPFrame.Sender]. +func (afrm ARPFrame) Sender16() (hardwareAddr *[6]byte, proto *[16]byte) { + return (*[6]byte)(afrm.buf[8:14]), (*[16]byte)(afrm.buf[14:30]) +} + +// Target6 returns the IPv6 target addresses. See [ARPFrame.Sender]. +func (afrm ARPFrame) Target16() (hardwareAddr *[6]byte, proto *[16]byte) { + return (*[6]byte)(afrm.buf[30:36]), (*[16]byte)(afrm.buf[36:52]) +} + type IPv4Frame struct { buf []byte } @@ -138,14 +167,22 @@ type IPv4Frame struct { // RawData returns the underlying slice with which the frame was created. func (ifrm IPv4Frame) RawData() []byte { return ifrm.buf } -func (ifrm IPv4Frame) Version() uint8 { return ifrm.buf[0] & 0xf } -func (ifrm IPv4Frame) IHL() uint8 { return ifrm.buf[0] >> 4 } - // HeaderLength returns the length of the IPv4 header as calculated using IHL. It includes IP options. func (ifrm IPv4Frame) HeaderLength() int { - return int(ifrm.IHL()) * 4 + return int(ifrm.ihl()) * 4 } +func (ifrm IPv4Frame) ihl() uint8 { + return ifrm.buf[0] >> 4 +} + +// VersionAndIHL returns the version and IHL fields in the IPv4 header. Version should always be 4. +func (ifrm IPv4Frame) VersionAndIHL() (version, IHL uint8) { + v := ifrm.buf[0] + return v & 0xf, v >> 4 +} + +// SetVersionAndIHL sets the version and IHL fields in the IPv4 header. Version should always be 4. func (ifrm IPv4Frame) SetVersionAndIHL(version, IHL uint8) { ifrm.buf[0] = version&0xf | IHL<<4 } // ToS (Type of Service) contains Differential Services Code Point (DSCP) and @@ -217,7 +254,7 @@ func (ifrm IPv4Frame) Protocol() IPProto { return IPProto(ifrm.buf[9]) } // SetProtocol sets protocol field. See [IPv4Frame.Protocol] and [IPProto]. func (ifrm IPv4Frame) SetProtocol(proto IPProto) { ifrm.buf[9] = uint8(proto) } -// CRC returns the cyclic-redundancy check field of the IPv4 packet. +// CRC returns the cyclic-redundancy-check (checksum) field of the IPv4 header. func (ifrm IPv4Frame) CRC() uint16 { return binary.BigEndian.Uint16(ifrm.buf[10:12]) } @@ -238,7 +275,7 @@ func (ifrm IPv4Frame) CalculateHeaderCRC() uint16 { func (ifrm IPv4Frame) crcWriteTCPPseudo(crc *CRC791) { crc.Write(ifrm.SourceAddr()[:]) crc.Write(ifrm.DestinationAddr()[:]) - crc.AddUint16(ifrm.TotalLength() - 4*uint16(ifrm.IHL())) + crc.AddUint16(ifrm.TotalLength() - 4*uint16(ifrm.ihl())) crc.AddUint16(uint16(ifrm.Protocol())) } @@ -258,6 +295,8 @@ func (ifrm IPv4Frame) DestinationAddr() *[4]byte { return (*[4]byte)(ifrm.buf[16:20]) } +// Payload returns the contents of the IPv4 packet, which may be zero sized. +// Be sure to call [IPv4Frame.ValidateSize] beforehand to avoid panic. func (ifrm IPv4Frame) Payload() []byte { off := ifrm.HeaderLength() l := ifrm.TotalLength() @@ -272,6 +311,7 @@ type IPv6Frame struct { func (i6frm IPv6Frame) RawData() []byte { return i6frm.buf } // Payload returns the contents of the IPv6 packet, which may be zero sized. +// Be sure to call [IPv6Frame.ValidateSize] beforehand to avoid panic. func (i6frm IPv6Frame) Payload() []byte { pl := i6frm.PayloadLength() return i6frm.buf[sizeHeaderIPv6 : sizeHeaderIPv6+pl] @@ -432,7 +472,7 @@ func (tfrm TCPFrame) SetCRC(checksum uint16) { func (tfrm TCPFrame) CalculateIPv4CRC(ifrm IPv4Frame) uint16 { var crc CRC791 ifrm.crcWriteTCPPseudo(&crc) - expectLen := int(ifrm.TotalLength()) - 4*int(ifrm.IHL()) + expectLen := int(ifrm.TotalLength()) - ifrm.HeaderLength() if expectLen != len(tfrm.buf) { println("unexpected TCP buffer length mismatches IPv4 header total length", expectLen, len(tfrm.buf)) } @@ -466,6 +506,8 @@ func (tfrm TCPFrame) UrgentPtr() uint16 { return binary.BigEndian.Uint16(tfrm.buf[18:20]) } +// Payload returns the payload content section of the TCP packet (not including TCP options). +// Be sure to call [TCPFrame.ValidateSize] beforehand to avoid panic. func (tfrm TCPFrame) Payload() []byte { return tfrm.buf[tfrm.HeaderLength():] } @@ -477,15 +519,17 @@ type UDPFrame struct { // RawData returns the underlying slice with which the frame was created. func (ufrm UDPFrame) RawData() []byte { return ufrm.buf } +// SourcePort identifies the sending port for the UDP packet. Must be non-zero. func (ufrm UDPFrame) SourcePort() uint16 { return binary.BigEndian.Uint16(ufrm.buf[0:2]) } -// SetSourcePort sets UDP source port. See [UDPFrame.SetSourcePort] +// SetSourcePort sets UDP source port. See [UDPFrame.SourcePort] func (ufrm UDPFrame) SetSourcePort(src uint16) { binary.BigEndian.PutUint16(ufrm.buf[0:2], src) } +// DestinationPort identifies the receiving port for the UDP packet. Must be non-zero. func (ufrm UDPFrame) DestinationPort() uint16 { return binary.BigEndian.Uint16(ufrm.buf[2:4]) } @@ -507,6 +551,7 @@ func (ufrm UDPFrame) SetLength(length uint16) { binary.BigEndian.PutUint16(ufrm.buf[4:6], length) } +// CRC returns the checksum field in the UDP header. func (ufrm UDPFrame) CRC() uint16 { return binary.BigEndian.Uint16(ufrm.buf[6:8]) } @@ -516,7 +561,8 @@ func (ufrm UDPFrame) SetCRC(checksum uint16) { binary.BigEndian.PutUint16(ufrm.buf[6:8], checksum) } -// Payload returns the data part of the UDP frame. +// Payload returns the payload content section of the UDP packet. +// Be sure to call [UDPFrame.ValidateSize] beforehand to avoid panic. func (ufrm UDPFrame) Payload() []byte { l := ufrm.Length() return ufrm.buf[sizeHeaderUDP:l] diff --git a/lneto/stringers.go b/lneto/stringers.go index a89be2d..e8a7c6f 100644 --- a/lneto/stringers.go +++ b/lneto/stringers.go @@ -1,4 +1,4 @@ -// Code generated by "stringer -type=EtherType,IPProto -linecomment -output stringers.go ."; DO NOT EDIT. +// Code generated by "stringer -type=EtherType,IPProto,ARPOp -linecomment -output stringers.go ."; DO NOT EDIT. package lneto @@ -289,3 +289,22 @@ func (i IPProto) String() string { return "IPProto(" + strconv.FormatInt(int64(i), 10) + ")" } } +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ARPRequest-1] + _ = x[ARPReply-2] +} + +const _ARPOp_name = "requestreply" + +var _ARPOp_index = [...]uint8{0, 7, 12} + +func (i ARPOp) String() string { + i -= 1 + if i >= ARPOp(len(_ARPOp_index)-1) { + return "ARPOp(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _ARPOp_name[_ARPOp_index[i]:_ARPOp_index[i+1]] +} diff --git a/lneto/validation.go b/lneto/validation.go index 12be388..e2802e4 100644 --- a/lneto/validation.go +++ b/lneto/validation.go @@ -8,11 +8,23 @@ var ( errShortIPv4 = errors.New("IPv4 total length exceeds frame") errBadIPv4TL = errors.New("IPv4 short total length") errShortIPv6 = errors.New("IPv6 payload length exceeds frame") - + errShortARP = errors.New("bad ARP size") errShortTCP = errors.New("TCP offset exceeds frame") errBadTCPOff = errors.New("TCP offset invalid") ) +// ValidateSize checks the frame's size fields and compares with the actual buffer +// the frame. It returns a non-nil error on finding an inconsistency. +func (afrm ARPFrame) ValidateSize() error { + _, hlen := afrm.Hardware() + _, ilen := afrm.Protocol() + minLen := 8 + 2*(hlen+ilen) + if len(afrm.buf) < int(minLen) { + return errShortARP + } + return nil +} + // ValidateSize checks the frame's size fields and compares with the actual buffer // the frame. It returns a non-nil error on finding an inconsistency. func (ufrm UDPFrame) ValidateSize() error {