StackNode refactor: add offsetToIP argument to Encapsulate

This commit is contained in:
Patricio Whittingslow
2025-12-19 00:38:28 -03:00
parent eaa36a589a
commit 1554b89a08
22 changed files with 180 additions and 94 deletions
+13 -8
View File
@@ -11,18 +11,21 @@ import (
// StackNode is an abstraction of a packet exchanging protocol controller. This is the building block for all protocols,
// from Ethernet to IP to TCP, practically any protocol can be expressed as a StackNode and function completely.
type StackNode interface {
// Encapsulate writes the stack node's frame into carrierData[frameOffset:]
// Encapsulate writes the stack node's frame into carrierData[offsetToFrame:]
// along with any other frame or payload the stack node encapsulates.
// The returned integer is amount of bytes written such that carrierData[frameOffset:frameOffset+n]
// contains written data. Data inside carrierData[:frameOffset] usually contains data necessary for
// The returned integer is amount of bytes written such that carrierData[offsetToFrame:offsetToFrame+n]
// contains written data. Data inside carrierData[:offsetToFrame] usually contains data necessary for
// a StackNode to correctly emit valid frame data: such is the case for TCP packets which require IP
// frame data for checksum calculation. Thus StackNodes must provide fields in their own frame
// required by sub-stacknodes for correct encapsulation; in the case of IPv4/6 this means including fields
// used in pseudo-header checksum like local IP (see [ipv4.CRCWriteUDPPseudo]).
//
// offsetToIP is the offset to the IP frame, if present, else its value should be -1.
// The relation offsetToIP<=offsetToFrame should always hold.
//
// When [net.ErrClosed] is returned the StackNode should be discarded and any written data passed up normally.
// Errors returned by Encapsulate are "extraordinary" and should not be returned unless the StackNode is receiving invalid carrierData/frameOffset.
Encapsulate(carrierData []byte, frameOffset int) (int, error)
Encapsulate(carrierData []byte, offsetToIP, offsetToFrame 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
@@ -38,10 +41,12 @@ type node struct {
currConnID uint64
connID *uint64
demux func([]byte, int) error
encapsulate func([]byte, int) (int, error)
encapsulate func([]byte, int, int) (int, error)
proto uint16
port uint16
remoteAddr []byte
// remoteAddr will be set on active(outbound) port connections
// that require an ARP to set the remoteAddr beforehand.
remoteAddr []byte
}
type handlers struct {
@@ -166,13 +171,13 @@ func (h *handlers) demuxByPort(buf []byte, offset int, port uint16) (*node, erro
// encapsulateAny finds a node suitable to write and encapsulates the package.
// If no data is sent it returns the last error encountered.
func (h *handlers) encapsulateAny(buf []byte, offset int) (_ *node, n int, err error) {
func (h *handlers) encapsulateAny(buf []byte, offsetIP, offsetThisFrame int) (_ *node, n int, err error) {
for i := range h.nodes {
node := &h.nodes[i]
if node.IsInvalid() {
continue
}
n, err = node.encapsulate(buf, offset)
n, err = node.encapsulate(buf, offsetIP, offsetThisFrame)
if h.tryHandleError(node, err) {
err = nil // CLOSE error handled gracefully by deleting node.
}
+2 -2
View File
@@ -94,7 +94,7 @@ func (listener *NodeTCPListener) TryAccept() (*tcp.Conn, error) {
}
// Encapsulate implements [StackNode].
func (listener *NodeTCPListener) Encapsulate(carrierData []byte, tcpFrameOffset int) (int, error) {
func (listener *NodeTCPListener) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (int, error) {
if listener.isClosed() {
return 0, net.ErrClosed
}
@@ -102,7 +102,7 @@ func (listener *NodeTCPListener) Encapsulate(carrierData []byte, tcpFrameOffset
if conn == nil {
continue
}
n, err := conn.Encapsulate(carrierData, tcpFrameOffset)
n, err := conn.Encapsulate(carrierData, offsetToIP, offsetToFrame)
if err != nil {
err = listener.maintainConn(listener.accepted, i, err)
}
+8 -3
View File
@@ -92,9 +92,9 @@ DROP:
return lneto.ErrPacketDrop
}
func (ls *StackEthernet) Encapsulate(carrierData []byte, frameOffset int) (n int, err error) {
func (ls *StackEthernet) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (n int, err error) {
mtu := ls.mtu
dst := carrierData[frameOffset:]
dst := carrierData[offsetToFrame:]
if len(dst) < int(mtu) {
return 0, io.ErrShortBuffer
}
@@ -104,7 +104,12 @@ func (ls *StackEthernet) Encapsulate(carrierData []byte, frameOffset int) (n int
}
*efrm.DestinationHardwareAddr() = ls.gwmac
var h *node
h, n, err = ls.handlers.encapsulateAny(dst[:mtu], 14)
// 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.
mtuLimit := offsetToFrame + int(mtu)
h, n, err = ls.handlers.encapsulateAny(carrierData[:mtuLimit], offsetToFrame+14, offsetToFrame+14)
if n == 0 {
return n, err
}
+5 -3
View File
@@ -140,8 +140,8 @@ func (sb *StackIP) Demux(carrierData []byte, offset int) error {
return err
}
func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error) {
frame := carrierData[frameOffset:]
func (sb *StackIP) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (int, error) {
frame := carrierData[offsetToFrame:]
if len(frame) < 256 {
return 0, io.ErrShortBuffer
}
@@ -158,7 +158,9 @@ func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error)
ifrm.SetTTL(64)
*ifrm.SourceAddr() = sb.ip
sb.ipID = id
node, n, err := sb.handlers.encapsulateAny(frame, headerlen)
// Children (TCP/UDP) start at offset headerlen (20 bytes after IP header start).
// offsetToIP is 0 relative to this slice (frame), children's frame starts at headerlen.
node, n, err := sb.handlers.encapsulateAny(carrierData, offsetToFrame, offsetToFrame+headerlen)
if n == 0 {
return n, err
}
+3 -3
View File
@@ -46,11 +46,11 @@ func (ps *StackPorts) Protocol() uint64 { return uint64(ps.protocol) }
func (ps *StackPorts) ConnectionID() *uint64 { return &ps.connID }
func (ps *StackPorts) Encapsulate(b []byte, offset int) (n int, err error) {
if int(ps.dstPortOff)+offset+2 > len(b) {
func (ps *StackPorts) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (n int, err error) {
if int(ps.dstPortOff)+offsetToFrame+2 > len(carrierData) {
return 0, io.ErrShortBuffer
}
_, n, err = ps.handlers.encapsulateAny(b, offset)
_, n, err = ps.handlers.encapsulateAny(carrierData, offsetToIP, offsetToFrame)
return n, err
}
+6 -5
View File
@@ -61,24 +61,25 @@ func (sudp *StackUDPPort) Demux(carrierData []byte, frameOffset int) error {
return err
}
func (sudp *StackUDPPort) Encapsulate(carrierData []byte, frameOffset int) (int, error) {
func (sudp *StackUDPPort) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (int, error) {
if sudp.h.IsInvalid() {
sudp.h.destroy()
return 0, net.ErrClosed
}
ufrm, err := udp.NewFrame(carrierData[frameOffset:])
ufrm, err := udp.NewFrame(carrierData[offsetToFrame:])
if err != nil {
return 0, err
}
ufrm.SetSourcePort(sudp.h.port)
ufrm.SetDestinationPort(sudp.rmport)
if len(sudp.raddr) > 0 && frameOffset >= 20 {
err = internal.SetIPAddrs(carrierData, 0, nil, sudp.raddr)
if len(sudp.raddr) > 0 && offsetToIP >= 0 {
err = internal.SetIPAddrs(carrierData[offsetToIP:], 0, nil, sudp.raddr)
if err != nil {
return 0, err
}
}
n, err := sudp.h.encapsulate(carrierData, frameOffset+8)
// Child payload starts 8 bytes after UDP header start.
n, err := sudp.h.encapsulate(carrierData, offsetToIP, offsetToFrame+8)
if n == 0 {
if err != nil {
slog.Error("stackudp:encapsulate", slog.String("err", err.Error()))
+1 -1
View File
@@ -44,7 +44,7 @@ func TestBasicStack2(t *testing.T) {
func expectExchange(t *testing.T, from, to *StackIP, buf []byte) {
t.Helper()
n, err := from.Encapsulate(buf, 0)
n, err := from.Encapsulate(buf, -1, 0)
if err != nil {
t.Error("expectExchange:encapsulate:", err)
} else if n == 0 {