package ethernet import ( "encoding/binary" "errors" "github.com/soypat/lneto" ) // NewFrame returns a EthFrame with data set to buf. // An error is returned if the buffer size is smaller than 14. // Users should still call [EthFrame.ValidateSize] before working // with payload/options of frames to avoid panics. func NewFrame(buf []byte) (Frame, error) { if len(buf) < sizeHeaderNoVLAN { return Frame{buf: nil}, errShort } return Frame{buf: buf}, nil } // Frame encapsulates the raw data of an Ethernet frame // without including preamble (first byte is start of destination address) // and provides methods for manipulating, validating and // retrieving fields and payload data. See [IEEE 802.3]. // // [IEEE 802.3]: https://standards.ieee.org/ieee/802.3/7071/ type Frame struct { buf []byte } // RawData returns the underlying slice with which the frame was created. func (efrm Frame) RawData() []byte { return efrm.buf } // HeaderLength returns the length of the ethernet packet header. Nominally returns 14; or 18 for VLAN packets. func (efrm Frame) HeaderLength() int { if efrm.IsVLAN() { return 18 } return sizeHeaderNoVLAN } // Payload returns the data portion of the ethernet packet with handling of VLAN packets. func (efrm Frame) Payload() []byte { hl := efrm.HeaderLength() et := efrm.EtherTypeOrSize() if et.IsSize() { return efrm.buf[hl:et] } return efrm.buf[hl:] } // DestinationHardwareAddr returns the target's MAC/hardware address for the ethernet packet. func (efrm Frame) DestinationHardwareAddr() (dst *[6]byte) { return (*[6]byte)(efrm.buf[0:6]) } // IsBroadcast returns true if the destination is the broadcast address ff:ff:ff:ff:ff:ff, false otherwise. func (efrm Frame) IsBroadcast() bool { return efrm.buf[0] == 0xff && efrm.buf[1] == 0xff && efrm.buf[2] == 0xff && efrm.buf[3] == 0xff && efrm.buf[4] == 0xff && efrm.buf[5] == 0xff } // SourceHardwareAddr returns the sender's MAC/hardware address of the ethernet packet. func (efrm Frame) SourceHardwareAddr() (src *[6]byte) { return (*[6]byte)(efrm.buf[6:12]) } // EtherTypeOrSize returns the EtherType/Size field of the ethernet packet. // Caller should check if the field is actually a valid EtherType or if it represents the Ethernet payload size with [EtherType.IsSize]. func (efrm Frame) EtherTypeOrSize() Type { return Type(binary.BigEndian.Uint16(efrm.buf[12:14])) } // SetEtherType sets the EtherType field of the ethernet packet. See [EtherType] and [Frame.EtherTypeOrSize]. func (efrm Frame) SetEtherType(v Type) { binary.BigEndian.PutUint16(efrm.buf[12:14], uint16(v)) } // VLANTag returns the VLAN tag field following the TPID=0x8100. See [VLANTag]. Call [Frame.ValidateSize] to ensure this function does not panic. func (efrm Frame) VLANTag() VLANTag { return VLANTag(binary.BigEndian.Uint16(efrm.buf[14:16])) } // SetVLANTag sets the VLAN tag field of the Ethernet Header. See [VLANTag]. Call [Frame.ValidateSize] to ensure this function does not panic. func (efrm Frame) SetVLANTag(vt VLANTag) { binary.BigEndian.PutUint16(efrm.buf[14:16], uint16(vt)) } // VLANEtherType returns the [EtherType] for a VLAN ethernet frame (octet position 16). Call [Frame.ValidateSize] to ensure this function does not panic. func (efrm Frame) VLANEtherType() Type { return Type(binary.BigEndian.Uint16(efrm.buf[16:18])) } // SetVLANEtherType sets the [EtherType] for a VLAN ethernet frame (octet position 16). Call [Frame.ValidateSize] to ensure this function does not panic. func (efrm Frame) SetVLANEtherType(vt Type) { binary.BigEndian.PutUint16(efrm.buf[16:18], uint16(vt)) } // IsVLAN returns true if the SizeOrEtherType is set to the VLAN tag 0x8100. This // indicates the EthernetHeader is invalid as-is and instead of EtherType the field // contains the first two octets of a 4 octet 802.1Q VLAN tag. In this case 4 more bytes // must be read from the wire, of which the last 2 of these bytes contain the actual // SizeOrEtherType field, which needs to be validated yet again in case the packet is // a VLAN double-tap packet. func (efrm Frame) IsVLAN() bool { return efrm.EtherTypeOrSize() == TypeVLAN } // ClearHeader zeros out the fixed(non-variable) header contents. func (frm Frame) ClearHeader() { for i := range frm.buf[:sizeHeaderNoVLAN] { frm.buf[i] = 0 } } // // Validation API. // var ( errShort = errors.New("ethernet: too short") errShortVLAN = errors.New("ethernet: short VLAN") ) // 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 (efrm Frame) ValidateSize(v *lneto.Validator) { sz := efrm.EtherTypeOrSize() if sz.IsSize() && len(efrm.buf) < int(sz) { v.AddError(errShort) } if sz == TypeVLAN && len(efrm.buf) < 18 { v.AddError(errShortVLAN) } }