Reduce heap allocations (#42)

* pcap: reuse Frame memory

* slog: reduce heap allocations of addresses; also prevent heap alloc of dhcp options in pcap

* dns: heapless improvement; add StackAsync buffer for more heapless operation; start thinking of errors

* reuse function for reclaiming frames among all protocols for lower memory usage

* remove extraneous code
This commit is contained in:
Pat Whittingslow
2026-02-27 16:38:33 +01:00
committed by GitHub
parent 92b54487df
commit f9748da94f
11 changed files with 198 additions and 130 deletions
+3 -1
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"math"
"math/bits"
"net"
@@ -220,7 +221,8 @@ func (c *Client) Demux(carrierData []byte, frameOffset int) error {
msgOK := msgType == MsgOffer || msgType == MsgAck
if !msgOK {
return fmt.Errorf("invalid DHCP message received or none got=%d", msgType)
internal.LogAttrs(nil, slog.LevelError, "invalid DHCP message", slog.Uint64("type", uint64(msgType)))
return lneto.ErrBug
}
err = c.setOptions(frm)
if err != nil {
+4 -5
View File
@@ -209,8 +209,9 @@ func (m *Message) AppendTo(buf []byte, txid uint16, flags HeaderFlags) (_ []byte
nans := uint16(len(m.Answers))
nauth := uint16(len(m.Authorities))
nadd := uint16(len(m.Additionals))
var hdr [SizeHeader]byte
f, err := NewFrame(hdr[:])
buf = slices.Grow(buf, int(m.Len()))
// Set the buffer directly with header fields.
f, err := NewFrame(buf[len(buf) : len(buf)+SizeHeader])
if err != nil {
return buf, err
}
@@ -220,9 +221,7 @@ func (m *Message) AppendTo(buf []byte, txid uint16, flags HeaderFlags) (_ []byte
f.SetANCount(nans)
f.SetNSCount(nauth)
f.SetARCount(nadd)
buf = slices.Grow(buf, int(m.Len()))
buf = append(buf, hdr[:]...)
buf = buf[:len(buf)+SizeHeader]
for _, q := range m.Questions {
buf, err = q.appendTo(buf)
if err != nil {
+1
View File
@@ -20,6 +20,7 @@ type errGeneric uint8
// Generic errors common to internet functioning.
const (
_ errGeneric = iota // non-initialized err
ErrBug // lneto-bug(use build tag "debugheaplog")
ErrPacketDrop // packet dropped
ErrBadCRC // incorrect checksum
ErrZeroSource // zero source(port/addr)
+19
View File
@@ -46,3 +46,22 @@ func SliceReuse[T any](buf *[]T, n int) {
*buf = (*buf)[:0]
}
}
// SliceReclaim extends the slice length by one and returns a pointer to
// the new last element. The returned element is not zeroed, so callers
// can reuse any existing allocations it may hold from prior use.
//
// Beware: as the name implies SliceReclaim reuses previously held memory
// so it is up to caller to ensure the reclaimed data is coherent by zeroing out
// the needed fields and reusing the previously held slices or pointers responsibly.
func SliceReclaim[T any](ptr *[]T) *T {
b := *ptr
n := len(b)
if n == cap(b) {
var z T
*ptr = append(b, z)
} else {
*ptr = b[:n+1]
}
return &(*ptr)[n]
}
+22
View File
@@ -0,0 +1,22 @@
package internal
import (
"encoding/binary"
"log/slog"
)
// SlogAddr4 returns a slog.Attr for a 4-byte IPv4 address
// packed into a uint64 without allocating a string.
func SlogAddr4(key string, addr *[4]byte) slog.Attr {
u64Addr := uint64(binary.BigEndian.Uint32(addr[:]))
return slog.Uint64(key, u64Addr)
}
// SlogAddr6 returns a slog.Attr for a 6-byte hardware (MAC) address
// packed into a uint64 without allocating a string.
func SlogAddr6(key string, addr *[6]byte) slog.Attr {
var buf [8]byte
copy(buf[2:], addr[:])
u64Addr := binary.BigEndian.Uint64(buf[:])
return slog.Uint64(key, u64Addr)
}
+90 -106
View File
@@ -14,6 +14,7 @@ import (
"github.com/soypat/lneto/dns"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/http/httpraw"
"github.com/soypat/lneto/internal"
"github.com/soypat/lneto/ipv4"
"github.com/soypat/lneto/ipv4/icmpv4"
"github.com/soypat/lneto/ipv6"
@@ -43,7 +44,31 @@ type PacketBreakdown struct {
SubfieldLimit int
}
// initFrames pre-allocates a []Frame with per-position Fields capacity
// sized for the protocol layers at each position. This avoids heap
// allocations on the first packet when using SliceReclaim on subsequent calls.
//
// Pre-allocated capacities per position:
//
// 0: Ethernet (3 base + VLAN tag = 4)
// 1: L3 max(IPv4=12, ARP=9, IPv6=8) = 12
// 2: L4 max(TCP=10, ICMP=8, UDP=4) = 10
// 3: App max(DHCP=15, NTP=13, HTTP=2, DNS=1) = 16
// 4-5: overflow/remaining = 2
func (pc *PacketBreakdown) initFrames() []Frame {
const nframes = 6
var fieldCaps = [nframes]int{4, 12, 10, 16, 2, 2}
frames := make([]Frame, nframes)
for i := range frames {
frames[i].Fields = make([]FrameField, 0, fieldCaps[i])
}
return frames[:0]
}
func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if dst == nil {
dst = pc.initFrames()
}
if bitOffset%8 != 0 {
return dst, errors.New("ethernet must be parsed at byte boundary")
}
@@ -56,23 +81,17 @@ func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset in
return dst, pc.validator().ErrPop()
}
finfo := Frame{
Protocol: ProtoEthernet,
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseEthernetFields[:]...)
finfo := reclaimFrame(&dst, ProtoEthernet, bitOffset, baseEthernetFields[:])
etype := efrm.EtherTypeOrSize()
end := 14*octet + bitOffset
if etype.IsSize() {
finfo.Fields[len(finfo.Fields)-1].Class = FieldClassSize
dst = append(dst, finfo)
dst = append(dst, remainingFrameInfo("Ethernet payload", FieldClassPayload, end, octet*len(pkt)))
reclaimRemainingFrame(&dst, "Ethernet payload", FieldClassPayload, end, octet*len(pkt))
return dst, nil
}
dst = append(dst, finfo)
if efrm.IsVLAN() {
finfo.Fields = append(finfo.Fields, FrameField{Name: "VLAN Tag", Class: FieldClassType, FrameBitOffset: end, BitLength: 2 * octet})
dst = append(dst, remainingFrameInfo("Ethernet VLAN", FieldClassPayload, end+2*octet, octet*len(pkt)))
reclaimRemainingFrame(&dst, "Ethernet VLAN", FieldClassPayload, end+2*octet, octet*len(pkt))
return dst, nil
}
switch etype {
@@ -83,7 +102,7 @@ func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset in
case ethernet.TypeIPv6:
dst, err = pc.CaptureIPv6(dst, pkt, end)
default:
dst = append(dst, remainingFrameInfo(etype, FieldClassPayload, end, octet*len(pkt)))
reclaimRemainingFrame(&dst, etype, FieldClassPayload, end, octet*len(pkt))
}
return dst, err
}
@@ -101,13 +120,8 @@ func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([
return dst, pc.validator().ErrPop()
}
finfo := Frame{
Protocol: ethernet.TypeARP,
PacketBitOffset: bitOffset,
}
finfo := reclaimFrame(&dst, ethernet.TypeARP, bitOffset, baseARPFields[:])
const varstart = 8 * octet
finfo.Fields = append(finfo.Fields, baseARPFields[:]...)
_, hlen := afrm.Hardware()
_, plen := afrm.Protocol()
finfo.Fields = append(finfo.Fields,
@@ -136,7 +150,6 @@ func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([
BitLength: int(plen) * octet,
},
)
dst = append(dst, finfo)
return dst, nil
}
@@ -152,12 +165,7 @@ func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) (
if pc.validator().HasError() {
return dst, pc.validator().ErrPop()
}
finfo := Frame{
Protocol: ethernet.TypeIPv6,
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseIPv6Fields[:]...)
dst = append(dst, finfo)
reclaimFrame(&dst, ethernet.TypeIPv6, bitOffset, baseIPv6Fields[:])
proto := ifrm6.NextHeader()
end := bitOffset + 40*octet
var protoErrs []error
@@ -201,11 +209,7 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
}
// limit packet to the actual IPv4 frame size
pkt = pkt[:bitOffset/8+int(ifrm4.TotalLength())]
finfo := Frame{
Protocol: ethernet.TypeIPv4,
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseIPv4Fields[:]...)
finfo := reclaimFrame(&dst, ethernet.TypeIPv4, bitOffset, baseIPv4Fields[:])
options := ifrm4.Options()
if len(options) > 0 {
finfo.Fields = append(finfo.Fields, FrameField{
@@ -217,7 +221,6 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
if ifrm4.CalculateHeaderCRC() != 0 {
finfo.Errors = append(finfo.Errors, lneto.ErrBadCRC)
}
dst = append(dst, finfo)
proto := ifrm4.Protocol()
end := bitOffset + octet*ifrm4.HeaderLength()
var protoErrs []error
@@ -277,7 +280,7 @@ func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt
case lneto.IPProtoICMP:
dst, err = pc.CaptureICMPv4(dst, pkt, bitOffset)
default:
dst = append(dst, remainingFrameInfo(proto, 0, bitOffset, octet*len(pkt)))
reclaimRemainingFrame(&dst, proto, 0, bitOffset, octet*len(pkt))
}
if len(ipProtoErrs) > 0 && len(dst) > nextFrame {
dst[nextFrame].Errors = append(dst[nextFrame].Errors, ipProtoErrs...)
@@ -298,11 +301,7 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
return dst, pc.validator().ErrPop()
}
end := bitOffset + octet*tfrm.HeaderLength()
finfo := Frame{
Protocol: lneto.IPProtoTCP,
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseTCPFields[:]...)
finfo := reclaimFrame(&dst, lneto.IPProtoTCP, bitOffset, baseTCPFields[:])
options := tfrm.Options()
if len(options) > 0 {
finfo.Fields = append(finfo.Fields, FrameField{
@@ -311,20 +310,16 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
BitLength: octet * len(options),
})
}
dst = append(dst, finfo)
payload := tfrm.Payload()
if len(payload) > 0 {
dst, err = pc.CaptureHTTP(dst, pkt, end)
if err != nil {
dst = append(dst, remainingFrameInfo(unknownPayloadProto, FieldClassPayload, end, octet*len(pkt)))
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
}
}
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")
@@ -337,12 +332,7 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
if pc.validator().HasError() {
return dst, pc.validator().ErrPop()
}
finfo := Frame{
Protocol: lneto.IPProtoUDP,
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseUDPFields[:]...)
dst = append(dst, finfo)
reclaimFrame(&dst, lneto.IPProtoUDP, bitOffset, baseUDPFields[:])
end := bitOffset + 8*octet
payload := ufrm.Payload()
dstport := ufrm.DestinationPort()
@@ -355,7 +345,7 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
dst, err = pc.CaptureNTP(dst, pkt, end)
}
if err != nil {
dst = append(dst, remainingFrameInfo(unknownPayloadProto, FieldClassPayload, end, octet*len(pkt)))
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
}
return dst, nil
}
@@ -370,11 +360,7 @@ func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int)
return dst, err
}
finfo := Frame{
Protocol: lneto.IPProtoICMP,
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseICMPv4Fields[:]...)
finfo := reclaimFrame(&dst, lneto.IPProtoICMP, bitOffset, baseICMPv4Fields[:])
// Add type-specific fields.
switch ifrm.Type() {
@@ -421,13 +407,12 @@ func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int)
})
}
}
dst = append(dst, finfo)
return dst, nil
}
func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 {
return nil, errors.New("DNS must be parsed at byte boundary")
return dst, errors.New("DNS must be parsed at byte boundary")
}
dnsData := pkt[bitOffset/8:]
pc.dmsg.LimitResourceDecoding(20, 20, 20, 20)
@@ -435,10 +420,7 @@ func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([
if err != nil && !incomplete {
return dst, err
}
finfo := Frame{
Protocol: "DNS",
PacketBitOffset: bitOffset,
}
finfo := reclaimFrame(&dst, "DNS", bitOffset, nil)
if incomplete {
finfo.Errors = append(finfo.Errors, errors.New("pcap: could not parse all DNS resources; add higher limit"))
}
@@ -447,52 +429,46 @@ func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([
FrameBitOffset: 0,
BitLength: int(off) * octet,
})
dst = append(dst, finfo)
return dst, nil
}
func (pc *PacketBreakdown) CaptureNTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 {
return nil, errors.New("NTP must be parsed at byte boundary")
return dst, errors.New("NTP must be parsed at byte boundary")
}
ntpData := pkt[bitOffset/8:]
_, err := ntp.NewFrame(ntpData)
if err != nil {
return dst, err
}
finfo := Frame{
Protocol: "NTP",
PacketBitOffset: bitOffset,
}
finfo.Fields = append(finfo.Fields, baseNTPFields[:]...)
dst = append(dst, finfo)
reclaimFrame(&dst, "NTP", bitOffset, baseNTPFields[:])
return dst, nil
}
func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 {
return nil, errors.New("DHCP must be parsed at byte boundary")
return dst, errors.New("DHCP must be parsed at byte boundary")
}
dhcpData := pkt[bitOffset/8:]
dfrm, err := dhcpv4.NewFrame(dhcpData)
if err != nil {
return nil, err
}
finfo := Frame{
Protocol: "DHCPv4",
PacketBitOffset: bitOffset,
return dst, err
}
finfo := reclaimFrame(&dst, "DHCPv4", bitOffset, baseDHCPv4Fields[:])
magic := dfrm.MagicCookie()
if magic != dhcpv4.MagicCookie {
finfo.Errors = append(finfo.Errors, errors.New("incorrect DHCPv4 magic cookie"))
}
finfo.Fields = append(finfo.Fields, baseDHCPv4Fields[:]...)
options := dfrm.OptionsPayload()
if len(options) > 0 && pc.SubfieldLimit > 0 {
var optfield FrameField
optfield.Class = FieldClassOptions
optfield.Name = "options"
// Reclaim FrameField from Fields backing array to reuse its SubFields backing array.
optfield := internal.SliceReclaim(&finfo.Fields)
*optfield = FrameField{ // Ensure consistent zeroing of memory, but keep slice reference for reuse.
Class: FieldClassOptions,
SubFields: optfield.SubFields[:0],
Name: "options",
}
err = dfrm.ForEachOption(func(optoff int, opt dhcpv4.OptNum, data []byte) error {
if len(optfield.SubFields) >= pc.SubfieldLimit {
return errors.New("option cap limit surpassed for DHCP")
@@ -544,12 +520,11 @@ func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int)
optfield.SubFields = append(optfield.SubFields, field)
return nil
})
finfo.Fields = append(finfo.Fields, optfield)
// optfield already in finfo.Fields via SliceReclaim, no append needed.
if err != nil {
finfo.Errors = append(finfo.Errors, err)
}
}
dst = append(dst, finfo)
return dst, nil
}
@@ -589,7 +564,7 @@ func httpBodyClass(contentType, body []byte) FieldClass {
func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
const httpProtocol = "HTTP"
if bitOffset%8 != 0 {
return nil, errors.New("HTTP must be parsed at byte boundary")
return dst, errors.New("HTTP must be parsed at byte boundary")
}
const asResponse = true
const asRequest = false
@@ -606,25 +581,22 @@ func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) (
hdrLen := pc.hdr.BufferParsed()
body, _ := pc.hdr.Body()
bodyClass := httpBodyClass(pc.hdr.Get("Content-Type"), body)
dst = append(dst, Frame{
Protocol: httpProtocol,
PacketBitOffset: bitOffset,
Fields: []FrameField{
{
Name: "HTTP Header",
Class: FieldClassText,
FrameBitOffset: 0,
BitLength: hdrLen * octet,
},
{
Name: "HTTP Body",
Class: bodyClass,
FrameBitOffset: hdrLen * octet,
BitLength: len(body) * octet,
},
finfo := reclaimFrame(&dst, httpProtocol, bitOffset, nil)
finfo.Fields = append(finfo.Fields,
FrameField{
Name: "HTTP Header",
Class: FieldClassText,
FrameBitOffset: 0,
BitLength: hdrLen * octet,
},
})
return dst, err
FrameField{
Name: "HTTP Body",
Class: bodyClass,
FrameBitOffset: hdrLen * octet,
BitLength: len(body) * octet,
},
)
return dst, nil
}
func (pc *PacketBreakdown) validator() *lneto.Validator {
@@ -653,9 +625,9 @@ func (ff Flags) IsLegacy() bool { return ff&FlagLegacy != 0 }
func (ff Flags) IsRightAligned() bool { return ff&FlagRightAligned != 0 }
type Frame struct {
PacketBitOffset int
Protocol any
Fields []FrameField
PacketBitOffset int
Errors []error
}
@@ -1331,14 +1303,26 @@ var baseNTPFields = [...]FrameField{
},
}
func remainingFrameInfo(proto any, class FieldClass, pktBitOffset, pktBitLen int) Frame {
return Frame{
// reclaimFrame extends dst via [internal.SliceReclaim], resets the reclaimed Frame
// with the given protocol and bit offset while preserving Fields and Errors
// backing arrays, and appends baseFields. Returns the Frame for further modification.
func reclaimFrame(dst *[]Frame, proto any, bitOffset int, baseFields []FrameField) *Frame {
finfo := internal.SliceReclaim(dst)
*finfo = Frame{
PacketBitOffset: bitOffset,
Protocol: proto,
PacketBitOffset: pktBitOffset,
Fields: []FrameField{
{
Class: class,
BitLength: pktBitLen - pktBitOffset,
}},
Fields: append(finfo.Fields[:0], baseFields...),
Errors: finfo.Errors[:0],
}
return finfo
}
// reclaimRemainingFrame extends dst via SliceReclaim and populates the reclaimed
// Frame as a single-field "remaining payload" frame, reusing the old Fields backing array.
func reclaimRemainingFrame(dst *[]Frame, proto any, class FieldClass, pktBitOffset, pktBitLen int) {
finfo := reclaimFrame(dst, proto, pktBitOffset, nil)
finfo.Fields = append(finfo.Fields, FrameField{
Class: class,
BitLength: pktBitLen - pktBitOffset,
})
}
+43 -5
View File
@@ -5,6 +5,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"math"
"net/netip"
"slices"
"strconv"
@@ -31,6 +32,7 @@ type Formatter struct {
DisableLegacyFilter bool
mubuf sync.Mutex
buf []byte
uintBuf [8]byte // scratch buffer for fieldAsUint to avoid TinyGo heap escape.
}
// FormatFrames appends the formatted frame data to the destination buffer according the Formatter state.
@@ -49,14 +51,17 @@ func (f *Formatter) FormatFrames(dst []byte, frms []Frame, pkt []byte) (_ []byte
return dst, nil
}
// FormatFrame
// FormatFrame formats a single frame's protocol, fields, and errors into dst.
func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, err error) {
sep := f.fieldSep()
bitlen := frm.LenBits()
dst = appendProtocol(dst, frm.Protocol)
if bitlen%8 == 0 {
dst = fmt.Appendf(dst, "%s len=%d", frm.Protocol, bitlen/8)
dst = append(dst, " len="...)
dst = strconv.AppendInt(dst, int64(bitlen/8), 10)
} else {
dst = fmt.Appendf(dst, "%s bitlen=%d", frm.Protocol, bitlen)
dst = append(dst, " bitlen="...)
dst = strconv.AppendInt(dst, int64(bitlen), 10)
}
for ifield := range frm.Fields {
@@ -68,7 +73,7 @@ func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, er
if field.Class == FieldClassFlags && frm.Protocol == lneto.IPProtoTCP {
// TCP flags pretty print special case.
dst = append(dst, "flags="...)
v, err := fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.Flags.IsRightAligned())
v, err := f.fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.Flags.IsRightAligned())
if err != nil {
return dst, err
}
@@ -158,7 +163,7 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
case FieldClassDst, FieldClassSrc, FieldClassSize, FieldClassAddress, FieldClassOperation:
// IP, MAC addresses and ports.
if field.BitLength <= 16 {
v, err := fieldAsUint(pkt, fieldBitStart, field.BitLength, field.Flags.IsRightAligned())
v, err := f.fieldAsUint(pkt, fieldBitStart, field.BitLength, field.Flags.IsRightAligned())
if err != nil {
return dst, err
}
@@ -198,3 +203,36 @@ func (f *Formatter) subfieldSep() string {
}
return sep
}
// fieldAsUint evaluates a packet field as a uint64 using the Formatter's
// scratch buffer to avoid a TinyGo heap escape from a local [8]byte.
func (f *Formatter) fieldAsUint(pkt []byte, fieldBitStart, bitlen int, rightAligned bool) (uint64, error) {
const badUint64 = math.MaxUint64
octets := (bitlen + 7) / 8
if octets > 8 {
return badUint64, errors.New("field too long to be represented by uint64")
}
f.uintBuf = [8]byte{}
_, err := appendField(f.uintBuf[8-octets:8-octets], pkt, fieldBitStart, bitlen, rightAligned)
if err != nil {
return badUint64, err
}
return binary.BigEndian.Uint64(f.uintBuf[:]), nil
}
// appendProtocol appends the string representation of a Frame.Protocol value
// to dst without going through fmt.Appendf reflect machinery.
func appendProtocol(dst []byte, p any) []byte {
switch p := p.(type) {
case proto:
return append(dst, string(p)...)
case string:
return append(dst, p...)
case ethernet.Type:
return append(dst, p.String()...)
case lneto.IPProto:
return append(dst, p.String()...)
default:
return fmt.Appendf(dst, "%v", p)
}
}
+2 -2
View File
@@ -6,10 +6,10 @@ import (
"io"
"log/slog"
"math"
"net"
"github.com/soypat/lneto"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/internal"
)
// StackEthernetConfig contains configuration parameters for [StackEthernet].
@@ -132,7 +132,7 @@ func (ls *StackEthernet) Demux(carrierData []byte, frameOffset int) (err error)
return err
}
DROP:
ls.handlers.info("LinkStack:drop-packet", slog.String("dsthw", net.HardwareAddr(dstaddr[:]).String()), slog.String("ethertype", efrm.EtherTypeOrSize().String()))
ls.handlers.info("LinkStack:drop-packet", internal.SlogAddr6("dsthw", dstaddr), slog.String("ethertype", efrm.EtherTypeOrSize().String()))
return lneto.ErrPacketDrop
}
+1 -1
View File
@@ -104,7 +104,7 @@ func (sb *StackIP) Demux(carrierData []byte, offset int) error {
// nodeIdx := getNodeByProto(sb.handlers, uint16(proto))
if node == nil {
// Drop packet.
sb.handlers.info("ip:demux.drop", slog.String("dstaddr", netip.AddrFrom4(*ifrm.DestinationAddr()).String()), slog.String("proto", ifrm.Protocol().String()))
sb.handlers.info("ip:demux.drop", internal.SlogAddr4("dstaddr", ifrm.DestinationAddr()), slog.String("proto", ifrm.Protocol().String()))
return lneto.ErrPacketDrop
}
// Incoming CRC Validation of common IP Protocols.
+7 -6
View File
@@ -194,15 +194,16 @@ func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ErrPacketDrop-1]
_ = x[ErrBadCRC-2]
_ = x[ErrZeroSource-3]
_ = x[ErrZeroDestination-4]
_ = x[ErrBug-1]
_ = x[ErrPacketDrop-2]
_ = x[ErrBadCRC-3]
_ = x[ErrZeroSource-4]
_ = x[ErrZeroDestination-5]
}
const _errGeneric_name = "packet droppedincorrect checksumzero source(port/addr)zero destination(port/addr)"
const _errGeneric_name = "lneto-bug(use build tag \"debugheaplog\")packet droppedincorrect checksumzero source(port/addr)zero destination(port/addr)"
var _errGeneric_index = [...]uint8{0, 14, 32, 54, 81}
var _errGeneric_index = [...]uint8{0, 39, 53, 71, 93, 120}
func (i errGeneric) String() string {
i -= 1
+6 -4
View File
@@ -48,6 +48,8 @@ type StackAsync struct {
prng uint32
addrBuf [6]byte // Temporary buffer for As4()/HardwareAddr6() results to avoid heap escapes.
totalsent uint64
totalrecv uint64
}
@@ -353,8 +355,8 @@ func (s *StackAsync) StartLookupIP(host string) error {
if err != nil {
return err
}
dns4 := s.dnssv.As4()
s.dnsUDP.SetStackNode(&s.dns, dns4[:], dns.ServerPort)
*(*[4]byte)(s.addrBuf[:4]) = s.dnssv.As4()
s.dnsUDP.SetStackNode(&s.dns, s.addrBuf[:4], dns.ServerPort)
err = s.udps.Register(&s.dnsUDP)
return err
}
@@ -417,8 +419,8 @@ func (s *StackAsync) StartNTP(addr netip.Addr) error {
defer s.mu.Unlock()
s.ntp.Reset(s.sysprec, time.Now)
addr4 := addr.As4()
s.ntpUDP.SetStackNode(&s.ntp, addr4[:], ntp.ServerPort)
*(*[4]byte)(s.addrBuf[:4]) = addr.As4()
s.ntpUDP.SetStackNode(&s.ntp, s.addrBuf[:4], ntp.ServerPort)
err := s.udps.Register(&s.ntpUDP)
return err
}