mirror of
https://github.com/soypat/lneto.git
synced 2026-08-11 02:13:44 +00:00
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:
+3
-1
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user