mirror of
https://github.com/soypat/lneto.git
synced 2026-08-03 14:37:50 +00:00
813b7b5e57
* good code today * add netdev.Runner * round off APIs more * better interface method documentation * improve DHCP netstack API * add pico w netdev example * remove temp file * keep thinking about this. this is hard :/ * fix ci * small fixer * working dhcp * fix rebase API mismatches * begin adding espradio example * keep working on espradio, icmp not working * fix icmp by fixing dhcp
173 lines
7.6 KiB
Go
173 lines
7.6 KiB
Go
package netdev
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/netip"
|
|
|
|
"github.com/soypat/lneto"
|
|
"github.com/soypat/lneto/ethernet"
|
|
"github.com/soypat/lneto/internal"
|
|
)
|
|
|
|
type Interface[C any] struct {
|
|
dev DevEthernet
|
|
netlink Netlink[C]
|
|
ip netip.Prefix
|
|
// Below are values calculated from [DevEthernet] return values to avoid recalculation.
|
|
frameSize int
|
|
frameOff int
|
|
mtu int
|
|
mac [6]byte
|
|
}
|
|
|
|
// DevEthernet is an L2 capable device HAL. It is an abstraction
|
|
// for devices that send actively and are not polled by an external host.
|
|
//
|
|
// DevEthernet-specific initialization (WiFi join, PHY auto-negotiation,
|
|
// firmware loading) must complete BEFORE the device is used as a stack endpoint.
|
|
type DevEthernet interface {
|
|
// HardwareAddr6 returns the device's 6-byte MAC address.
|
|
// For PHY-only devices, returns the MAC provided at configuration.
|
|
HardwareAddr6() ([6]byte, error)
|
|
// SendEthFrameOffset transmits a complete Ethernet frame at offset given by [DevEthernet.MaxFrameSizeAndOffset].
|
|
// The frame includes the Ethernet header but NOT the FCS/CRC
|
|
// trailer (device or stack handles CRC as appropriate).
|
|
// SendEthFrameOffset blocks until the transmission is queued succesfully
|
|
// or finished sending. Should not be called concurrently
|
|
// unless user is sure the driver supports it.
|
|
SendOffsetEthFrame(offsetTxEthFrame []byte) error
|
|
// SetRecvHandler registers the function called when an Ethernet
|
|
// frame is received. Buffers needed by the device to operate efficiently
|
|
// should be allocated on its side. This function is mutually exclusive with EthPoll:
|
|
// use on or the other to receive data.
|
|
SetEthRecvHandler(handler func(rxEthframe []byte))
|
|
// EthPoll services the device. For poll-based devices (e.g. CYW43439
|
|
// over SPI), reads from the bus and invokes the handler for each
|
|
// received frame. This method is mutually exclusive with SetEthRecvHandler:
|
|
// use one or the other to receive data but not return data via both channels.
|
|
EthPoll(buf []byte) (ethFrameOff, ethernetBytes int, err error)
|
|
// MaxFrameSizeAndOffset returns the max complete device frame size
|
|
// (including headers and any overhead) for buffer allocation.
|
|
// The second value returned is the offset at which the ethernet frame
|
|
// should be stored when being passed to [DevEthernet.SendOffsetEthFrame].
|
|
// Buffers allocated should be maxEthernetFrameSize+frameOff where maxEthernetFrameSize
|
|
// is usually 1500 but less or equal to maxFrameSize-frameOff.
|
|
// MTU can be calculated doing:
|
|
// // mfu-(14+4+4) for:
|
|
// // ethernet header+ethernet CRC if present+ethernet VLAN overhead for VLAN support.
|
|
// mtu := dev.MaxFrameSizeAndOffset() - ethernet.MaxOverheadSize
|
|
MaxFrameSizeAndOffset() (maxFrameSize int, frameOff int)
|
|
}
|
|
|
|
// Stack is an abstraction for a networking stack.
|
|
type Stack interface {
|
|
// Configure configures this Stack with the argument mac, ip and gateway addresses.
|
|
// The Stack must resolve the gateway hardware address if set.
|
|
// Configure(mac net.HardwareAddr, ip netip.Prefix, gw netip.Addr) error
|
|
|
|
// EnableICMP enables responding/sending ICMP echo frames.
|
|
EnableICMP(enabled bool) error
|
|
// EnableDHCP enables DHCP on the device if enabled=true and performs a DHCP request.
|
|
EnableDHCP(ctx context.Context, enabled bool, reqIP netip.Addr) (assigned netip.Addr, routerGW netip.Addr, subnetBits int, _ error)
|
|
// Socket is a berkeley socket abstraction. Returns an [net.Listener] or [net.Conn] depending on laddr/raddr combination.
|
|
Socket(ctx context.Context, network string, family, sotype int, laddr, raddr netip.AddrPort) (c any, err error)
|
|
// EgressPackets instructs Stack to write outgoing packets into bufs and writing the sizes into sizes.
|
|
// offset can be used to tell the stack to start writing after an offset for each buffer.
|
|
// The size written into sizes includes only Ethernet frame size and so is independent of offset value.
|
|
EgressPackets(bufs [][]byte, sizes []int, offset int) error
|
|
// IngressPackets is called on incoming packets so the Stack can direct packets to
|
|
// their respective node/connection and update internal state.
|
|
// offset instructs the Stack to start reading packets at an offset.
|
|
IngressPackets(bufs [][]byte, offset int) error
|
|
}
|
|
|
|
// Netlink represents the physical part of a network device which can connect/disconnect.
|
|
// One netlink may correspond to many network devices for interconnected systems.
|
|
type Netlink[C any] interface {
|
|
// LinkConnect attempts to connect the Netlink if it was not already connected.
|
|
// It will block until it succeeds/fails and not retry after returning.
|
|
LinkConnect(connectParams C) error
|
|
// LinkDisconnect disconnects the Netlink immediately.
|
|
LinkDisconnect()
|
|
// Link notify sets the callback to be executed after connection state
|
|
// changes for the Netlink. The callback can signal an immediate reconnect is desired
|
|
// by setting reconnectNowRetries to a positive integer. The netlink should then retry connection
|
|
// immediately with the given reconnectParams. reconnectParams should not be nil if reconnectNowRetries is positive.
|
|
LinkNotify(cb NotifyCallback[C])
|
|
}
|
|
|
|
// NotifyCallback is a convenience type alias that serves mostly as semantic code documentation.
|
|
// NotifyCallback is called when a [Netlink] connects or disconnects from a network. The callback returns:
|
|
// - reconnectNowRetries: Amount of times to attempt reconnection before giving up.
|
|
// - reconnectParams: parameters to use in reconnection.
|
|
type NotifyCallback[C any] = func(connected bool) (reconnectNowRetries int, reconnectParams C)
|
|
|
|
// InterfaceConfig mostly optional configuration.
|
|
type InterfaceConfig struct {
|
|
// NetworkIP sets the network's IP range and this interface's IP address. See [netip.Prefix].
|
|
// This field is optional if not using a networking stack.
|
|
NetworkIP netip.Prefix
|
|
// HardwareAddr6 overrides the device hardware address during Init.
|
|
// This field is optional if [DevEthernet.HardwareAddr6] returns valid MAC.
|
|
HardwareAddr6 [6]byte
|
|
// MTU is the maximum ethernet payload size. Does not include ethernet header(14b) and FCS(4b).
|
|
// If MTU is zero the default ipv4.MTU value of 1500 is used.
|
|
MTU uint16
|
|
}
|
|
|
|
// Init initializes the interface from scratch with a netlink and device. If Init fails all methods on Interface are unsafe to call (panic).
|
|
func (iface *Interface[C]) Init(netlink Netlink[C], dev DevEthernet, cfg InterfaceConfig) (err error) {
|
|
if netlink == nil || dev == nil {
|
|
return lneto.ErrInvalidConfig
|
|
}
|
|
maxFrameSize, frameOff := dev.MaxFrameSizeAndOffset()
|
|
maxEthFrame := maxFrameSize - frameOff
|
|
maxEthPayload := maxEthFrame - 14
|
|
mtu := int(cfg.MTU)
|
|
if mtu == 0 {
|
|
mtu = min(1500, maxEthPayload)
|
|
}
|
|
if mtu > maxEthPayload {
|
|
return errors.New("MTU exceeds max frame size")
|
|
} else if mtu < ethernet.MinimumMTU || mtu > ethernet.MaxMTU {
|
|
return errors.New("bad DevEthernet max frame size and/or frame offset. typical is 1500,0")
|
|
}
|
|
var mac [6]byte
|
|
if internal.IsZeroed(cfg.HardwareAddr6) {
|
|
mac, err = dev.HardwareAddr6()
|
|
if err != nil {
|
|
return err
|
|
} else if internal.IsZeroed(mac) {
|
|
return lneto.ErrInvalidAddr
|
|
}
|
|
} else {
|
|
mac = cfg.HardwareAddr6
|
|
}
|
|
*iface = Interface[C]{
|
|
dev: dev,
|
|
netlink: netlink,
|
|
ip: cfg.NetworkIP,
|
|
frameSize: maxFrameSize,
|
|
frameOff: frameOff,
|
|
mtu: mtu,
|
|
mac: mac,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HardwareAddr6 returns the hardware address the [Interface] was configured with.
|
|
func (iface *Interface[C]) HardwareAddr6() [6]byte {
|
|
return iface.mac
|
|
}
|
|
|
|
// NetworkAddr returns the IP and subnet of the network behind the interface. See [netip.Prefix].
|
|
// May be unset/invalid.
|
|
func (iface *Interface[C]) NetworkAddr() netip.Prefix {
|
|
return iface.ip
|
|
}
|
|
|
|
func (iface *Interface[C]) bufsize() int {
|
|
return iface.frameOff + 14 + iface.mtu
|
|
}
|