split out node callback between tinygo and go to avoid heap allocs

This commit is contained in:
Patricio Whittingslow
2026-01-13 10:38:21 -03:00
parent f51cdf74b6
commit 2e6e66e691
6 changed files with 78 additions and 21 deletions
+19 -17
View File
@@ -40,12 +40,15 @@ type StackNode interface {
// node is a concrete StackNode as stored in Stacks. Methods are devirtualized for performance benefits, especially on TinyGo.
type node struct {
currConnID uint64
connID *uint64
demux func([]byte, int) error
encapsulate func([]byte, int, int) (int, error)
proto uint16
port uint16
currConnID uint64
connID *uint64
// cbnode has different definitions in tinygo and normal Go compiled programs
// for performance and heap control reasons.
callbacks cbnode
// demux func([]byte, int) error
// encapsulate func([]byte, int, int) (int, error)
proto uint16
port uint16
// remoteAddr will be set on active(outbound) port connections
// that require an ARP to set the remoteAddr beforehand.
remoteAddr []byte
@@ -152,7 +155,7 @@ func (h *handlers) demuxByProto(buf []byte, offset int, proto uint16) (*node, er
if node == nil {
return nil, lneto.ErrPacketDrop
}
err := node.demux(buf, offset)
err := node.callbacks.Demux(buf, offset)
if h.tryHandleError(node, err) {
err = nil
}
@@ -165,7 +168,7 @@ func (h *handlers) demuxByPort(buf []byte, offset int, port uint16) (*node, erro
if node == nil {
return nil, lneto.ErrPacketDrop
}
err := node.demux(buf, offset)
err := node.callbacks.Demux(buf, offset)
if h.tryHandleError(node, err) {
err = nil
node = nil // Node is destroyed in tryHandleError and invalidated.
@@ -181,7 +184,7 @@ func (h *handlers) encapsulateAny(buf []byte, offsetIP, offsetThisFrame int) (_
if node.IsInvalid() {
continue
}
n, err = node.encapsulate(buf, offsetIP, offsetThisFrame)
n, err = node.callbacks.Encapsulate(buf, offsetIP, offsetThisFrame)
if h.tryHandleError(node, err) {
err = nil // CLOSE error handled gracefully by deleting node.
node = nil // Node is destroyed in tryHandleError and invalidated.
@@ -206,7 +209,7 @@ var (
)
func (node *node) IsInvalid() bool {
return node.demux == nil || node.encapsulate == nil || (node.connID != nil && node.currConnID != *node.connID)
return node.callbacks.IsZeroed() || (node.connID != nil && node.currConnID != *node.connID)
}
func checkNodeErr(node *node, err error) (discard bool) {
@@ -223,13 +226,12 @@ func nodeFromStackNode(s StackNode, port uint16, protocol uint64, remoteAddr []b
currConnID = *connIDPtr
}
return node{
currConnID: currConnID,
connID: connIDPtr,
demux: s.Demux,
encapsulate: s.Encapsulate,
proto: uint16(protocol),
port: port,
remoteAddr: remoteAddr, // SHARED MEMORY- used to signal.
currConnID: currConnID,
connID: connIDPtr,
callbacks: makecbnode(s),
proto: uint16(protocol),
port: port,
remoteAddr: remoteAddr, // SHARED MEMORY- used to signal.
}
}
+26
View File
@@ -0,0 +1,26 @@
//go:build !tinygo
package internet
func makecbnode(s StackNode) cbnode {
return cbnode{
_s: s,
}
}
type cbnode struct {
// Do not access outside of handlers/node logic.
_s StackNode
}
func (s cbnode) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (int, error) {
return s._s.Encapsulate(carrierData, offsetToIP, offsetToFrame)
}
func (s cbnode) Demux(carrierData []byte, frameOffset int) error {
return s._s.Demux(carrierData, frameOffset)
}
func (s cbnode) IsZeroed() bool {
return s._s == nil
}
+29
View File
@@ -0,0 +1,29 @@
//go:build tinygo
package internet
func makecbnode(s StackNode) cbnode {
return cbnode{
demux: s.Demux,
encapsulate: s.Encapsulate,
}
}
type cbnode struct {
// Do not access outside of handlers/node logic.
demux func([]byte, int) error
// Do not access outside of handlers/node logic.
encapsulate func([]byte, int, int) (int, error)
}
func (s *cbnode) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (int, error) {
return s.encapsulate(carrierData, offsetToIP, offsetToFrame)
}
func (s *cbnode) Demux(carrierData []byte, frameOffset int) error {
return s.demux(carrierData, frameOffset)
}
func (s cbnode) IsZeroed() bool {
return s.demux == nil || s.encapsulate == nil
}
+1 -1
View File
@@ -136,7 +136,7 @@ func (sb *StackIP) Demux(carrierData []byte, offset int) error {
}
}
sb.handlers.info("ipDemux", slog.String("ipproto", proto.String()), slog.Int("plen", int(totalLen)))
err = node.demux(frame[:totalLen], off)
err = node.callbacks.Demux(frame[:totalLen], off)
if sb.handlers.tryHandleError(node, err) {
sb.handlers.info("ipclose", slog.String("proto", proto.String()))
err = nil
+1 -1
View File
@@ -134,7 +134,7 @@ func (ps *StackPortsMACFiltered) Encapsulate(carrierData []byte, offsetToIP, off
if node.IsInvalid() || (len(node.remoteAddr) > 0 && internal.IsZeroed(node.remoteAddr...)) {
continue
}
n, err = node.encapsulate(carrierData, offsetToIP, offsetToFrame)
n, err = node.callbacks.Encapsulate(carrierData, offsetToIP, offsetToFrame)
if h.tryHandleError(node, err) {
err = nil // CLOSE error handled gracefully by deleting node.
}
+2 -2
View File
@@ -51,7 +51,7 @@ func (sudp *StackUDPPort) Demux(carrierData []byte, frameOffset int) error {
if sudp.rmport != 0 && src != sudp.rmport {
return lneto.ErrPacketDrop // Not from our target remote port.
}
err = sudp.h.demux(carrierData, frameOffset+8)
err = sudp.h.callbacks.Demux(carrierData, frameOffset+8)
if err != nil {
if checkNodeErr(&sudp.h, err) {
sudp.h.destroy()
@@ -79,7 +79,7 @@ func (sudp *StackUDPPort) Encapsulate(carrierData []byte, offsetToIP, offsetToFr
}
}
// Child payload starts 8 bytes after UDP header start.
n, err := sudp.h.encapsulate(carrierData, offsetToIP, offsetToFrame+8)
n, err := sudp.h.callbacks.Encapsulate(carrierData, offsetToIP, offsetToFrame+8)
if n == 0 {
if err != nil {
slog.Error("stackudp:encapsulate", slog.String("err", err.Error()))