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
+13 -5
View File
@@ -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.
+1 -2
View File
@@ -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
}
+13 -14
View File
@@ -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:
+12 -12
View File
@@ -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)
+4 -3
View File
@@ -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)
+5 -13
View File
@@ -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)
}
}
+2
View File
@@ -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
+5 -4
View File
@@ -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.
+27 -23
View File
@@ -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
+16 -21
View File
@@ -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 {
+3 -9
View File
@@ -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)
}
}
+5 -9
View File
@@ -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)
+10 -5
View File
@@ -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() {
+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))
}
+6 -15
View File
@@ -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)
}
}
+3 -6
View File
@@ -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
}
+2 -7
View File
@@ -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)
}
}
+3 -2
View File
@@ -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)
+6 -5
View File
@@ -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
+6 -6
View File
@@ -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
+12 -2
View File
@@ -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
+6 -10
View File
@@ -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 {
+12 -12
View File
@@ -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")
+5 -13
View File
@@ -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)
}
}
+5 -11
View File
@@ -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) {
+5 -6
View File
@@ -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()
+8 -8
View File
@@ -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()) {
+4 -5
View File
@@ -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 {
+9 -16
View File
@@ -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<endseq", slog.Uint64("seq", uint64(currentSeq)), slog.Uint64("endseq", uint64(endSeq)))
return 0, lneto.ErrBug
}
// Reading unsent ring consumes unsent and converts it to "sent".
unsent, _ := rtx.unsentRing()
@@ -321,11 +314,11 @@ func (sl *sentlist) AddPacket(datalen, off, bufsize int, seq Value) *ringidx {
func (sl *sentlist) RecvAck(ack Value, bufsize int) error {
newest := sl.Newest()
if newest == nil {
return errNoPacketToAck
return lneto.ErrPacketDrop
}
endseq := newest.endSeq()
if endseq.LessThan(ack) {
return errAckUnsent
return lneto.ErrPacketDrop
}
// Mark fully acked.
for i := 0; i < len(sl.pkts); i++ {
+3 -9
View File
@@ -2,7 +2,6 @@ package udp
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: buf}, errors.New("UDP packet too short")
return Frame{buf: buf}, lneto.ErrShortBuffer
}
return Frame{buf: buf}, nil
}
@@ -90,19 +89,14 @@ func (frm Frame) ClearHeader() {
// Validation API.
//
var (
errBadLen = errors.New("udp: bad UDP length")
errShort = errors.New("udp: short buffer")
)
// 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 (ufrm Frame) ValidateSize(v *lneto.Validator) {
ul := ufrm.Length()
if ul < sizeHeader {
v.AddError(errBadLen)
v.AddError(lneto.ErrInvalidLengthField)
}
if int(ul) > len(ufrm.RawData()) {
v.AddError(errShort)
v.AddError(lneto.ErrShortBuffer)
}
}
+8 -10
View File
@@ -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]
}
+5 -5
View File
@@ -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 {
+2 -2
View File
@@ -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{