mirror of
https://github.com/soypat/lneto.git
synced 2026-08-11 10:23:47 +00:00
132 lines
4.7 KiB
Go
132 lines
4.7 KiB
Go
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 correct 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)
|
|
}
|
|
}
|