Error rewrites (#43)

* 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

* errors: begin standardise errors in lneto

* errors: finish standardization of errors

* fix merge issues

* add more lneto errors to rest of package

* format errors.go
This commit is contained in:
Pat Whittingslow
2026-02-28 20:18:27 +01:00
committed by GitHub
parent 3d0bc93cbe
commit fa5ba918bb
38 changed files with 272 additions and 322 deletions
+1 -5
View File
@@ -93,7 +93,7 @@ func (h *handlers) prepAdd() error {
if h.full() {
h.compact()
if h.full() {
return errNodesFull
return lneto.ErrBufferFull
}
}
return nil
@@ -200,11 +200,7 @@ func (h *handlers) encapsulateAny(buf []byte, offsetIP, offsetThisFrame int) (_
}
var (
errZeroMaxNodesArg = errors.New("zero max nodes arg")
errZeroPort = errors.New("port must be greater than zero")
errInvalidProto = errors.New("invalid protocol")
errProtoRegistered = errors.New("protocol already registered")
errNodesFull = errors.New("no more room for new nodes")
_ = net.ErrClosed
)
+26 -25
View File
@@ -28,6 +28,9 @@ const unknownPayloadProto = "payload?"
var (
ErrFieldByClassNotFound = errors.New("pcap: field by class not found")
ErrLimitExceeded = errors.New("pcap: limit exceeded")
errNotByteAligned = errors.New("must be parsed at byte boundary")
errInvalidFieldIdx = errors.New("invalid field index")
)
type PacketBreakdown struct {
@@ -61,13 +64,11 @@ func (pc *PacketBreakdown) initFrames() []Frame {
}
func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:eth:start")
if dst == nil {
dst = pc.initFrames()
debuglog("pcap:eth:initframes")
}
if bitOffset%8 != 0 {
return dst, errors.New("ethernet must be parsed at byte boundary")
return dst, errNotByteAligned
}
efrm, err := ethernet.NewFrame(pkt[bitOffset/8:])
if err != nil {
@@ -110,7 +111,7 @@ func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset in
func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:arp:start")
if bitOffset%8 != 0 {
return dst, errors.New("ARP must be parsed at byte boundary")
return dst, errNotByteAligned
}
afrm, err := arp.NewFrame(pkt[bitOffset/8:])
if err != nil {
@@ -157,7 +158,7 @@ func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([
func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:ipv6:start")
if bitOffset%8 != 0 {
return dst, errors.New("IPv6 must be parsed at byte boundary")
return dst, errNotByteAligned
}
ifrm6, err := ipv6.NewFrame(pkt[bitOffset/8:])
if err != nil {
@@ -201,7 +202,7 @@ func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) (
func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:ipv4:start")
if bitOffset%8 != 0 {
return dst, errors.New("IPv4 must be parsed at byte boundary")
return dst, errNotByteAligned
}
ifrm4, err := ipv4.NewFrame(pkt[bitOffset/8:])
if err != nil {
@@ -301,7 +302,7 @@ func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt
func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:tcp:start")
if bitOffset%8 != 0 {
return dst, errors.New("TCP must be parsed at byte boundary")
return dst, errNotByteAligned
}
tfrm, err := tcp.NewFrame(pkt[bitOffset/8:])
if err != nil {
@@ -339,7 +340,7 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:udp:start")
if bitOffset%8 != 0 {
return dst, errors.New("UDP must be parsed at byte boundary")
return dst, errNotByteAligned
}
ufrm, err := udp.NewFrame(pkt[bitOffset/8:])
if err != nil {
@@ -372,7 +373,7 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:icmp:start")
if bitOffset%8 != 0 {
return dst, errors.New("ICMPv4 must be parsed at byte boundary")
return dst, errNotByteAligned
}
icmpData := pkt[bitOffset/8:]
ifrm, err := icmpv4.NewFrame(icmpData)
@@ -432,7 +433,7 @@ func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int)
func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 {
return dst, errors.New("DNS must be parsed at byte boundary")
return dst, errNotByteAligned
}
dnsData := pkt[bitOffset/8:]
pc.dmsg.LimitResourceDecoding(20, 20, 20, 20)
@@ -442,9 +443,9 @@ func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([
}
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"))
finfo.Errors = append(finfo.Errors, ErrLimitExceeded)
}
finfo.Fields = append(finfo.Fields, FrameField{
finfo.Fields = append(finfo.Fields[:0], FrameField{
Name: "Data",
FrameBitOffset: 0,
BitLength: int(off) * octet,
@@ -454,7 +455,7 @@ func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([
func (pc *PacketBreakdown) CaptureNTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 {
return dst, errors.New("NTP must be parsed at byte boundary")
return dst, errNotByteAligned
}
ntpData := pkt[bitOffset/8:]
_, err := ntp.NewFrame(ntpData)
@@ -467,7 +468,7 @@ func (pc *PacketBreakdown) CaptureNTP(dst []Frame, pkt []byte, bitOffset int) ([
func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 {
return dst, errors.New("DHCP must be parsed at byte boundary")
return dst, errNotByteAligned
}
dhcpData := pkt[bitOffset/8:]
dfrm, err := dhcpv4.NewFrame(dhcpData)
@@ -477,7 +478,7 @@ func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int)
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.Errors = append(finfo.Errors, lneto.ErrInvalidField)
}
options := dfrm.OptionsPayload()
@@ -491,7 +492,7 @@ func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int)
}
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")
return ErrLimitExceeded
}
// optoff points to start of length and num bytes, skip over them with FrameBitOffset.
field := FrameField{Name: opt.String(), FrameBitOffset: (optoff + 2) * octet, BitLength: len(data) * octet}
@@ -585,7 +586,7 @@ func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) (
debuglog("pcap:http:start")
const httpProtocol = "HTTP"
if bitOffset%8 != 0 {
return dst, errors.New("HTTP must be parsed at byte boundary")
return dst, errNotByteAligned
}
const asResponse = true
const asRequest = false
@@ -668,7 +669,7 @@ func (frm Frame) FieldByClass(c FieldClass) (int, error) {
}
if field.Name == "" { // Prioritize "canonical" fields with no name.
if selected >= 0 && frm.Fields[selected].Name == "" {
return -1, errors.New("multiple class fields with no name")
return -1, lneto.ErrMismatch
}
selected = i
} else if selected >= 0 {
@@ -681,7 +682,7 @@ func (frm Frame) FieldByClass(c FieldClass) (int, error) {
return -1, ErrFieldByClassNotFound
}
if multiple && frm.Fields[selected].Name != "" {
return -1, errors.New("multiple classes found and none have empty name")
return -1, lneto.ErrMismatch
}
return selected, nil
}
@@ -690,7 +691,7 @@ func (frm Frame) FieldByClass(c FieldClass) (int, error) {
func (frm Frame) FieldAsUint(fieldIdx int, pkt []byte) (uint64, error) {
const badUint64 = math.MaxUint64
if fieldIdx < 0 || fieldIdx >= len(frm.Fields) {
return badUint64, errors.New("invalid field index")
return badUint64, errInvalidFieldIdx
}
field := frm.Fields[fieldIdx]
return fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.Flags.IsRightAligned())
@@ -699,7 +700,7 @@ func (frm Frame) FieldAsUint(fieldIdx int, pkt []byte) (uint64, error) {
// AppendField appends the binary on-the-wire representation of the field and aligns the field so it starts at the first bit of appended data.
func (frm Frame) AppendField(dst []byte, fieldIdx int, pkt []byte) ([]byte, error) {
if fieldIdx < 0 || fieldIdx >= len(frm.Fields) {
return dst, errors.New("invalid field index")
return dst, errInvalidFieldIdx
}
field := frm.Fields[fieldIdx]
return appendField(dst, pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.Flags.IsRightAligned())
@@ -709,7 +710,7 @@ func fieldAsUint(pkt []byte, fieldBitStart, bitlen int, rightAligned bool) (uint
const badUint64 = math.MaxUint64
octets := (bitlen + 7) / 8
if octets > 8 {
return badUint64, errors.New("field too long to be represented by uint64")
return badUint64, lneto.ErrUnsupported
}
var buf [8]byte
_, err := appendField(buf[8-octets:8-octets], pkt, fieldBitStart, bitlen, rightAligned)
@@ -725,13 +726,13 @@ func appendField(dst, pkt []byte, fieldBitStart, bitlen int, rightAligned bool)
octets := (bitlen + 7) / 8 // total octets needed to represent field.
octetsStart := fieldBitStart / 8
if octets+octetsStart > len(pkt) {
return dst, errors.New("buffer overflow")
return dst, lneto.ErrShortBuffer
}
firstBitOffset := fieldBitStart % 8
lastOctetExcessBits := fieldBitEnd % 8
if firstBitOffset == 0 {
if rightAligned {
return dst, errors.New("invalid right aligned set for fully aligned field")
return dst, lneto.ErrBug
}
// Optimized path: field starts at byte boundary.
dst = append(dst, pkt[octetsStart:octetsStart+octets]...)
@@ -752,7 +753,7 @@ func appendField(dst, pkt []byte, fieldBitStart, bitlen int, rightAligned bool)
// Right aligned with trailing bits. i.e: IPv6 Traffic Class.
// Field spans an extra byte, so need octets+1 bytes from packet.
if octets+octetsStart+1 > len(pkt) {
return dst, errors.New("buffer overflow")
return dst, lneto.ErrShortBuffer
}
for i := 0; i < octets; i++ {
b := (pkt[octetsStart+i] & mask) << (8 - firstBitOffset)
+3 -3
View File
@@ -3,7 +3,6 @@ package pcap
import (
"encoding/binary"
"encoding/hex"
"errors"
"math"
"net/netip"
"slices"
@@ -13,6 +12,7 @@ import (
_ "time"
"unsafe"
"github.com/soypat/lneto"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/ntp"
"github.com/soypat/lneto/tcp"
@@ -162,7 +162,7 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
// inspired by [time.RFC3339]
const littlerfc3339 = "2006-01-02T15:04:05.9999"
if len(f.buf) != 8 {
return dst, errors.New("only timestamp8 supported")
return dst, lneto.ErrUnsupported
}
ts := ntp.TimestampFromUint64(binary.BigEndian.Uint64(f.buf))
dst = ts.Time().AppendFormat(dst, littlerfc3339)
@@ -223,7 +223,7 @@ func (f *Formatter) fieldAsUint(pkt []byte, fieldBitStart, bitlen int, rightAlig
const badUint64 = math.MaxUint64
octets := (bitlen + 7) / 8
if octets > 8 {
return badUint64, errors.New("field too long to be represented by uint64")
return badUint64, lneto.ErrUnsupported
}
f.uintBuf = [8]byte{}
_, err := appendField(f.uintBuf[8-octets:8-octets], pkt, fieldBitStart, bitlen, rightAligned)
+4 -5
View File
@@ -2,7 +2,6 @@ package internet
import (
"encoding/binary"
"errors"
"io"
"log/slog"
"math"
@@ -76,11 +75,11 @@ func (ls *StackEthernet) Reset6(mac, gateway [6]byte, mtu, maxNodes int) error {
// The connection ID is incremented on each call to invalidate existing connections.
func (ls *StackEthernet) Configure(cfg StackEthernetConfig) error {
if cfg.MTU > (math.MaxUint16-ethernet.MaxOverheadSize) || cfg.MTU < 256 {
return errors.New("invalid MTU")
return lneto.ErrInvalidConfig
} else if cfg.MaxNodes <= 0 {
return errZeroMaxNodesArg
return lneto.ErrInvalidConfig
} else if cfg.AppendCRC32 && cfg.CRC32Update == nil {
return errors.New("need CRC32Update to append ethernet CRC")
return lneto.ErrInvalidConfig
}
ls.handlers.reset("StackEthernet", cfg.MaxNodes)
*ls = StackEthernet{
@@ -107,7 +106,7 @@ func (ls *StackEthernet) Protocol() uint64 { return 1 }
func (ls *StackEthernet) Register(h StackNode) error {
proto := h.Protocol()
if proto > math.MaxUint16 || proto <= 1500 {
return errInvalidProto
return lneto.ErrInvalidConfig
}
return ls.handlers.registerByProto(nodeFromStackNode(h, 0, proto, nil))
}
+5 -6
View File
@@ -1,7 +1,6 @@
package internet
import (
"errors"
"io"
"log/slog"
"net/netip"
@@ -26,7 +25,7 @@ type StackIP struct {
func (sb *StackIP) Reset(addr netip.Addr, maxNodes int) error {
if maxNodes <= 0 {
return errZeroMaxNodesArg
return lneto.ErrInvalidConfig
}
err := sb.SetAddr(addr)
if err != nil {
@@ -44,9 +43,9 @@ func (sb *StackIP) Reset(addr netip.Addr, maxNodes int) error {
func (sb *StackIP) SetAddr(addr netip.Addr) error {
if !addr.IsValid() {
return errors.New("invalid IP")
return lneto.ErrInvalidAddr
} else if !addr.Is4() {
return errors.New("require IPv4")
return lneto.ErrUnsupported
}
sb.ip = addr.As4()
return nil
@@ -200,7 +199,7 @@ func (sb *StackIP) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int
func (sb *StackIP) Register(h StackNode) error {
proto := h.Protocol()
if proto > 255 {
return errInvalidProto
return lneto.ErrInvalidConfig
}
return sb.handlers.registerByPortProto(nodeFromStackNode(h, h.LocalPort(), proto, nil))
}
@@ -208,7 +207,7 @@ func (sb *StackIP) Register(h StackNode) error {
func (sb *StackIP) recvicmp(icmpData []byte) error {
var crc lneto.CRC791
if crc.PayloadSum16(icmpData) != 0 {
return errors.New("ICMP CRC mismatch")
return lneto.ErrBadCRC
}
return nil
}
+7 -8
View File
@@ -2,7 +2,6 @@ package internet
import (
"encoding/binary"
"errors"
"io"
"log/slog"
"math"
@@ -33,9 +32,9 @@ func (ps *StackPorts) ResetTCP(maxNodes int) error {
func (ps *StackPorts) Reset(protocol uint64, dstPortOffset uint16, maxNodes int) error {
if protocol > math.MaxUint16 {
return errInvalidProto
return lneto.ErrInvalidConfig
} else if maxNodes <= 0 {
return errZeroMaxNodesArg
return lneto.ErrInvalidConfig
}
ps.handlers.reset("StackPorts(proto="+strconv.Itoa(int(protocol))+")", maxNodes)
*ps = StackPorts{
@@ -92,9 +91,9 @@ func (ps *StackPorts) Register(h StackNode) error {
port := h.LocalPort()
proto := h.Protocol()
if port <= 0 {
return errZeroPort
return lneto.ErrZeroSource
} else if proto != uint64(ps.protocol) {
return errInvalidProto
return lneto.ErrInvalidConfig
}
return ps.handlers.registerByPortProto(nodeFromStackNode(h, port, proto, nil))
}
@@ -110,11 +109,11 @@ func (mfsp *StackPortsMACFiltered) Register(h StackNode, addr []byte) error {
port := h.LocalPort()
proto := h.Protocol()
if port <= 0 {
return errZeroPort
return lneto.ErrZeroSource
} else if proto != uint64(mfsp.sp.protocol) {
return errInvalidProto
return lneto.ErrInvalidConfig
} else if addr != nil && len(addr) != 6 {
return errors.New("invalid MAC")
return lneto.ErrInvalidAddr
}
return mfsp.sp.handlers.registerByPortProto(nodeFromStackNode(h, port, proto, addr))
}