From fa5ba918bbabe659b1af56c14eb914b7078ca168 Mon Sep 17 00:00:00 2001 From: Pat Whittingslow Date: Sat, 28 Feb 2026 20:18:27 +0100 Subject: [PATCH] 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 --- arp/definitions.go | 18 ++++++++++---- arp/frame.go | 3 +-- arp/handler.go | 27 ++++++++++---------- dhcpv4/client.go | 24 +++++++++--------- dhcpv4/definitions.go | 7 +++--- dhcpv4/frame.go | 18 ++++---------- dhcpv4/server.go | 2 ++ dns/client.go | 9 ++++--- dns/definitions.go | 50 ++++++++++++++++++++----------------- errors.go | 37 ++++++++++++--------------- ethernet/frame.go | 12 +++------ internal/ip.go | 14 ++++------- internal/ring.go | 15 +++++++---- internet/definitions.go | 6 +---- internet/pcap/capture.go | 51 +++++++++++++++++++------------------- internet/pcap/format.go | 6 ++--- internet/stack-ethernet.go | 9 +++---- internet/stack-ip.go | 11 ++++---- internet/stack-ports.go | 15 ++++++----- ipv4/frame.go | 21 +++++----------- ipv4/icmpv4/icmpv4.go | 9 +++---- ipv6/frame.go | 9 ++----- ntp/client.go | 5 ++-- ntp/ntp.go | 11 ++++---- phy/phy.go | 12 ++++----- stringers.go | 14 +++++++++-- tcp/conn.go | 16 +++++------- tcp/definitions.go | 24 +++++++++--------- tcp/frame.go | 18 ++++---------- tcp/handler.go | 16 ++++-------- tcp/listener.go | 11 ++++---- tcp/options.go | 16 ++++++------ tcp/syncookie.go | 9 +++---- tcp/txqueue.go | 25 +++++++------------ udp/frame.go | 12 +++------ x/xnet/stack-async.go | 18 ++++++-------- x/xnet/stack-berkeley.go | 10 ++++---- x/xnet/tcppool.go | 4 +-- 38 files changed, 272 insertions(+), 322 deletions(-) diff --git a/arp/definitions.go b/arp/definitions.go index 62b81f7..4cd02ce 100644 --- a/arp/definitions.go +++ b/arp/definitions.go @@ -1,6 +1,10 @@ package arp -import "errors" +import ( + "errors" + + "github.com/soypat/lneto" +) //go:generate stringer -type=Operation -linecomment -output stringers.go . @@ -11,10 +15,14 @@ const ( ) var ( - errARPBufferFull = errors.New("ARP client need handling:too many ops pending") - errShortARP = errors.New("packet too short to be ARP") - errARPUnsupported = errors.New("ARP not supported") - errLargeSizes = errors.New("size of ARP protocol+hardware is unusually large") + errQueryPending = errors.New("arp: query pending") + errQueryNotFound = errors.New("arp: query not found") + + // errGeneric aliases for common ARP errors. + errARPBufferFull = lneto.ErrBufferFull + errShortARP = lneto.ErrShortBuffer + errARPUnsupported = lneto.ErrUnsupported + errLargeSizes = lneto.ErrPacketDrop ) // Operation represents the type of ARP packet, either request or reply/response. diff --git a/arp/frame.go b/arp/frame.go index d7f2aa3..5aec03c 100644 --- a/arp/frame.go +++ b/arp/frame.go @@ -2,7 +2,6 @@ package arp import ( "encoding/binary" - "errors" "fmt" "net" "net/netip" @@ -17,7 +16,7 @@ import ( // with payload/options of frames to avoid panics. func NewFrame(buf []byte) (Frame, error) { if len(buf) < sizeHeaderv4 { - return Frame{buf: nil}, errors.New("ARP packet too short") + return Frame{buf: nil}, lneto.ErrShortBuffer } return Frame{buf: buf}, nil } diff --git a/arp/handler.go b/arp/handler.go index 2547aba..57907f4 100644 --- a/arp/handler.go +++ b/arp/handler.go @@ -1,7 +1,6 @@ package arp import ( - "errors" "log/slog" "github.com/soypat/lneto" @@ -36,7 +35,7 @@ func (h *Handler) ConnectionID() *uint64 { return &h.connID } func (h *Handler) UpdateProtoAddr(protoAddr []byte) error { if len(protoAddr) != len(h.ourProtoAddr) { - return errors.New("mismatch ARP proto size") + return lneto.ErrMismatchLen } copy(h.ourProtoAddr, protoAddr) return nil @@ -45,9 +44,9 @@ func (h *Handler) UpdateProtoAddr(protoAddr []byte) error { func (h *Handler) Reset(cfg HandlerConfig) error { if len(cfg.HardwareAddr) == 0 || len(cfg.HardwareAddr) > 255 || len(cfg.ProtocolAddr) == 0 || len(cfg.ProtocolAddr) > 255 { - return errors.New("invalid Handler address config") + return lneto.ErrInvalidConfig } else if cfg.MaxQueries <= 0 || cfg.MaxPending <= 0 { - return errors.New("invalid Handler query or pending config") + return lneto.ErrInvalidConfig } *h = Handler{ connID: h.connID + 1, @@ -102,16 +101,16 @@ func (h *Handler) QueryResult(protoAddr []byte) (hwAddr []byte, err error) { for i := range h.queries { if internal.BytesEqual(protoAddr, h.queries[i].protoaddr) { if !h.queries[i].querysent { - return nil, errors.New("query not yet sent") + return nil, errQueryPending } mac := h.queries[i].response() if mac == nil { - return nil, errors.New("no response yet") + return nil, errQueryPending } return mac, nil } } - return nil, errors.New("query not exist or dropped") + return nil, errQueryNotFound } func (h *Handler) DiscardQuery(protoAddr []byte) error { @@ -122,7 +121,7 @@ func (h *Handler) DiscardQuery(protoAddr []byte) error { return nil } } - return errors.New("query not found") + return errQueryNotFound } func (h *Handler) compactQueries() { @@ -150,15 +149,15 @@ func (h *Handler) StartQuery(dstHWAddr, proto []byte) error { if len(h.queries) == cap(h.queries) { h.compactQueries() if len(h.queries) == cap(h.queries) { - return errors.New("too many ongoing queries") + return lneto.ErrBufferFull } } if len(proto) != len(h.ourProtoAddr) { - return errors.New("bad protocol address length") + return lneto.ErrMismatchLen } else if dstHWAddr != nil && len(dstHWAddr) != len(h.ourHWAddr) { - return errors.New("mismatch hardware size") + return lneto.ErrMismatchLen } else if dstHWAddr != nil && !internal.IsZeroed(dstHWAddr...) { - return errors.New("write-to buffer must be zeroed out") + return lneto.ErrInvalidConfig } h.queries = h.queries[:len(h.queries)+1] q := &h.queries[len(h.queries)-1] @@ -230,11 +229,11 @@ func (h *Handler) Demux(ethFrame []byte, frameOffset int) error { } htype, hlen := afrm.Hardware() if htype != h.htype || int(hlen) != len(h.ourHWAddr) { - return errors.New("bad ARP hardware") + return lneto.ErrMismatch } protoType, protoLen := afrm.Protocol() if protoType != h.protoType || int(protoLen) != len(h.ourProtoAddr) { - return errors.New("bad ARP proto") + return lneto.ErrMismatch } switch afrm.Operation() { case OpRequest: diff --git a/dhcpv4/client.go b/dhcpv4/client.go index 27c6869..b134cd6 100644 --- a/dhcpv4/client.go +++ b/dhcpv4/client.go @@ -2,8 +2,6 @@ package dhcpv4 import ( "encoding/binary" - "errors" - "fmt" "io" "log/slog" "math" @@ -80,13 +78,13 @@ func (c *Client) Reset() { func (c *Client) BeginRequest(xid uint32, cfg RequestConfig) error { if len(cfg.Hostname) > 36 { - return errors.New("requested hostname too long") + return lneto.ErrInvalidConfig } else if c.state != StateInit && c.state != 0 { - return errors.New("dhcp client must be closed/Init before new request") + return lneto.ErrInvalidConfig } else if xid == 0 { - return errors.New("zero xid") + return lneto.ErrInvalidConfig } else if len(cfg.ClientID) > 32 { - return errors.New("client ID too long") + return lneto.ErrInvalidConfig } c.reset(xid) c.state = StateInit @@ -143,7 +141,7 @@ func (c *Client) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) } opts := frm.OptionsPayload() if len(opts) < 255 { - return 0, errors.New("too short packet for options") + return 0, lneto.ErrShortBuffer } var nextState ClientState @@ -181,7 +179,8 @@ func (c *Client) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) nextState = StateRequesting default: - return 0, errors.New("unhandled state" + c.state.String()) + internal.LogAttrs(nil, slog.LevelError, "dhcpv4:unhandled-state", slog.String("state", c.state.String())) + return 0, lneto.ErrBug } n, _ := EncodeOption(opts[numOpts:], OptClientIdentifier, c.clientID...) numOpts += n @@ -210,13 +209,13 @@ func (c *Client) Demux(carrierData []byte, frameOffset int) error { if err != nil { return err } else if frm.XID() != c.currentXID { - return errors.New("dhcpv4 unexpected transaction ID") + return lneto.ErrMismatch } else if frm.MagicCookie() != MagicCookie { - return errors.New("dhcpv4 bad magic cookie") + return lneto.ErrInvalidField } msgType := c.getMessageType(frm) if msgType == MsgNack { - return errors.New("dhcp nack received") + return lneto.ErrPacketDrop } msgOK := msgType == MsgOffer || msgType == MsgAck @@ -243,7 +242,8 @@ func (c *Client) Demux(carrierData []byte, frameOffset int) error { c.state = StateBound } default: - return fmt.Errorf("dhcpv4 unexpected state in recv %s", c.state.String()) + internal.LogAttrs(nil, slog.LevelError, "dhcpv4:unexpected-recv-state", slog.String("state", c.state.String())) + return lneto.ErrBug } if frameOffset > 28 && c.svIPtos == 0 { ifrm, _ := ipv4.NewFrame(carrierData) diff --git a/dhcpv4/definitions.go b/dhcpv4/definitions.go index e470a09..b00df18 100644 --- a/dhcpv4/definitions.go +++ b/dhcpv4/definitions.go @@ -1,8 +1,9 @@ package dhcpv4 import ( - "errors" "unsafe" + + "github.com/soypat/lneto" ) //go:generate stringer -type=OptNum,Op,MessageType,ClientState -linecomment -output stringers.go @@ -52,9 +53,9 @@ func EncodeOption32(dst []byte, opt OptNum, v uint32) (int, error) { func EncodeOption(dst []byte, opt OptNum, data ...byte) (int, error) { if len(data) > 255 { - return 0, errors.New("DHCPv4 option data too long (>255)") + return 0, lneto.ErrInvalidLengthField } else if len(dst) < 2+len(data) { - return 0, errors.New("DHCP option buffer too short") + return 0, lneto.ErrShortBuffer } _ = dst[2+len(data)] dst[0] = byte(opt) diff --git a/dhcpv4/frame.go b/dhcpv4/frame.go index 8778498..1d4fa12 100644 --- a/dhcpv4/frame.go +++ b/dhcpv4/frame.go @@ -2,7 +2,6 @@ package dhcpv4 import ( "encoding/binary" - "errors" "github.com/soypat/lneto" ) @@ -27,7 +26,7 @@ const ( // An error is returned if the buffer size is smaller than 240. func NewFrame(buf []byte) (Frame, error) { if len(buf) < OptionsOffset { - return Frame{}, errSmallFrame + return Frame{}, lneto.ErrShortBuffer } return Frame{buf: buf}, nil } @@ -126,9 +125,9 @@ func (frm Frame) ForEachOption(fn func(off int, opt OptNum, data []byte) error) // Parse DHCP options. ptr := OptionsOffset if ptr > len(frm.buf) { - return errSmallFrame + return lneto.ErrShortBuffer } else if len(frm.buf[ptr:]) == 0 { - return errNoOptions + return lneto.ErrInvalidField } callback := fn != nil for ptr+1 < len(frm.buf) { @@ -141,7 +140,7 @@ func (frm Frame) ForEachOption(fn func(off int, opt OptNum, data []byte) error) } optlen := int(frm.buf[ptr+1]) if ptr+2+optlen > len(frm.buf) { - return errDHCPBadOption + return lneto.ErrInvalidLengthField } if callback { optionData := frm.buf[ptr+2 : ptr+2+optlen] @@ -158,16 +157,9 @@ func (frm Frame) ForEachOption(fn func(off int, opt OptNum, data []byte) error) // Validation API. // -var ( - errSmallFrame = errors.New("DHCPv4: frame size <240") - errDHCPBadOption = errors.New("DHCPv4: opt length exceeds payload") - errNoOptions = errors.New("DHCPv4: no options") - errOptionNotFit = errors.New("DHCPv4: options dont fit") -) - func (frm Frame) ValidateSize(vld *lneto.Validator) { err := frm.ForEachOption(nil) // Does all necessary validation. if err != nil { - vld.AddError(errDHCPBadOption) + vld.AddError(lneto.ErrInvalidLengthField) } } diff --git a/dhcpv4/server.go b/dhcpv4/server.go index d82067c..9b0e148 100644 --- a/dhcpv4/server.go +++ b/dhcpv4/server.go @@ -10,6 +10,8 @@ import ( "github.com/soypat/lneto/internal" ) +var errOptionNotFit = errors.New("DHCPv4: options dont fit") + type Server struct { connID uint64 nextAddr netip.Addr diff --git a/dns/client.go b/dns/client.go index 802e143..e0151d5 100644 --- a/dns/client.go +++ b/dns/client.go @@ -1,12 +1,12 @@ package dns import ( - "errors" - "fmt" + "log/slog" "math" "net" "github.com/soypat/lneto" + "github.com/soypat/lneto/internal" ) type Client struct { @@ -34,7 +34,7 @@ func (sudp *Client) ConnectionID() *uint64 { return &sudp.connID } func (c *Client) StartResolve(localPort, txid uint16, cfg ResolveConfig) error { nd := len(cfg.Questions) if nd > math.MaxUint16 { - return errors.New("overflow uint16 in DNS questions") + return lneto.ErrBufferFull } c.reset(localPort, txid, dnsSendQuery, cfg.EnableRecursion) c.msg.LimitResourceDecoding(uint16(nd), uint16(nd), 0, 0) @@ -61,7 +61,8 @@ func (c *Client) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) if err != nil { return 0, err } else if len(data) > int(msglen) { - return 0, fmt.Errorf("unexpected write %d v %d", len(data), msglen) + internal.LogAttrs(nil, slog.LevelError, "dns:unexpected-write", slog.Int("got", len(data)), slog.Int("want", int(msglen))) + return 0, lneto.ErrBug } c.state = dnsAwaitResponse // Unset don't frag since DNS requests go through LOTS of nodes. diff --git a/dns/definitions.go b/dns/definitions.go index 262e2d7..1baff1c 100644 --- a/dns/definitions.go +++ b/dns/definitions.go @@ -3,35 +3,39 @@ package dns import ( "encoding/binary" "errors" + + "github.com/soypat/lneto" ) //go:generate stringer -type=Type,Class,RCode,OpCode -linecomment -output stringers.go . // common errors. Taken from golang.org/x/net/dns/dnsmessage module. var ( - errNoResponse = errors.New("no DNS response") - errNameTooLong = errors.New("DNS name exceeds maximum length") - errNoNullTerm = errors.New("DNS name missing null terminator") - errCalcLen = errors.New("DNS calculated name label length exceeds remaining buffer length") - errCantAddLabel = errors.New("long/empty/zterm/escape DNS label or not enough space") - errBaseLen = errors.New("DNS frame length too short") - errReserved = errors.New("segment prefix is reserved") - errTooManyPtr = errors.New("too many pointers (>10)") - errInvalidPtr = errors.New("invalid pointer") - errInvalidName = errors.New("invalid dns name") - errNilResouceBody = errors.New("nil resource body") - errResourceLen = errors.New("insufficient data for resource body length") - errSegTooLong = errors.New("segment length too long") - errZeroSegLen = errors.New("zero length segment") - errResTooLong = errors.New("resource length too long") - errTooManyQuestions = errors.New("too many Questions") - errTooManyAnswers = errors.New("too many Answers") - errTooManyAuthorities = errors.New("too many Authorities") - errTooManyAdditionals = errors.New("too many Additionals") - errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)") - errStringTooLong = errors.New("character string exceeds maximum length (255)") - errCompressedSRV = errors.New("compressed name in SRV resource data") - errEmptyDomainName = errors.New("empty domain name") + errNoResponse = errors.New("no DNS response") + errNameTooLong = errors.New("DNS name exceeds maximum length") + errNoNullTerm = errors.New("DNS name missing null terminator") + errCalcLen = errors.New("DNS calculated name label length exceeds remaining buffer length") + errCantAddLabel = errors.New("long/empty/zterm/escape DNS label or not enough space") + errBaseLen = lneto.ErrShortBuffer + errReserved = errors.New("segment prefix is reserved") + errTooManyPtr = errors.New("too many pointers (>10)") + errInvalidPtr = errors.New("invalid pointer") + errInvalidName = errors.New("invalid dns name") + errNilResouceBody = errors.New("nil resource body") + errResourceLen = errors.New("insufficient data for resource body length") + errSegTooLong = errors.New("segment length too long") + errZeroSegLen = errors.New("zero length segment") + errResTooLong = errors.New("resource length too long") + + errTooManyQuestions = lneto.ErrBufferFull + errTooManyAnswers = lneto.ErrBufferFull + errTooManyAuthorities = lneto.ErrBufferFull + errTooManyAdditionals = lneto.ErrBufferFull + + errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)") + errStringTooLong = errors.New("character string exceeds maximum length (255)") + errCompressedSRV = errors.New("compressed name in SRV resource data") + errEmptyDomainName = errors.New("empty domain name") ) // Frame encapsulates the raw data of a DNS packet diff --git a/errors.go b/errors.go index e20fca2..1144087 100644 --- a/errors.go +++ b/errors.go @@ -1,30 +1,25 @@ package lneto -// type ErrorPacketDrop struct { -// Message string -// } - -// var genericErrPacketDrop = &ErrorPacketDrop{Message: ErrPacketDrop.Error()} - -// // ErrGenericPacketDrop returns the generic packet drop error. It performs no allocations. -// func ErrGenericPacketDrop() error { -// return genericErrPacketDrop -// } - -// func (err *ErrorPacketDrop) Error() string { -// return err.Message -// } - 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) - ErrZeroDestination // zero destination(port/addr) + _ errGeneric = iota // non-initialized err + ErrBug // lneto-bug(use build tag "debugheaplog") + ErrPacketDrop // packet dropped + ErrBadCRC // incorrect checksum + ErrZeroSource // zero source(port/addr) + ErrZeroDestination // zero destination(port/addr) + ErrShortBuffer // short buffer + ErrBufferFull // buffer full + ErrInvalidAddr // invalid address + ErrUnsupported // unsupported + ErrMismatch // mismatch + ErrMismatchLen // mismatched length + ErrInvalidConfig // invalid configuration + ErrInvalidField // invalid field + ErrInvalidLengthField // invalid length field + ErrExhausted // resource exhausted ) func (err errGeneric) Error() string { diff --git a/ethernet/frame.go b/ethernet/frame.go index c4ef385..82d663a 100644 --- a/ethernet/frame.go +++ b/ethernet/frame.go @@ -2,7 +2,6 @@ package ethernet import ( "encoding/binary" - "errors" "github.com/soypat/lneto" ) @@ -13,7 +12,7 @@ import ( // with payload/options of frames to avoid panics. func NewFrame(buf []byte) (Frame, error) { if len(buf) < sizeHeaderNoVLAN { - return Frame{buf: nil}, errShort + return Frame{buf: nil}, lneto.ErrShortBuffer } return Frame{buf: buf}, nil } @@ -115,19 +114,14 @@ func (frm Frame) ClearHeader() { // Validation API. // -var ( - errShort = errors.New("ethernet: too short") - errShortVLAN = errors.New("ethernet: short VLAN") -) - // ValidateSize checks the frame's size fields and compares with the actual buffer // the frame. It returns a non-nil error on finding an inconsistency. func (efrm Frame) ValidateSize(v *lneto.Validator) { sz := efrm.EtherTypeOrSize() if sz.IsSize() && len(efrm.buf) < int(sz) { - v.AddError(errShort) + v.AddError(lneto.ErrInvalidLengthField) } if sz == TypeVLAN && len(efrm.buf) < 18 { - v.AddError(errShortVLAN) + v.AddError(lneto.ErrShortBuffer) } } diff --git a/internal/ip.go b/internal/ip.go index ac86109..e1bdc35 100644 --- a/internal/ip.go +++ b/internal/ip.go @@ -2,12 +2,8 @@ package internal import ( "encoding/binary" - "errors" -) -var ( - errUnsupportedIP = errors.New("unsupported IP version") - errInvalidIPVersionToSetAddr = errors.New("invalid ip version to setDstAddr") + "github.com/soypat/lneto" ) func GetIPAddr(buf []byte) (src, dst []byte, id, ipEndOff uint16, err error) { @@ -25,7 +21,7 @@ func GetIPAddr(buf []byte) (src, dst []byte, id, ipEndOff uint16, err error) { dst = buf[24:40] ipEndOff = 40 default: - err = errUnsupportedIP + err = lneto.ErrUnsupported } return src, dst, id, ipEndOff, err } @@ -44,13 +40,13 @@ func SetIPAddrs(buf []byte, id uint16, src, dst []byte) (err error) { srcaddr = buf[8:24] dstaddr = buf[24:40] default: - return errUnsupportedIP + return lneto.ErrUnsupported } if src != nil && len(srcaddr) != len(src) { - return errors.New("mismatched length of ip src addr") + return lneto.ErrMismatchLen } if dst != nil && len(dstaddr) != len(dst) { - return errors.New("mismatched length of ip dst addr") + return lneto.ErrMismatchLen } copy(srcaddr, src) copy(dstaddr, dst) diff --git a/internal/ring.go b/internal/ring.go index 25ae469..bbd1a56 100644 --- a/internal/ring.go +++ b/internal/ring.go @@ -6,11 +6,16 @@ import ( "io" "math" "unsafe" + + "github.com/soypat/lneto" ) var ( - ErrRingBufferFull = errors.New("lneto/ring: buffer full") - errRingNoData = errors.New("lneto/ring: empty write") + ErrRingBufferFull = lneto.ErrBufferFull + errRingNoData = errors.New("lneto/ring: empty write") + errInvalidDiscard = errors.New("lneto/ring: invalid discard amount") + errDiscardExceeds = errors.New("lneto/ring: discard exceeds length") + errOffsetOverflow = errors.New("lneto/ring: offset too large (32 bit overflow)") ) // Ring implements basic Ring buffer functionality. @@ -92,12 +97,12 @@ func (r *Ring) Write(b []byte) (int, error) { // This method panics if amount of bytes is more than buffered (see [Ring.Buffered]). func (r *Ring) ReadDiscard(n int) error { if n <= 0 { - return errors.New("invalid discard amount") + return errInvalidDiscard } buffered := r.Buffered() switch { case n > buffered: - return errors.New("discard exceeds length") + return errDiscardExceeds case n == buffered: r.Reset() case n+r.Off > len(r.Buf): @@ -111,7 +116,7 @@ func (r *Ring) ReadDiscard(n int) error { // ReadAt reads data at an offset from start of readable data but does not advance read pointer. [io.EOF] returned when no data available. func (r *Ring) ReadAt(p []byte, off64 int64) (int, error) { if math.MaxInt != math.MaxInt64 && off64+int64(len(p)) > math.MaxInt32 { - return 0, errors.New("offset too large (32 bit overflow)") // Check only compiles for 32-bit platforms. + return 0, errOffsetOverflow // Check only compiles for 32-bit platforms. } off := int(off64) if off+len(p) > r.Buffered() { diff --git a/internet/definitions.go b/internet/definitions.go index 8467f9a..afd361c 100644 --- a/internet/definitions.go +++ b/internet/definitions.go @@ -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 ) diff --git a/internet/pcap/capture.go b/internet/pcap/capture.go index 4576fd1..cc75c06 100644 --- a/internet/pcap/capture.go +++ b/internet/pcap/capture.go @@ -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) diff --git a/internet/pcap/format.go b/internet/pcap/format.go index d26e964..6b1ee7d 100644 --- a/internet/pcap/format.go +++ b/internet/pcap/format.go @@ -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) diff --git a/internet/stack-ethernet.go b/internet/stack-ethernet.go index 71e0e5c..2dd0e45 100644 --- a/internet/stack-ethernet.go +++ b/internet/stack-ethernet.go @@ -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)) } diff --git a/internet/stack-ip.go b/internet/stack-ip.go index c634b9b..f41ff1d 100644 --- a/internet/stack-ip.go +++ b/internet/stack-ip.go @@ -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 } diff --git a/internet/stack-ports.go b/internet/stack-ports.go index fb4f52c..3e42cde 100644 --- a/internet/stack-ports.go +++ b/internet/stack-ports.go @@ -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)) } diff --git a/ipv4/frame.go b/ipv4/frame.go index 3b35bda..3cf13f2 100644 --- a/ipv4/frame.go +++ b/ipv4/frame.go @@ -2,7 +2,6 @@ package ipv4 import ( "encoding/binary" - "errors" "fmt" "net/netip" @@ -15,7 +14,7 @@ import ( // with payload/options of frames to avoid panics. func NewFrame(buf []byte) (Frame, error) { if len(buf) < sizeHeader { - return Frame{buf: nil}, errors.New("ipv4: short buffer") + return Frame{buf: nil}, lneto.ErrShortBuffer } return Frame{buf: buf}, nil } @@ -191,27 +190,19 @@ func (ifrm Frame) ClearHeader() { // Validation API. // -var ( - errBadTL = errors.New("ipv4: bad total length") - errShort = errors.New("ipv4: short data") - errBadIHL = errors.New("ipv4: bad IHL") - errBadVersion = errors.New("ipv4: bad version") - errEvil = errors.New("ipv4: evil packet") -) - // ValidateSize checks the frame's size fields and compares with the actual buffer // the frame. It returns a non-nil error on finding an inconsistency. func (ifrm Frame) ValidateSize(v *lneto.Validator) { ihl := ifrm.ihl() tl := ifrm.TotalLength() if tl < sizeHeader { - v.AddError(errBadTL) + v.AddError(lneto.ErrInvalidLengthField) } if int(tl) > len(ifrm.RawData()) { - v.AddError(errShort) + v.AddError(lneto.ErrShortBuffer) } if ihl < 5 || uint16(ihl)*4 > tl { - v.AddError(errBadIHL) + v.AddError(lneto.ErrInvalidLengthField) } } @@ -220,10 +211,10 @@ func (ifrm Frame) ValidateExceptCRC(v *lneto.Validator) { ifrm.ValidateSize(v) flags := ifrm.Flags() if ifrm.version() != 4 { - v.AddError(errBadVersion) + v.AddError(lneto.ErrInvalidField) } if v.Flags()&lneto.ValidateEvilBit != 0 && flags.IsEvil() { - v.AddError(errEvil) + v.AddError(lneto.ErrPacketDrop) } } diff --git a/ipv4/icmpv4/icmpv4.go b/ipv4/icmpv4/icmpv4.go index aad016d..137f59e 100644 --- a/ipv4/icmpv4/icmpv4.go +++ b/ipv4/icmpv4/icmpv4.go @@ -2,7 +2,8 @@ package icmpv4 import ( "encoding/binary" - "errors" + + "github.com/soypat/lneto" ) type Type uint8 @@ -52,13 +53,9 @@ const ( CodeRedirectToSAndHost // redirect for ToS+host ) -var ( - errShortFrame = errors.New("icmpv4: short frame") -) - func NewFrame(buf []byte) (Frame, error) { if len(buf) < 8 { - return Frame{}, errShortFrame + return Frame{}, lneto.ErrShortBuffer } return Frame{buf: buf}, nil } diff --git a/ipv6/frame.go b/ipv6/frame.go index c0877d9..96695fc 100644 --- a/ipv6/frame.go +++ b/ipv6/frame.go @@ -2,7 +2,6 @@ package ipv6 import ( "encoding/binary" - "errors" "github.com/soypat/lneto" ) @@ -13,7 +12,7 @@ import ( // with payload/options of frames to avoid panics. func NewFrame(buf []byte) (Frame, error) { if len(buf) < sizeHeader { - return Frame{buf: nil}, errShortBuf + return Frame{buf: nil}, lneto.ErrShortBuffer } return Frame{buf: buf}, nil } @@ -119,16 +118,12 @@ func (i6frm Frame) ClearHeader() { // Validate API. // -var ( - errShortFrame = errors.New("ipv6: short frame") - errShortBuf = errors.New("ipv6: short buffer for frame") -) // ValidateSize checks the frame's size fields and compares with the actual buffer // the frame. It returns a non-nil error on finding an inconsistency. func (i6frm Frame) ValidateSize(v *lneto.Validator) { tl := i6frm.PayloadLength() if int(tl)+sizeHeader > len(i6frm.RawData()) { - v.AddError(errShortFrame) + v.AddError(lneto.ErrInvalidLengthField) } } diff --git a/ntp/client.go b/ntp/client.go index 2d6c387..aa3991e 100644 --- a/ntp/client.go +++ b/ntp/client.go @@ -1,8 +1,9 @@ package ntp import ( - "errors" "time" + + "github.com/soypat/lneto" ) type state uint8 @@ -100,7 +101,7 @@ func (c *Client) Demux(carrierData []byte, frameOffset int) error { xmt := frm.TransmitTime() orig := frm.OriginTime() if xmt == orig || orig != c.t[0] { - return errors.New("bogus NTP packet") + return lneto.ErrPacketDrop } txelapsed := c.now().Sub(c.start) diff --git a/ntp/ntp.go b/ntp/ntp.go index 92c7e86..d8777d4 100644 --- a/ntp/ntp.go +++ b/ntp/ntp.go @@ -3,11 +3,12 @@ package ntp import ( "encoding/binary" - "errors" "math" "math/bits" "sync" "time" + + "github.com/soypat/lneto" ) // NTP Global Parameters. @@ -26,7 +27,7 @@ const ( func NewFrame(buf []byte) (Frame, error) { if len(buf) < SizeHeader { - return Frame{buf: nil}, errors.New("NTP frame too short") + return Frame{buf: nil}, lneto.ErrShortBuffer } return Frame{buf: buf}, nil } @@ -189,12 +190,12 @@ func TimestampFromUint64(ts uint64) Timestamp { func TimestampFromTime(t time.Time) (Timestamp, error) { t = t.UTC() if t.Before(baseTime) { - return Timestamp{}, errors.New("ntp.TimestampFromTime: time is before baseTime") + return Timestamp{}, lneto.ErrUnsupported } off := t.Sub(baseTime) sec := uint64(off / time.Second) if sec > math.MaxUint32 { - return Timestamp{}, errors.New("ntp.TimestampFromTime: time is too large") + return Timestamp{}, lneto.ErrUnsupported } fra := uint64(off%time.Second) * math.MaxUint32 / uint64(time.Second) return Timestamp{ @@ -254,7 +255,7 @@ func (d Date) Time() (time.Time, error) { } hi, seclo := bits.Mul64(uint64(sec), uint64(time.Second)) if hi != 0 || seclo > math.MaxInt64-uint64(time.Second)-1 { - return time.Time{}, errors.New("ntp.Date.Time overflow") + return time.Time{}, lneto.ErrUnsupported } off := time.Duration(seclo) off += time.Second * time.Duration(d.frac>>32) / math.MaxUint32 diff --git a/phy/phy.go b/phy/phy.go index c9b584b..38c391f 100644 --- a/phy/phy.go +++ b/phy/phy.go @@ -9,6 +9,8 @@ package phy import ( "errors" "time" + + "github.com/soypat/lneto" ) // MDIOBus is a HAL for MDIO bus access supporting both Clause 22 and Clause 45 devices. @@ -33,7 +35,7 @@ func FindClause22PHYs(mdio MDIOBus, dst []uint8) (n int, err error) { const maxAddr = 31 const regBasicStatus = 0x01 if len(dst) < 32 { - return -1, errors.New("require buffer length 32 for FindPHYs") + return -1, lneto.ErrShortBuffer } n = 0 for addr := uint8(0); addr <= maxAddr; addr++ { @@ -56,9 +58,7 @@ func FindClause22PHYs(mdio MDIOBus, dst []uint8) (n int, err error) { return n, err } -var ( - errInvalidPhyAddr = errors.New("invalid phy addr") -) +var errInvalidPhyAddr error = lneto.ErrInvalidAddr type Device struct { mdio MDIOBus @@ -73,7 +73,7 @@ func (phy *Device) ConfigureAs22(mdio MDIOBus, phyAddr uint8) error { return errInvalidPhyAddr } else if mdio == nil { - return errors.New("nil mdio bus") + return lneto.ErrInvalidConfig } phy.mdio = mdio phy.phyaddr = phyAddr @@ -176,7 +176,7 @@ func (phy *Device) SetupForced(mode LinkMode) error { case 10: // No speed bits = 10Mbps default: - return errors.New("unsupported forced link mode") + return lneto.ErrUnsupported } if mode.IsFullDuplex() { ctl |= BMCRFullDuplex diff --git a/stringers.go b/stringers.go index a2799ec..8e065b1 100644 --- a/stringers.go +++ b/stringers.go @@ -199,11 +199,21 @@ func _() { _ = x[ErrBadCRC-3] _ = x[ErrZeroSource-4] _ = x[ErrZeroDestination-5] + _ = x[ErrShortBuffer-6] + _ = x[ErrBufferFull-7] + _ = x[ErrInvalidAddr-8] + _ = x[ErrUnsupported-9] + _ = x[ErrMismatch-10] + _ = x[ErrMismatchLen-11] + _ = x[ErrInvalidConfig-12] + _ = x[ErrInvalidField-13] + _ = x[ErrInvalidLengthField-14] + _ = x[ErrExhausted-15] } -const _errGeneric_name = "lneto-bug(use build tag \"debugheaplog\")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)short bufferbuffer fullinvalid addressunsupportedmismatchmismatched lengthinvalid configurationinvalid fieldinvalid length fieldresource exhausted" -var _errGeneric_index = [...]uint8{0, 39, 53, 71, 93, 120} +var _errGeneric_index = [...]uint16{0, 39, 53, 71, 93, 120, 132, 143, 158, 169, 177, 194, 215, 228, 248, 266} func (i errGeneric) String() string { i -= 1 diff --git a/tcp/conn.go b/tcp/conn.go index 5cf8c8b..021ae34 100644 --- a/tcp/conn.go +++ b/tcp/conn.go @@ -15,12 +15,8 @@ import ( ) var ( - errDeadlineExceeded = os.ErrDeadlineExceeded - errNoRemoteAddr = errors.New("tcp: no remote address established") - errInvalidIP = errors.New("tcp: invalid IP") - errMismatchedIPVersion = errors.New("mismatched IP version") - errBadDemuxOffset = errors.New("bad offset in TCPConn.Recv") - errIPAddrMismatch = errors.New("IP addr mismatch on TCPConn") + errDeadlineExceeded = os.ErrDeadlineExceeded + errNoRemoteAddr = errors.New("tcp: no remote address established") ) // Conn builds on the [Handler] abstraction and adds IP header knowledge, time management, and familiar user facing API @@ -135,7 +131,7 @@ func (conn *Conn) OpenActive(localPort uint16, remote netip.AddrPort, iss Value) conn.mu.Lock() defer conn.mu.Unlock() if !remote.IsValid() { - return errInvalidIP + return lneto.ErrInvalidAddr } rport := remote.Port() err := conn.h.OpenActive(localPort, rport, iss) @@ -330,14 +326,14 @@ func (conn *Conn) Demux(buf []byte, off int) (err error) { conn.mu.Lock() defer conn.mu.Unlock() if off >= len(buf) { - return errBadDemuxOffset + return lneto.ErrShortBuffer } raddr, _, id, _, err := internal.GetIPAddr(buf[:off]) if err != nil { return err } if conn.isRaddrSet() && !internal.BytesEqual(conn.remoteAddr, raddr) { - return errIPAddrMismatch + return lneto.ErrMismatch } conn.trace("tcpconn.Recv", slog.Uint64("lport", uint64(conn.h.LocalPort())), slog.Uint64("rport", uint64(conn.h.remotePort))) err = conn.h.Recv(buf[off:]) @@ -365,7 +361,7 @@ func (conn *Conn) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) if err != nil { return 0, err } else if len(raddr) != len(conn.remoteAddr) { - return 0, errMismatchedIPVersion + return 0, lneto.ErrMismatchLen } n, err = conn.h.Send(carrierData[offsetToFrame:]) if err != nil || n == 0 { diff --git a/tcp/definitions.go b/tcp/definitions.go index 4ce0a3c..8b731f9 100644 --- a/tcp/definitions.go +++ b/tcp/definitions.go @@ -6,24 +6,24 @@ import ( "math/bits" "strconv" "unsafe" + + "github.com/soypat/lneto" ) //go:generate stringer -type=State,OptionKind -linecomment -output stringers.go . var ( - // errDropSegment is a flag that signals to drop a segment silently. - errDropSegment = errors.New("drop segment") - errWindowTooLarge = errors.New("invalid window size > 2**16") + errDropSegment error = lneto.ErrPacketDrop + errWindowTooLarge = errors.New("invalid window size > 2**16") - errBufferTooSmall = errors.New("tcp buffer too small") - errNeedClosedTCBToOpen = errors.New("need closed TCB to call open") - errInvalidState = errors.New("invalid state") - errConnNotExist = errors.New("connection does not exist") - errConnectionClosing = errors.New("connection closing") - errExpectedSYN = errors.New("seqs:expected SYN") - errBadSegack = errors.New("seqs:bad segack") - errFinwaitExpectedACK = errors.New("seqs:finwait1 expected ACK") - errFinwaitExpectedFinack = errors.New("seqs:finwait2 expected FINACK") + errBufferTooSmall error = lneto.ErrShortBuffer + errNeedClosedTCBToOpen = errors.New("need closed TCB to call open") + errInvalidState = errors.New("invalid state") + errConnNotExist = errors.New("connection does not exist") + errConnectionClosing = errors.New("connection closing") + errExpectedSYN = errors.New("seqs:expected SYN") + errBadSegack = errors.New("seqs:bad segack") + errFinwaitExpectedACK = errors.New("seqs:finwait1 expected ACK") errWindowOverflow = newRejectErr("wnd > 2**16") errSeqNotInWindow = newRejectErr("seq not in snd/rcv.wnd") diff --git a/tcp/frame.go b/tcp/frame.go index 57a94e6..8645566 100644 --- a/tcp/frame.go +++ b/tcp/frame.go @@ -2,7 +2,6 @@ package tcp import ( "encoding/binary" - "errors" "fmt" "math" @@ -19,7 +18,7 @@ const ( // with payload/options of frames to avoid panics. func NewFrame(buf []byte) (Frame, error) { if len(buf) < sizeHeaderTCP { - return Frame{buf: nil}, errors.New("TCP packet too short") + return Frame{buf: nil}, lneto.ErrShortBuffer } return Frame{buf: buf}, nil } @@ -178,13 +177,6 @@ func (tfrm Frame) String() string { // Validation API // -var ( - errShortTCP = errors.New("TCP offset exceeds frame") - errBadTCPOff = errors.New("TCP offset invalid") - errEvilPacket = errors.New("evil packet") - errZeroDstPort = errors.New("TCP zero destination port") - errZeroSrcPort = errors.New("TCP zero source port") -) // func (tfrm Frame) Validate(v *lneto.Validator) { // tfrm.ValidateSize(v) @@ -196,19 +188,19 @@ var ( func (tfrm Frame) ValidateSize(v *lneto.Validator) { off := tfrm.HeaderLength() if off < sizeHeaderTCP { - v.AddBitPosErr(12*8, 4, errBadTCPOff) + v.AddBitPosErr(12*8, 4, lneto.ErrInvalidLengthField) } if off > len(tfrm.RawData()) { - v.AddBitPosErr(12*8, 4, errShortTCP) + v.AddBitPosErr(12*8, 4, lneto.ErrInvalidLengthField) } } func (tfrm Frame) ValidateExceptCRC(v *lneto.Validator) { tfrm.ValidateSize(v) if tfrm.DestinationPort() == 0 { - v.AddBitPosErr(2*8, 16, errZeroDstPort) + v.AddBitPosErr(2*8, 16, lneto.ErrZeroDestination) } if tfrm.SourcePort() == 0 { - v.AddBitPosErr(0, 16, errZeroSrcPort) + v.AddBitPosErr(0, 16, lneto.ErrZeroSource) } } diff --git a/tcp/handler.go b/tcp/handler.go index 480eea6..0526dc3 100644 --- a/tcp/handler.go +++ b/tcp/handler.go @@ -1,7 +1,6 @@ package tcp import ( - "errors" "io" "net" @@ -11,11 +10,6 @@ import ( "github.com/soypat/lneto/internal" ) -var ( - errMismatchedSrcPort = errors.New("source port mismatch") - errMismatchedDstPort = errors.New("destination port mismatch") -) - // Handler is a low level TCP handling data structure. It implements logic // related to data buffering, frame sequencing and connection state handling. // Does NOT implement IP related logic, so no CRC calculation/validation or pseudo header logic. @@ -56,10 +50,10 @@ func (h *Handler) State() State { return h.scb.State() } // If the argument buffer is nil then the respective currently set buffer will be reused. func (h *Handler) SetBuffers(txbuf, rxbuf []byte, packets int) error { if h.bufRx.Buf == nil && (len(rxbuf) < minBufferSize || len(txbuf) < minBufferSize) { - return errors.New("tcp: short buffer") + return lneto.ErrShortBuffer } if !h.scb.State().IsClosed() { - return errors.New("tcp.Handler must be closed before setting buffers") + return lneto.ErrInvalidConfig } if rxbuf != nil { h.bufRx.Buf = rxbuf @@ -156,15 +150,15 @@ func (h *Handler) Recv(incomingPacket []byte) error { remotePort := tfrm.SourcePort() if h.remotePort != 0 && remotePort != h.remotePort { - return errMismatchedSrcPort + return lneto.ErrMismatch } dstPort := tfrm.DestinationPort() if h.localPort != dstPort { - return errMismatchedDstPort + return lneto.ErrMismatch } payload := tfrm.Payload() if len(payload) > h.bufRx.Free() { - return errors.New("rx buffer full") + return lneto.ErrBufferFull } segIncoming := tfrm.Segment(len(payload)) if h.scb.IncomingIsKeepalive(segIncoming) { diff --git a/tcp/listener.go b/tcp/listener.go index e9ac8b2..572d58a 100644 --- a/tcp/listener.go +++ b/tcp/listener.go @@ -1,7 +1,6 @@ package tcp import ( - "errors" "log/slog" "net" "sync" @@ -70,7 +69,7 @@ func (listener *Listener) Close() error { listener.mu.Lock() defer listener.mu.Unlock() if listener.isClosed() { - return errors.New("already closed") + return net.ErrClosed } listener.debug("listener:reset", slog.Uint64("port", uint64(listener.port))) listener.connID++ @@ -80,9 +79,9 @@ func (listener *Listener) Close() error { func (listener *Listener) Reset(port uint16, pool pool) error { if port == 0 { - return errZeroDstPort + return lneto.ErrZeroSource } else if pool == nil { - return errors.New("nil TCP pool") + return lneto.ErrInvalidConfig } listener.mu.Lock() defer listener.mu.Unlock() @@ -126,7 +125,7 @@ func (listener *Listener) TryAccept() (*Conn, any, error) { listener.incoming[i] = handler{} // discard from ready. return conn, userData, nil } - return nil, nil, errors.New("no conns available") + return nil, nil, lneto.ErrExhausted } // Encapsulate implements [StackNode]. @@ -199,7 +198,7 @@ func (listener *Listener) Demux(carrierData []byte, tcpFrameOffset int) error { } dst := tfrm.DestinationPort() if dst != listener.port { - return errors.New("not our port") + return lneto.ErrMismatch } src := tfrm.SourcePort() diff --git a/tcp/options.go b/tcp/options.go index bc6f7dd..ca83908 100644 --- a/tcp/options.go +++ b/tcp/options.go @@ -1,9 +1,9 @@ package tcp import ( - "errors" - "fmt" "strings" + + "github.com/soypat/lneto" ) type OptionKind uint8 @@ -88,11 +88,11 @@ func (op OptionCodec) PutOption32(dst []byte, kind OptionKind, v uint32) (int, e func (op OptionCodec) PutOption(dst []byte, kind OptionKind, data ...byte) (int, error) { putSize := 2 + len(data) if len(dst) < putSize { - return -1, errBufferTooSmall + return -1, lneto.ErrShortBuffer } else if putSize > 255 { - return -1, errors.New("option data too large") + return -1, lneto.ErrInvalidLengthField } else if kind == OptNop || kind == OptEnd { - return -1, errors.New("cant put Nop or End option type") + return -1, lneto.ErrInvalidField } dst[0] = byte(kind) dst[1] = byte(putSize) @@ -111,13 +111,13 @@ func (op OptionCodec) ForEachOption(opts []byte, fn func(OptionKind, []byte) err continue } if len(opts[off:]) < 1 { - return errors.New("short TCP options") + return lneto.ErrShortBuffer } size := int(opts[off]) // Total option length including kind and length bytes. off++ dataLen := size - 2 // Data bytes after kind and length. if dataLen < 0 || len(opts[off:]) < dataLen { - return fmt.Errorf("option %q length %d exceeds buffer size %d", kind.String(), size, len(opts[off:])) + return lneto.ErrShortBuffer } if !skipSizeValidation { @@ -133,7 +133,7 @@ func (op OptionCodec) ForEachOption(opts []byte, fn func(OptionKind, []byte) err expectSize = 2 } if expectSize != -1 && size != expectSize { - return fmt.Errorf("bad TCP option %q size want %d got %d", kind.String(), expectSize, size) + return lneto.ErrInvalidLengthField } } if !(skipObsolete && kind.IsObsolete()) { diff --git a/tcp/syncookie.go b/tcp/syncookie.go index b5989ce..ddacd5a 100644 --- a/tcp/syncookie.go +++ b/tcp/syncookie.go @@ -2,8 +2,9 @@ package tcp import ( "encoding/binary" - "errors" "io" + + "github.com/soypat/lneto" ) // Embed low 5 bits of counter into cookie for efficient validation. @@ -45,15 +46,13 @@ type SYNCookieConfig struct { MaxCounterDelta uint32 } -var ( - errInvalidCookie = errors.New("tcp: invalid SYN cookie") -) +var errInvalidCookie error = lneto.ErrMismatch // Reset initializes or reinitializes the SYNCookie with the given configuration. // The counter is preserved across resets to maintain cookie validity during secret rotation. func (sc *SYNCookieJar) Reset(config SYNCookieConfig) error { if config.Rand == nil { - return errors.New("need rand function") + return lneto.ErrInvalidConfig } _, err := io.ReadFull(config.Rand, sc.secret[:]) if err != nil { diff --git a/tcp/txqueue.go b/tcp/txqueue.go index a345971..f97901e 100644 --- a/tcp/txqueue.go +++ b/tcp/txqueue.go @@ -1,20 +1,12 @@ package tcp import ( - "errors" + "log/slog" + "github.com/soypat/lneto" "github.com/soypat/lneto/internal" ) -var ( - errPacketQueueFull = errors.New("packet queue full") - errQueuedPacketsLEZ = errors.New("queued packets <=0") - errInvalidBufSize = errors.New("invalid buffer size") - errSeqLessThanLast = errors.New("sequence number less than last sequence number") - errNoPacketToAck = errors.New("no packet to ack") - errAckUnsent = errors.New("ack of unsent packet") -) - const ( // this must be at least 2 for buffer to work. minBufferSize = 2 @@ -60,9 +52,9 @@ type ringidx struct { func (rtx *ringTx) Reset(buf []byte, maxqueuedPackets int, iss Value) error { buf = buf[:len(buf):len(buf)] // safely omit capacity section. if maxqueuedPackets <= 0 { - return errQueuedPacketsLEZ + return lneto.ErrInvalidConfig } else if len(buf) < minBufferSize || len(buf) < maxqueuedPackets { - return errInvalidBufSize + return lneto.ErrShortBuffer } *rtx = ringTx{ @@ -127,11 +119,12 @@ func (rtx *ringTx) Write(b []byte) (n int, err error) { func (rtx *ringTx) MakePacket(b []byte, currentSeq Value) (int, error) { free := rtx.slist.Free() if free == 0 { - return 0, errPacketQueueFull + return 0, lneto.ErrBufferFull } endSeq, ok := rtx.sentEndSeq() if ok && currentSeq.LessThan(endSeq) { - return 0, errSeqLessThanLast + internal.LogAttrs(nil, slog.LevelError, "txqueue:seq len(ufrm.RawData()) { - v.AddError(errShort) + v.AddError(lneto.ErrShortBuffer) } } diff --git a/x/xnet/stack-async.go b/x/xnet/stack-async.go index 22ed8ea..499f2e3 100644 --- a/x/xnet/stack-async.go +++ b/x/xnet/stack-async.go @@ -99,13 +99,13 @@ func (s *StackAsync) Reset(cfg StackConfig) error { addr := cfg.StaticAddress s.prng = uint32(cfg.RandSeed) if s.prng == 0 { - return errors.New("zero random seed") + return lneto.ErrInvalidConfig } s.hostname = cfg.Hostname if !addr.IsValid() { addr = netip.AddrFrom4([4]byte{}) // If static not set DHCP will be performed and address will be zero. } else if addr.Is6() { - return errors.New("IPv6 unsupported as of yet") + return lneto.ErrUnsupported } const linkNodes = 2 // ARP and IP nodes ecfg := internet.StackEthernetConfig{ @@ -171,13 +171,11 @@ func (s *StackAsync) Reset(cfg StackConfig) error { return nil } -var errInvalidIPAddr = errors.New("invaldi IP address") - func (s *StackAsync) resetARP() error { mac := s.link.HardwareAddr6() addr := s.ip.Addr() if !addr.IsValid() { - return errInvalidIPAddr + return lneto.ErrInvalidAddr } proto := ethernet.TypeIPv4 if addr.Is6() { @@ -384,7 +382,7 @@ func (s *StackAsync) ResultLookupIP(host string) ([]netip.Addr, bool, error) { } else if len(data) == 16 { addrs = append(addrs, netip.AddrFrom16([16]byte(data))) } else { - err = errors.New("bogus IP") + err = lneto.ErrInvalidAddr } } if err == nil && len(addrs) == 0 { @@ -441,7 +439,7 @@ func (s *StackAsync) StartResolveHardwareAddress6(ip netip.Addr) error { s.mu.Lock() defer s.mu.Unlock() if !ip.Is4() { - return errors.New("unsupported or invalid IP address") + return lneto.ErrUnsupported } addr := ip.As4() return s.arp.StartQuery(nil, addr[:]) @@ -452,7 +450,7 @@ func (s *StackAsync) ResultResolveHardwareAddress6(ip netip.Addr) (hw [6]byte, e s.mu.Lock() defer s.mu.Unlock() if !ip.Is4() { - return hw, errors.New("unsupported or invalid IP address") + return hw, lneto.ErrUnsupported } addr := ip.As4() hwslice, err := s.arp.QueryResult(addr[:]) @@ -469,7 +467,7 @@ func (s *StackAsync) DiscardResolveHardwareAddress6(ip netip.Addr) error { s.mu.Lock() defer s.mu.Unlock() if !ip.Is4() { - return errors.New("unsupported or invalid IP address") + return lneto.ErrUnsupported } addr := ip.As4() return s.arp.DiscardQuery(addr[:]) @@ -526,7 +524,7 @@ func (stack *StackAsync) AssimilateDHCPResults(results *DHCPResults) error { } if len(results.DNSServers) > 0 { if !results.DNSServers[0].IsValid() || !results.DNSServers[0].Is4() { - return errors.New("bad DNS server address, IPv6 or invalid") + return lneto.ErrInvalidAddr } stack.dnssv = results.DNSServers[0] } diff --git a/x/xnet/stack-berkeley.go b/x/xnet/stack-berkeley.go index c61e4f4..388484b 100644 --- a/x/xnet/stack-berkeley.go +++ b/x/xnet/stack-berkeley.go @@ -2,12 +2,12 @@ package xnet import ( "context" - "errors" "net" "net/netip" "syscall" "time" + "github.com/soypat/lneto" "github.com/soypat/lneto/tcp" ) @@ -36,7 +36,7 @@ func (s StackBerkeley) Socket(ctx context.Context, network string, family, sotyp switch family { case syscall.AF_INET: default: - return nil, errors.New("unsupported address family") + return nil, lneto.ErrUnsupported } var local, remote netip.AddrPort if laddr != nil { @@ -54,10 +54,10 @@ func (s StackBerkeley) Socket(ctx context.Context, network string, family, sotyp switch network { case "udp", "udp4": - return nil, errors.New("udp not yet supported") + return nil, lneto.ErrUnsupported case "tcp", "tcp4": if sotype != sockSTREAM { - return nil, errors.New("unsupported socket type") + return nil, lneto.ErrUnsupported } if raddr != nil { @@ -106,7 +106,7 @@ func (s StackBerkeley) Socket(ctx context.Context, network string, family, sotyp return &l, nil } } - return nil, errors.New("unsupported network") + return nil, lneto.ErrUnsupported } type tcplistener struct { diff --git a/x/xnet/tcppool.go b/x/xnet/tcppool.go index f881f2e..9dead6f 100644 --- a/x/xnet/tcppool.go +++ b/x/xnet/tcppool.go @@ -2,11 +2,11 @@ package xnet import ( "context" - "errors" "log/slog" "sync" "time" + "github.com/soypat/lneto" "github.com/soypat/lneto/tcp" ) @@ -52,7 +52,7 @@ type TCPPoolConfig struct { func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) { if cfg.EstablishedTimeout <= 0 || cfg.ClosingTimeout <= 0 { - return nil, errors.New("invalid timeout") + return nil, lneto.ErrInvalidConfig } n := cfg.PoolSize pool := &TCPPool{