mirror of
https://github.com/soypat/lneto.git
synced 2026-08-20 06:29:03 +00:00
huge tap/bridge overhaul; udp node; dhcp node; DHCP example
This commit is contained in:
+43
-13
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user