package internet import ( "encoding/binary" "io" "log/slog" "math" "github.com/soypat/lneto" "github.com/soypat/lneto/ethernet" "github.com/soypat/lneto/internal" ) // StackEthernetConfig contains configuration parameters for [StackEthernet]. type StackEthernetConfig struct { // MTU is the Maximum Transmission Unit, representing the maximum ethernet // payload size in bytes (excluding the 14-byte ethernet header). // Standard ethernet MTU is 1500. Must be between 256 and 65535. MTU int // MaxNodes is the maximum number of protocol handlers (e.g., IP, ARP) // that can be registered with the stack. Must be greater than 0. MaxNodes int // MAC is the local hardware (MAC) address for this ethernet interface. MAC [6]byte // Gateway is the hardware (MAC) address of the default gateway. // Outgoing frames will be addressed to this MAC. Gateway [6]byte // AppendCRC32, if true, appends a 32-bit CRC to outgoing frames. // Only needed if the PHY does not handle CRC generation. AppendCRC32 bool // CRC32Update is a IEEE CRC32 implementation that should be provided if AppendCRC32 is set. CRC32Update func(crc uint32, p []byte) uint32 } type StackEthernet struct { connID uint64 handlers handlers mac [6]byte gwmac [6]byte mtu uint16 acceptMulticast bool onSend func(p []byte) // crcupdate set when crc32 has been configured to be appended. crcupdate func(crc uint32, p []byte) uint32 } func (ls *StackEthernet) SetGateway6(gw [6]byte) { ls.gwmac = gw } func (ls *StackEthernet) Gateway6() (gw [6]byte) { return ls.gwmac } func (ls *StackEthernet) SetAcceptMulticast(accept bool) { ls.acceptMulticast = accept } func (ls *StackEthernet) SetHardwareAddr6(mac [6]byte) { ls.mac = mac } func (ls *StackEthernet) HardwareAddr6() [6]byte { return ls.mac } func (ls *StackEthernet) OnEncapsulate(cb func([]byte)) { ls.onSend = cb } // Reset6 resets the stack with the given parameters. // // Deprecated: Use [StackEthernet.Configure] instead. func (ls *StackEthernet) Reset6(mac, gateway [6]byte, mtu, maxNodes int) error { return ls.Configure(StackEthernetConfig{ MTU: mtu, MaxNodes: maxNodes, MAC: mac, Gateway: gateway, }) } // Configure resets and configures the ethernet stack with the given configuration. // It validates the configuration parameters and resets internal state. // The connection ID is incremented on each call to invalidate existing connections. func (ls *StackEthernet) Configure(cfg StackEthernetConfig) error { if cfg.MTU > ethernet.MaxMTU || cfg.MTU < ethernet.MinimumMTU { return lneto.ErrInvalidConfig } else if cfg.MaxNodes <= 0 { return lneto.ErrInvalidConfig } else if cfg.AppendCRC32 && cfg.CRC32Update == nil { return lneto.ErrInvalidConfig } ls.handlers.reset("StackEthernet", cfg.MaxNodes) *ls = StackEthernet{ connID: ls.connID + 1, handlers: ls.handlers, mac: cfg.MAC, gwmac: cfg.Gateway, mtu: uint16(cfg.MTU), acceptMulticast: ls.acceptMulticast, } if cfg.AppendCRC32 { ls.crcupdate = cfg.CRC32Update } return nil } // MaxFrameLength returns the maximum ethernet frame length in bytes, which is the MTU plus the Ethernet header (14 bytes) and CRC (4 bytes if enabled). // This is the maximum size of an Ethernet frame that can be sent from the stack. func (ls *StackEthernet) MaxFrameLength() int { base := int(ls.mtu) + 14 if ls.crcupdate != nil { base += 4 } return base } // MTU is the Maximum Transmission Unit of the stack corresponding // to the maximum payload size of an ethernet frame that can be sent through the stack. // Important to note that the actual ethernet frame size is MTU + Ethernet header (14) + CRC (4 if enabled), this is known as the Maximum Frame Length. func (ls *StackEthernet) MTU() int { return int(ls.mtu) } func (ls *StackEthernet) ConnectionID() *uint64 { return &ls.connID } func (ls *StackEthernet) LocalPort() uint16 { return 0 } func (ls *StackEthernet) Protocol() uint64 { return 1 } func (ls *StackEthernet) RegisterEthernet(h lneto.StackNode) error { proto := h.Protocol() if proto > math.MaxUint16 || proto <= 1500 { return lneto.ErrInvalidConfig } return ls.handlers.registerByProto(nodeFromStackNode(h, 0, proto, nil)) } func (ls *StackEthernet) Demux(carrierData []byte, frameOffset int) (err error) { pkt := carrierData[frameOffset:] efrm, err := ethernet.NewFrame(pkt) if err != nil { return err } etype := efrm.EtherTypeOrSize() dstaddr := efrm.DestinationHardwareAddr() var vld lneto.Validator if !efrm.IsBroadcast() && ls.mac != *dstaddr { if !ls.acceptMulticast || dstaddr[0]&1 == 0 { goto DROP } } efrm.ValidateSize(&vld) if vld.HasError() { return vld.ErrPop() } if h, err := ls.handlers.demuxByProto(efrm.Payload(), 0, uint16(etype)); h != nil { return err } DROP: // Frames not addressed to us are routine noise on a shared LAN (multicast/ // broadcast spam). Log at debug so a production logger at info level gates it // out instead of allocating slog attrs per dropped packet. if internal.LogEnabled(ls.handlers.logger.log, slog.LevelDebug) { ls.handlers.debug("LinkStack:drop-packet", internal.SlogAddr6("dsthw", dstaddr), slog.String("ethertype", efrm.EtherTypeOrSize().String())) } return lneto.ErrPacketDrop } func (ls *StackEthernet) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (n int, err error) { mtu := ls.mtu dst := carrierData[offsetToFrame:] requiredSize := int(mtu) + 14 if ls.crcupdate != nil { requiredSize += 4 } if len(dst) < requiredSize { return 0, io.ErrShortBuffer } efrm, err := ethernet.NewFrame(dst) if err != nil { return 0, err } *efrm.DestinationHardwareAddr() = ls.gwmac var h *node // Children (IP/ARP) start at offset 14 (after ethernet header). // For IP: offsetToIP=14, offsetToFrame=14 // For ARP: offsetToIP=-1, offsetToFrame=14 (but ARP ignores offsetToIP) // Clip carrierData to MTU to prevent writes beyond MTU limit. payloadOffset := offsetToFrame + 14 mtuLimit := payloadOffset + int(mtu) h, n, err = ls.handlers.encapsulateAny(carrierData[:mtuLimit], payloadOffset, payloadOffset) if n == 0 { return n, err } // Found packet *efrm.SourceHardwareAddr() = ls.mac efrm.SetEtherType(ethernet.Type(h.proto)) n += 14 // Pad to minimum Ethernet frame size (60 bytes without CRC, 64 with). // Frames shorter than this are "runt frames" and may be dropped by switches/routers // according to 802.3 minimum length of 64 octets. const minFrameSize = 60 for n < minFrameSize { dst[n] = 0 n++ } if ls.onSend != nil { ls.onSend(dst[:n]) } if ls.crcupdate != nil { crc := ls.crcupdate(0, carrierData[offsetToFrame:offsetToFrame+n]) binary.LittleEndian.PutUint32(carrierData[offsetToFrame+n:], crc) n += 4 } return n, err }