Files
Pat Whittingslow 63f7870fe5 Add round-robin implementation (#66)
* add handler.encapsulateNode

* implement round robin handler approach

* simplify round robin implementation

* leave TODO

* internet.node touch up

* fix conflict resolve f-up

* replace certain ErrShortBuffer with ErrTruncatedFrame error
2026-04-09 09:36:17 -03:00

252 lines
8.9 KiB
Go

package ipv4
import (
"encoding/binary"
"fmt"
"net/netip"
"github.com/soypat/lneto"
)
// NewFrame returns a new [Frame] with data set to buf.
// An error is returned if the buffer size is smaller than 20.
// Users should still call [Frame.ValidateSize] before working
// with payload/options of frames to avoid panics.
func NewFrame(buf []byte) (Frame, error) {
if len(buf) < sizeHeader {
return Frame{buf: nil}, lneto.ErrTruncatedFrame
}
return Frame{buf: buf}, nil
}
// Frame encapsulates the raw data of an IPv4 packet
// and provides methods for manipulating, validating and
// retreiving fields and payload data. See [RFC791].
// Below is an example of setting IPv4 fields for MDNS:
//
// [RFC791]: https://tools.ietf.org/html/rfc791
type Frame struct {
buf []byte
}
// RawData returns the underlying slice with which the frame was created.
func (ifrm Frame) RawData() []byte { return ifrm.buf }
// HeaderLength returns the length of the IPv4 header as calculated using IHL. It includes IP options.
func (ifrm Frame) HeaderLength() int {
return int(ifrm.ihl()) * 4
}
func (ifrm Frame) ihl() uint8 { return ifrm.buf[0] & 0xf }
func (ifrm Frame) version() uint8 { return ifrm.buf[0] >> 4 }
// VersionAndIHL returns the version and IHL fields in the IPv4 header. Version should always be 4.
func (ifrm Frame) VersionAndIHL() (version, IHL uint8) {
v := ifrm.buf[0]
return v >> 4, v & 0xf
}
// SetVersionAndIHL sets the version and IHL fields in the IPv4 header. Version should always be 4.
func (ifrm Frame) SetVersionAndIHL(version, IHL uint8) { ifrm.buf[0] = version<<4 | IHL&0xf }
// ToS (Type of Service) contains Differential Services Code Point (DSCP) and
// Explicit Congestion Notification (ECN) union data.
//
// DSCP originally defined as the type of service (ToS), this field specifies
// differentiated services (DiffServ) per RFC 2474. Real-time data streaming
// makes use of the DSCP field. An example is Voice over IP (VoIP), which is
// used for interactive voice services.
//
// ECN is defined in RFC 3168 and allows end-to-end notification of
// network congestion without dropping packets. ECN is an optional feature available
// when both endpoints support it and effective when also supported by the underlying network.
func (ifrm Frame) ToS() ToS {
return ToS(ifrm.buf[1])
}
// SetToS sets ToS field. See [Frame.ToS].
func (ifrm Frame) SetToS(tos ToS) { ifrm.buf[1] = byte(tos) }
// TotalLength defines the entire packet size in bytes, including IP header and data.
// The minimum size is 20 bytes (IPv4 header without data) and the maximum is 65,535 bytes.
// All hosts are required to be able to reassemble datagrams of size up to 576 bytes,
// but most modern hosts handle much larger packets.
//
// Links may impose further restrictions on the packet size, in which case datagrams
// must be fragmented. Fragmentation in IPv4 is performed in either the
// sending host or in routers. Reassembly is performed at the receiving host.
func (ifrm Frame) TotalLength() uint16 {
return binary.BigEndian.Uint16(ifrm.buf[2:4])
}
// SetTotalLength sets TotalLength field. See [Frame.TotalLength].
func (ifrm Frame) SetTotalLength(tl uint16) { binary.BigEndian.PutUint16(ifrm.buf[2:4], tl) }
// ID is an identification field and is primarily used for uniquely
// identifying the group of fragments of a single IP datagram.
func (ifrm Frame) ID() uint16 {
return binary.BigEndian.Uint16(ifrm.buf[4:6])
}
// SetID sets ID field. See [Frame.ID].
func (ifrm Frame) SetID(id uint16) { binary.BigEndian.PutUint16(ifrm.buf[4:6], id) }
// Flags returns the [Flags] of the IP packet.
func (ifrm Frame) Flags() Flags {
return Flags(binary.BigEndian.Uint16(ifrm.buf[6:8]))
}
// SetFlags sets the IPv4 flags field. See [Flags].
func (ifrm Frame) SetFlags(flags Flags) {
binary.BigEndian.PutUint16(ifrm.buf[6:8], uint16(flags))
}
// TTL is an eight-bit time to live field limits a datagram's lifetime to prevent
// network failure in the event of a routing loop. In practice, the field
// is used as a hop count—when the datagram arrives at a router,
// the router decrements the TTL field by one. When the TTL field hits zero,
// the router discards the packet and typically sends an ICMP time exceeded message to the sender.
func (ifrm Frame) TTL() uint8 { return ifrm.buf[8] }
// SetTTL sets the IP frame's TTL field. See [Frame.TTL].
func (ifrm Frame) SetTTL(ttl uint8) { ifrm.buf[8] = ttl }
// Protocol field defines the protocol used in the data portion of the IP datagram. TCP is 6, UDP is 17.
// See [IPProto].
func (ifrm Frame) Protocol() lneto.IPProto { return lneto.IPProto(ifrm.buf[9]) }
// SetProtocol sets protocol field. See [Frame.Protocol] and [lneto.IPProto].
func (ifrm Frame) SetProtocol(proto lneto.IPProto) { ifrm.buf[9] = uint8(proto) }
// CRC returns the cyclic-redundancy-check (checksum) field of the IPv4 header.
func (ifrm Frame) CRC() uint16 {
return binary.BigEndian.Uint16(ifrm.buf[10:12])
}
// SetCRC sets the CRC field of the IP packet. See [Frame.CRC].
func (ifrm Frame) SetCRC(cs uint16) {
binary.BigEndian.PutUint16(ifrm.buf[10:12], cs)
}
// CalculateHeaderCRC calculates the CRC for this IPv4 frame including CRC field.
// If the result of this function is 0 then the CRC is valid for the frame.
// To calculate the CRC for a frame set the CRC field to zero and then call this function:
//
// ifrm.SetCRC(0)
// crcValue := ifrm.CalculateHeaderCRC()
// ifrm.SetCRC(crcValue)
func (ifrm Frame) CalculateHeaderCRC() uint16 {
var crc lneto.CRC791
ifrm.CRCWriteHeader(&crc)
return crc.Sum16()
}
func (ifrm Frame) CRCWriteHeader(crc *lneto.CRC791) {
crc.WriteEven(ifrm.buf[:20])
}
// CRCWriteTCPPseudo is used to calculate the TCP checksum for IPv4 framed packets.
// To calculate the CRC of a TCP frame:
//
// ifrm.CRCWriteTCPPseudo(&crc)
// // tfrm.SetCRC(0) // Zero if calculating frame for sending out.
// crcValue := crc.PayloadSum16(ifrm.Payload()) // If validating a received frame crcValue should be zero here.
func (ifrm Frame) CRCWriteTCPPseudo(crc *lneto.CRC791) {
crc.WriteEven(ifrm.sourceAndDestinationAddr())
crc.AddUint16(ifrm.TotalLength() - uint16(ifrm.HeaderLength()))
crc.AddUint16(uint16(ifrm.Protocol()))
}
// CRCWriteUDPPseudo writes the IPv4 UDP pseudo-header into crc for checksum
// calculation. Typical usage for computing or validating a UDP checksum:
//
// ifrm.CRCWriteUDPPseudo(&crc, ufrm.Length())
// // ufrm.SetCRC(0) // Zero before computing checksum for transmission.
// crcValue := crc.PayloadSum16(ifrm.Payload()) // For received frames, crcValue should be zero if valid.
func (ifrm Frame) CRCWriteUDPPseudo(crc *lneto.CRC791, udpLength uint16) {
crc.WriteEven(ifrm.sourceAndDestinationAddr())
crc.AddUint16(udpLength)
crc.AddUint16(uint16(ifrm.Protocol()))
}
func (ifrm Frame) sourceAndDestinationAddr() []byte {
return ifrm.buf[12:20]
}
// SourceAddr returns pointer to the source IPv4 address in the IP header.
func (ifrm Frame) SourceAddr() *[4]byte {
return (*[4]byte)(ifrm.buf[12:16])
}
// DestinationAddr returns pointer to the destination IPv4 address in the IP header.
func (ifrm Frame) 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 [Frame.ValidateSize] beforehand to avoid panic.
func (ifrm Frame) Payload() []byte {
off := ifrm.HeaderLength()
l := ifrm.TotalLength()
return ifrm.buf[off:l]
}
// Options returns the options portion of the IPv4 header. May be zero lengthed.
// Be sure to call [Frame.ValidateSize] beforehand to avoid panic.
func (ifrm Frame) Options() []byte {
off := ifrm.HeaderLength()
return ifrm.buf[sizeHeader:off]
}
// ClearHeader zeros out the fixed(non-variable) header contents.
func (ifrm Frame) ClearHeader() {
for i := range ifrm.buf[:sizeHeader] {
ifrm.buf[i] = 0
}
}
//
// Validation API.
//
// 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 (ifrm Frame) ValidateSize(v *lneto.Validator) {
ihl := ifrm.ihl()
tl := ifrm.TotalLength()
if tl < sizeHeader {
v.AddError(lneto.ErrInvalidLengthField)
}
if int(tl) > len(ifrm.RawData()) {
v.AddError(lneto.ErrTruncatedFrame)
}
if ihl < 5 || uint16(ihl)*4 > tl {
v.AddError(lneto.ErrInvalidLengthField)
}
}
// ValidateExceptCRC checks for invalid frame values but does not check CRC.
func (ifrm Frame) ValidateExceptCRC(v *lneto.Validator) {
ifrm.ValidateSize(v)
flags := ifrm.Flags()
if ifrm.version() != 4 {
v.AddError(lneto.ErrInvalidField)
}
if v.Flags()&lneto.ValidateEvilBit != 0 && flags.IsEvil() {
v.AddError(lneto.ErrPacketDrop)
}
}
func (ifrm Frame) String() string {
dst := netip.AddrFrom4(*ifrm.DestinationAddr())
src := netip.AddrFrom4(*ifrm.SourceAddr())
hl := ifrm.HeaderLength()
tl := int(ifrm.TotalLength())
ttl := ifrm.TTL()
id := ifrm.ID()
proto := ifrm.Protocol()
tos := ifrm.ToS()
return fmt.Sprintf("IP %s SRC=%s DST=%s LEN=%d OPT=%d TTL=%d ID=%d ToS=0x%x", proto.String(), src.String(), dst.String(), tl, tl-hl, ttl, id, tos)
}