huge tap/bridge overhaul; udp node; dhcp node; DHCP example

This commit is contained in:
soypat
2025-06-15 18:01:04 -03:00
parent 29ac9a3a1a
commit dcc7a95e62
16 changed files with 706 additions and 157 deletions
+43 -13
View File
@@ -24,7 +24,6 @@ type StackNode interface {
Encapsulate(carrierData []byte, frameOffset int) (int, error)
// Demux reads from the argument buffer where frameOffset is the offset of this StackNode's frame first byte.
// The stack node then dispatches(demuxes) the encapsulated frames to its corresponding sub-stack-node(s).
//
Demux(carrierData []byte, frameOffset int) error
LocalPort() uint16
Protocol() uint64
@@ -39,7 +38,6 @@ type node struct {
connID *uint64
demux func([]byte, int) error
encapsulate func([]byte, int) (int, error)
lastErrs [2]error
proto uint16
port uint16
}
@@ -57,34 +55,66 @@ func handleNodeError(nodesPtr *[]node, nodeIdx int, err error) (discarded bool)
panic("unreachable")
}
nodes := *nodesPtr
badConnID := nodes[nodeIdx].connID != nil && *nodes[nodeIdx].connID != nodes[nodeIdx].currConnID
if err == net.ErrClosed || badConnID {
if checkNodeErr(&nodes[nodeIdx], err) {
*nodesPtr = slices.Delete(nodes, nodeIdx, nodeIdx+1)
discarded = true
} else {
// Advance Queue of errors
nodes[nodeIdx].lastErrs[1] = nodes[nodeIdx].lastErrs[0]
nodes[nodeIdx].lastErrs[0] = err
}
}
return discarded
}
func checkNode(node *node) (discard bool) {
return node.demux == nil || node.connID != nil && node.currConnID != *node.connID
}
func checkNodeErr(node *node, err error) (discard bool) {
return checkNode(node) || (err != nil && err == net.ErrClosed)
}
func addNode(nodes *[]node, h StackNode, port uint16, protocol uint64) {
*nodes = append(*nodes, nodeFromStackNode(h, port, protocol))
}
func nodeFromStackNode(s StackNode, port uint16, protocol uint64) node {
if protocol > math.MaxUint16 {
panic(">16bit protocol number unsupported")
}
var currConnID uint64
connIDPtr := h.ConnectionID()
connIDPtr := s.ConnectionID()
if connIDPtr != nil {
currConnID = *connIDPtr
}
*nodes = append(*nodes, node{
return node{
currConnID: currConnID,
connID: connIDPtr,
demux: h.Demux,
encapsulate: h.Encapsulate,
demux: s.Demux,
encapsulate: s.Encapsulate,
proto: uint16(protocol),
port: port,
})
}
}
func getNode(nodes []node, port uint16, protocol uint16) (node *node) {
for i := range nodes {
node := &nodes[i]
if node.port == port && node.proto == protocol {
return node
}
}
return nil
}
// destroy removes all references to underlying StackNode. Allows garbage collection of node if possible.
func (n *node) destroy() {
*n = node{}
}
func getNodeByProto(nodes []node, protocol uint16) int {
for i := range nodes {
node := &nodes[i]
if node.proto == protocol {
return i
}
}
return -1
}
-31
View File
@@ -1,31 +0,0 @@
package internet
import (
"github.com/soypat/lneto/ntp"
)
var _ StackNode = (*NodeNTPClient)(nil)
type NodeNTPClient struct {
c ntp.Client
}
func (n *NodeNTPClient) Protocol() uint64 {
return 0
}
func (n *NodeNTPClient) LocalPort() uint16 {
return ntp.ClientPort
}
func (n *NodeNTPClient) ConnectionID() *uint64 {
return n.c.ConnectionID()
}
func (n *NodeNTPClient) Demux(carrierData []byte, ntpOffset int) error {
return nil
}
func (n *NodeNTPClient) Encapsulate(carrierData []byte, ntpOffset int) (int, error) {
return 0, nil
}
+14 -1
View File
@@ -208,6 +208,11 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
ifrm4.CRCWriteTCPPseudo(&crc)
tfrm, err := tcp.NewFrame(ifrm4.Payload())
if err == nil {
tfrm.ValidateSize(pc.validator())
if pc.vld.HasError() {
println("BAD TCP")
return dst, pc.vld.ErrPop()
}
tfrm.CRCWrite(&crc)
wantSum := crc.Sum16()
gotSum := tfrm.CRC()
@@ -215,10 +220,15 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
protoErrs = append(protoErrs, &crcError16{protocol: "ipv4+tcp", want: wantSum, got: gotSum})
}
}
} else if proto == lneto.IPProtoUDP || proto == lneto.IPProtoUDPLite {
} else if proto == lneto.IPProtoUDP {
ifrm4.CRCWriteUDPPseudo(&crc)
ufrm, err := udp.NewFrame(ifrm4.Payload())
if err == nil {
ufrm.ValidateSize(pc.validator())
if pc.vld.HasError() {
println("BAD UDP")
return dst, pc.vld.ErrPop()
}
ufrm.CRCWriteIPv4(&crc)
wantSum := crc.Sum16()
gotSum := ufrm.CRC()
@@ -289,6 +299,9 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
return dst, nil
}
// func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
// }
func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 {
return dst, errors.New("UDP must be parsed at byte boundary")
+12
View File
@@ -20,6 +20,18 @@ type StackEthernet struct {
mtu uint16
}
func (ls *StackEthernet) SetGateway6(gw [6]byte) {
ls.gwmac = gw
}
func (ls *StackEthernet) SetHardwareAddr6(mac [6]byte) {
ls.mac = mac
}
func (ls *StackEthernet) HardwareAddr6() [6]byte {
return ls.mac
}
func (ls *StackEthernet) Reset6(mac, gateway [6]byte, mtu int) error {
if mtu > math.MaxUint16 || mtu < 256 {
return errors.New("invalid MTU")
+77 -52
View File
@@ -4,15 +4,14 @@ import (
"errors"
"io"
"log/slog"
"net"
"net/netip"
"slices"
"github.com/soypat/lneto"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/internal"
"github.com/soypat/lneto/ipv4"
"github.com/soypat/lneto/tcp"
"github.com/soypat/lneto/udp"
)
var _ StackNode = (*StackIP)(nil)
@@ -73,40 +72,60 @@ func (sb *StackIP) Demux(carrierData []byte, offset int) error {
}
dst := ifrm.DestinationAddr()
if *dst != sb.ip {
goto DROP
}
{
sb.validator.ResetErr()
ifrm.ValidateExceptCRC(&sb.validator)
if err = sb.validator.ErrPop(); err != nil {
return err
}
gotCRC := ifrm.CRC()
wantCRC := ifrm.CalculateHeaderCRC()
if gotCRC != wantCRC {
sb.error("StackIP:Demux:crc-mismatch", slog.Uint64("want", uint64(wantCRC)), slog.Uint64("got", uint64(gotCRC)))
return errors.New("IPv4 CRC mismatch")
}
off := ifrm.HeaderLength()
totalLen := ifrm.TotalLength()
for i := range sb.handlers {
h := &sb.handlers[i]
proto := ifrm.Protocol()
if h.proto == uint16(proto) {
sb.info("ipDemux", slog.String("ipproto", proto.String()), slog.Int("plen", int(totalLen)))
err = h.demux(frame[:totalLen], off)
if err == net.ErrClosed {
sb.info("ipclose", slog.String("proto", proto.String()))
sb.handlers = slices.Delete(sb.handlers, i, i+1)
}
return err
}
}
return nil // Not meant for us.
}
DROP:
sb.info("iprecv:drop", slog.String("dstaddr", netip.AddrFrom4(*ifrm.DestinationAddr()).String()), slog.String("proto", ifrm.Protocol().String()))
return nil
sb.validator.ResetErr()
ifrm.ValidateExceptCRC(&sb.validator)
if err = sb.validator.ErrPop(); err != nil {
return err
}
gotCRC := ifrm.CRC()
wantCRC := ifrm.CalculateHeaderCRC()
if gotCRC != wantCRC {
sb.error("StackIP:Demux:crc-mismatch", slog.Uint64("want", uint64(wantCRC)), slog.Uint64("got", uint64(gotCRC)))
return errors.New("IPv4 CRC mismatch")
}
off := ifrm.HeaderLength()
totalLen := ifrm.TotalLength()
proto := ifrm.Protocol()
nodeIdx := getNodeByProto(sb.handlers, uint16(proto))
if nodeIdx < 0 {
// Drop packet.
sb.info("iprecv:drop", slog.String("dstaddr", netip.AddrFrom4(*ifrm.DestinationAddr()).String()), slog.String("proto", ifrm.Protocol().String()))
return nil
}
// Incoming CRC Validation of common IP Protocols.
var crc lneto.CRC791
switch proto {
case lneto.IPProtoTCP:
ifrm.CRCWriteTCPPseudo(&crc)
tfrm, err := tcp.NewFrame(ifrm.Payload())
if err != nil {
return err
}
tfrm.CRCWrite(&crc)
if crc.Sum16() != tfrm.CRC() {
return errors.New("TCP CRC mismatch")
}
case lneto.IPProtoUDP:
ifrm.CRCWriteUDPPseudo(&crc)
ufrm, err := udp.NewFrame(ifrm.Payload())
if err != nil {
return err
}
ufrm.CRCWriteIPv4(&crc)
if crc.Sum16() != ufrm.CRC() {
return errors.New("UDP CRC mismatch")
}
}
sb.info("ipDemux", slog.String("ipproto", proto.String()), slog.Int("plen", int(totalLen)))
err = sb.handlers[nodeIdx].demux(frame[:totalLen], off)
if handleNodeError(&sb.handlers, nodeIdx, err) {
sb.info("ipclose", slog.String("proto", proto.String()))
err = nil
}
return err
}
func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error) {
@@ -117,7 +136,7 @@ func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error)
ifrm, _ := ipv4.NewFrame(frame)
const ihl = 5
const headerlen = ihl * 4
ifrm.SetVersionAndIHL(4, 5)
ifrm.SetVersionAndIHL(4, ihl)
ifrm.SetToS(0)
ifrm.SetID(0)
*ifrm.SourceAddr() = sb.ip
@@ -128,25 +147,31 @@ func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error)
if err != nil {
sb.error("StackIP:handle", slog.String("proto", proto.String()), slog.String("err", err.Error()))
continue
} else if n == 0 {
continue
}
if n > 0 {
const dontFrag = 0x4000
totalLen := n + headerlen
ifrm.SetTotalLength(uint16(totalLen))
ifrm.SetFlags(dontFrag)
ifrm.SetTTL(64)
ifrm.SetProtocol(proto)
ifrm.SetCRC(ifrm.CalculateHeaderCRC())
if ifrm.Protocol() == lneto.IPProtoTCP {
var crc lneto.CRC791
ifrm.CRCWriteTCPPseudo(&crc)
tfrm, _ := tcp.NewFrame(ifrm.Payload())
tfrm.CRCWrite(&crc)
tfrm.SetCRC(crc.Sum16())
sb.info("StackIP:send", slog.String("ip", ifrm.String()), slog.String("tcp", tfrm.String()))
}
return totalLen, nil
const dontFrag = 0x4000
totalLen := n + headerlen
ifrm.SetTotalLength(uint16(totalLen))
ifrm.SetFlags(dontFrag)
ifrm.SetTTL(64)
ifrm.SetProtocol(proto)
ifrm.SetCRC(ifrm.CalculateHeaderCRC())
// Calculate CRC for our newly generated packet.
var crc lneto.CRC791
switch proto {
case lneto.IPProtoTCP:
ifrm.CRCWriteTCPPseudo(&crc)
tfrm, _ := tcp.NewFrame(ifrm.Payload())
tfrm.CRCWrite(&crc)
tfrm.SetCRC(crc.Sum16())
case lneto.IPProtoUDP:
ifrm.CRCWriteUDPPseudo(&crc)
ufrm, _ := udp.NewFrame(ifrm.Payload())
ufrm.CRCWriteIPv4(&crc)
ufrm.SetCRC(crc.Sum16())
}
return totalLen, nil
}
return 0, nil
}
+12 -6
View File
@@ -3,26 +3,32 @@ package internet
import (
"encoding/binary"
"io"
"math"
)
type StackPorts struct {
connID uint64
protocol uint64
handlers []node
dstPortOff int
protocol uint16
}
func (ps *StackPorts) Reset(protocol uint64, dstPortOffset int) {
func (ps *StackPorts) Reset(protocol uint64, dstPortOffset int) error {
if protocol > math.MaxUint16 {
return errInvalidProto
}
*ps = StackPorts{
connID: ps.connID + 1,
handlers: ps.handlers[:0],
dstPortOff: dstPortOffset,
protocol: protocol,
protocol: uint16(protocol),
}
return nil
}
func (ps *StackPorts) LocalPort() uint16 { return 0 }
func (ps *StackPorts) Protocol() uint64 { return ps.protocol }
func (ps *StackPorts) Protocol() uint64 { return uint64(ps.protocol) }
func (ps *StackPorts) ConnectionID() *uint64 { return &ps.connID }
@@ -65,13 +71,13 @@ func (ps *StackPorts) Register(h StackNode) error {
proto := h.Protocol()
if port <= 0 {
return errZeroPort
} else if proto != ps.protocol {
} else if proto != uint64(ps.protocol) {
return errInvalidProto
}
ps.handlers = append(ps.handlers, node{
demux: h.Demux,
encapsulate: h.Encapsulate,
port: uint16(port),
port: port,
})
return nil
}
+78
View File
@@ -0,0 +1,78 @@
package internet
import (
"log/slog"
"net"
"github.com/soypat/lneto"
"github.com/soypat/lneto/udp"
)
type StackUDPPort struct {
h node
vld lneto.Validator
rmport uint16
}
func (sudp *StackUDPPort) SetStackNode(node StackNode, rmport uint16) {
sudp.h = nodeFromStackNode(node, node.LocalPort(), node.Protocol())
sudp.rmport = rmport
}
func (sudp *StackUDPPort) Protocol() uint64 { return uint64(lneto.IPProtoUDP) }
func (sudp *StackUDPPort) LocalPort() uint16 { return sudp.h.port }
func (sudp *StackUDPPort) ConnectionID() *uint64 { return sudp.h.connID }
func (sudp *StackUDPPort) Demux(carrierData []byte, frameOffset int) error {
if checkNode(&sudp.h) {
sudp.h.destroy()
return net.ErrClosed
}
ufrm, err := udp.NewFrame(carrierData[frameOffset:])
if err != nil {
return err
}
ufrm.ValidateSize(&sudp.vld)
if sudp.vld.HasError() {
return sudp.vld.ErrPop()
}
dst := ufrm.DestinationPort()
if dst != sudp.h.port {
return nil // Not meant for us.
}
src := ufrm.SourcePort()
if sudp.rmport != 0 && src != sudp.rmport {
return nil // Not from our target remote port.
}
err = sudp.h.demux(ufrm.Payload(), 8)
if err != nil {
if checkNodeErr(&sudp.h, err) {
sudp.h.destroy()
}
slog.Error("stackudp:demux", slog.String("err", err.Error()))
}
return err
}
func (sudp *StackUDPPort) Encapsulate(carrierData []byte, frameOffset int) (int, error) {
if checkNode(&sudp.h) {
sudp.h.destroy()
return 0, net.ErrClosed
}
ufrm, err := udp.NewFrame(carrierData[frameOffset:])
if err != nil {
return 0, err
}
ufrm.SetSourcePort(sudp.h.port)
ufrm.SetDestinationPort(sudp.rmport)
n, err := sudp.h.encapsulate(carrierData[frameOffset:], 8)
if err != nil {
slog.Error("stackudp:demux", slog.String("err", err.Error()))
}
ufrm.SetLength(8 + uint16(n))
// UDP CRC left to IP layer.
return n, err
}