Files
lneto/internet/stack-ethernet.go
T
Pat Whittingslow a2970b923d add ipv6 to xnet.StackAsync (#107)
* add ipv6 to xnet.StackAsync

* dns improvements

* improve DNS workings of StackAsync

* add tentative ICMPv6

* work on prefixes and fix some small bugs, plan UDP/TCP6

* fix bugs in StackAsync and ipv4.Prefix.Contains

* update arpsubtable

* completely remove legacy internet.StackIP for StackIPv4/v6

* ipv4/ipv6 tcp/udp

* add TCP6/UDP6 dialing APIs

* add xnet.Stack6 interface

* more ipv6 integration into StackAsync; various tweaks to lneto and documentation+TODOs

* add stack6 tests

* replace netip.Prefix with ipv4.Prefix where it makes sense
2026-05-13 15:31:18 -03:00

213 lines
6.4 KiB
Go

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:
ls.handlers.info("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
}