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 package arp
import "errors" import (
"errors"
"github.com/soypat/lneto"
)
//go:generate stringer -type=Operation -linecomment -output stringers.go . //go:generate stringer -type=Operation -linecomment -output stringers.go .
@@ -11,10 +15,14 @@ const (
) )
var ( var (
errARPBufferFull = errors.New("ARP client need handling:too many ops pending") errQueryPending = errors.New("arp: query pending")
errShortARP = errors.New("packet too short to be ARP") errQueryNotFound = errors.New("arp: query not found")
errARPUnsupported = errors.New("ARP not supported")
errLargeSizes = errors.New("size of ARP protocol+hardware is unusually large") // 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. // Operation represents the type of ARP packet, either request or reply/response.
+1 -2
View File
@@ -2,7 +2,6 @@ package arp
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
@@ -17,7 +16,7 @@ import (
// with payload/options of frames to avoid panics. // with payload/options of frames to avoid panics.
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < sizeHeaderv4 { 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 return Frame{buf: buf}, nil
} }
+13 -14
View File
@@ -1,7 +1,6 @@
package arp package arp
import ( import (
"errors"
"log/slog" "log/slog"
"github.com/soypat/lneto" "github.com/soypat/lneto"
@@ -36,7 +35,7 @@ func (h *Handler) ConnectionID() *uint64 { return &h.connID }
func (h *Handler) UpdateProtoAddr(protoAddr []byte) error { func (h *Handler) UpdateProtoAddr(protoAddr []byte) error {
if len(protoAddr) != len(h.ourProtoAddr) { if len(protoAddr) != len(h.ourProtoAddr) {
return errors.New("mismatch ARP proto size") return lneto.ErrMismatchLen
} }
copy(h.ourProtoAddr, protoAddr) copy(h.ourProtoAddr, protoAddr)
return nil return nil
@@ -45,9 +44,9 @@ func (h *Handler) UpdateProtoAddr(protoAddr []byte) error {
func (h *Handler) Reset(cfg HandlerConfig) error { func (h *Handler) Reset(cfg HandlerConfig) error {
if len(cfg.HardwareAddr) == 0 || len(cfg.HardwareAddr) > 255 || if len(cfg.HardwareAddr) == 0 || len(cfg.HardwareAddr) > 255 ||
len(cfg.ProtocolAddr) == 0 || len(cfg.ProtocolAddr) > 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 { } else if cfg.MaxQueries <= 0 || cfg.MaxPending <= 0 {
return errors.New("invalid Handler query or pending config") return lneto.ErrInvalidConfig
} }
*h = Handler{ *h = Handler{
connID: h.connID + 1, connID: h.connID + 1,
@@ -102,16 +101,16 @@ func (h *Handler) QueryResult(protoAddr []byte) (hwAddr []byte, err error) {
for i := range h.queries { for i := range h.queries {
if internal.BytesEqual(protoAddr, h.queries[i].protoaddr) { if internal.BytesEqual(protoAddr, h.queries[i].protoaddr) {
if !h.queries[i].querysent { if !h.queries[i].querysent {
return nil, errors.New("query not yet sent") return nil, errQueryPending
} }
mac := h.queries[i].response() mac := h.queries[i].response()
if mac == nil { if mac == nil {
return nil, errors.New("no response yet") return nil, errQueryPending
} }
return mac, nil return mac, nil
} }
} }
return nil, errors.New("query not exist or dropped") return nil, errQueryNotFound
} }
func (h *Handler) DiscardQuery(protoAddr []byte) error { func (h *Handler) DiscardQuery(protoAddr []byte) error {
@@ -122,7 +121,7 @@ func (h *Handler) DiscardQuery(protoAddr []byte) error {
return nil return nil
} }
} }
return errors.New("query not found") return errQueryNotFound
} }
func (h *Handler) compactQueries() { func (h *Handler) compactQueries() {
@@ -150,15 +149,15 @@ func (h *Handler) StartQuery(dstHWAddr, proto []byte) error {
if len(h.queries) == cap(h.queries) { if len(h.queries) == cap(h.queries) {
h.compactQueries() h.compactQueries()
if len(h.queries) == cap(h.queries) { if len(h.queries) == cap(h.queries) {
return errors.New("too many ongoing queries") return lneto.ErrBufferFull
} }
} }
if len(proto) != len(h.ourProtoAddr) { 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) { } 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...) { } 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] h.queries = h.queries[:len(h.queries)+1]
q := &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() htype, hlen := afrm.Hardware()
if htype != h.htype || int(hlen) != len(h.ourHWAddr) { if htype != h.htype || int(hlen) != len(h.ourHWAddr) {
return errors.New("bad ARP hardware") return lneto.ErrMismatch
} }
protoType, protoLen := afrm.Protocol() protoType, protoLen := afrm.Protocol()
if protoType != h.protoType || int(protoLen) != len(h.ourProtoAddr) { if protoType != h.protoType || int(protoLen) != len(h.ourProtoAddr) {
return errors.New("bad ARP proto") return lneto.ErrMismatch
} }
switch afrm.Operation() { switch afrm.Operation() {
case OpRequest: case OpRequest:
+12 -12
View File
@@ -2,8 +2,6 @@ package dhcpv4
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"io" "io"
"log/slog" "log/slog"
"math" "math"
@@ -80,13 +78,13 @@ func (c *Client) Reset() {
func (c *Client) BeginRequest(xid uint32, cfg RequestConfig) error { func (c *Client) BeginRequest(xid uint32, cfg RequestConfig) error {
if len(cfg.Hostname) > 36 { if len(cfg.Hostname) > 36 {
return errors.New("requested hostname too long") return lneto.ErrInvalidConfig
} else if c.state != StateInit && c.state != 0 { } 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 { } else if xid == 0 {
return errors.New("zero xid") return lneto.ErrInvalidConfig
} else if len(cfg.ClientID) > 32 { } else if len(cfg.ClientID) > 32 {
return errors.New("client ID too long") return lneto.ErrInvalidConfig
} }
c.reset(xid) c.reset(xid)
c.state = StateInit c.state = StateInit
@@ -143,7 +141,7 @@ func (c *Client) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int)
} }
opts := frm.OptionsPayload() opts := frm.OptionsPayload()
if len(opts) < 255 { if len(opts) < 255 {
return 0, errors.New("too short packet for options") return 0, lneto.ErrShortBuffer
} }
var nextState ClientState var nextState ClientState
@@ -181,7 +179,8 @@ func (c *Client) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int)
nextState = StateRequesting nextState = StateRequesting
default: 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...) n, _ := EncodeOption(opts[numOpts:], OptClientIdentifier, c.clientID...)
numOpts += n numOpts += n
@@ -210,13 +209,13 @@ func (c *Client) Demux(carrierData []byte, frameOffset int) error {
if err != nil { if err != nil {
return err return err
} else if frm.XID() != c.currentXID { } else if frm.XID() != c.currentXID {
return errors.New("dhcpv4 unexpected transaction ID") return lneto.ErrMismatch
} else if frm.MagicCookie() != MagicCookie { } else if frm.MagicCookie() != MagicCookie {
return errors.New("dhcpv4 bad magic cookie") return lneto.ErrInvalidField
} }
msgType := c.getMessageType(frm) msgType := c.getMessageType(frm)
if msgType == MsgNack { if msgType == MsgNack {
return errors.New("dhcp nack received") return lneto.ErrPacketDrop
} }
msgOK := msgType == MsgOffer || msgType == MsgAck msgOK := msgType == MsgOffer || msgType == MsgAck
@@ -243,7 +242,8 @@ func (c *Client) Demux(carrierData []byte, frameOffset int) error {
c.state = StateBound c.state = StateBound
} }
default: 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 { if frameOffset > 28 && c.svIPtos == 0 {
ifrm, _ := ipv4.NewFrame(carrierData) ifrm, _ := ipv4.NewFrame(carrierData)
+4 -3
View File
@@ -1,8 +1,9 @@
package dhcpv4 package dhcpv4
import ( import (
"errors"
"unsafe" "unsafe"
"github.com/soypat/lneto"
) )
//go:generate stringer -type=OptNum,Op,MessageType,ClientState -linecomment -output stringers.go //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) { func EncodeOption(dst []byte, opt OptNum, data ...byte) (int, error) {
if len(data) > 255 { 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) { } 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[2+len(data)]
dst[0] = byte(opt) dst[0] = byte(opt)
+5 -13
View File
@@ -2,7 +2,6 @@ package dhcpv4
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"github.com/soypat/lneto" "github.com/soypat/lneto"
) )
@@ -27,7 +26,7 @@ const (
// An error is returned if the buffer size is smaller than 240. // An error is returned if the buffer size is smaller than 240.
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < OptionsOffset { if len(buf) < OptionsOffset {
return Frame{}, errSmallFrame return Frame{}, lneto.ErrShortBuffer
} }
return Frame{buf: buf}, nil 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. // Parse DHCP options.
ptr := OptionsOffset ptr := OptionsOffset
if ptr > len(frm.buf) { if ptr > len(frm.buf) {
return errSmallFrame return lneto.ErrShortBuffer
} else if len(frm.buf[ptr:]) == 0 { } else if len(frm.buf[ptr:]) == 0 {
return errNoOptions return lneto.ErrInvalidField
} }
callback := fn != nil callback := fn != nil
for ptr+1 < len(frm.buf) { 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]) optlen := int(frm.buf[ptr+1])
if ptr+2+optlen > len(frm.buf) { if ptr+2+optlen > len(frm.buf) {
return errDHCPBadOption return lneto.ErrInvalidLengthField
} }
if callback { if callback {
optionData := frm.buf[ptr+2 : ptr+2+optlen] 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. // 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) { func (frm Frame) ValidateSize(vld *lneto.Validator) {
err := frm.ForEachOption(nil) // Does all necessary validation. err := frm.ForEachOption(nil) // Does all necessary validation.
if err != nil { if err != nil {
vld.AddError(errDHCPBadOption) vld.AddError(lneto.ErrInvalidLengthField)
} }
} }
+2
View File
@@ -10,6 +10,8 @@ import (
"github.com/soypat/lneto/internal" "github.com/soypat/lneto/internal"
) )
var errOptionNotFit = errors.New("DHCPv4: options dont fit")
type Server struct { type Server struct {
connID uint64 connID uint64
nextAddr netip.Addr nextAddr netip.Addr
+5 -4
View File
@@ -1,12 +1,12 @@
package dns package dns
import ( import (
"errors" "log/slog"
"fmt"
"math" "math"
"net" "net"
"github.com/soypat/lneto" "github.com/soypat/lneto"
"github.com/soypat/lneto/internal"
) )
type Client struct { 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 { func (c *Client) StartResolve(localPort, txid uint16, cfg ResolveConfig) error {
nd := len(cfg.Questions) nd := len(cfg.Questions)
if nd > math.MaxUint16 { if nd > math.MaxUint16 {
return errors.New("overflow uint16 in DNS questions") return lneto.ErrBufferFull
} }
c.reset(localPort, txid, dnsSendQuery, cfg.EnableRecursion) c.reset(localPort, txid, dnsSendQuery, cfg.EnableRecursion)
c.msg.LimitResourceDecoding(uint16(nd), uint16(nd), 0, 0) 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 { if err != nil {
return 0, err return 0, err
} else if len(data) > int(msglen) { } 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 c.state = dnsAwaitResponse
// Unset don't frag since DNS requests go through LOTS of nodes. // Unset don't frag since DNS requests go through LOTS of nodes.
+27 -23
View File
@@ -3,35 +3,39 @@ package dns
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"github.com/soypat/lneto"
) )
//go:generate stringer -type=Type,Class,RCode,OpCode -linecomment -output stringers.go . //go:generate stringer -type=Type,Class,RCode,OpCode -linecomment -output stringers.go .
// common errors. Taken from golang.org/x/net/dns/dnsmessage module. // common errors. Taken from golang.org/x/net/dns/dnsmessage module.
var ( var (
errNoResponse = errors.New("no DNS response") errNoResponse = errors.New("no DNS response")
errNameTooLong = errors.New("DNS name exceeds maximum length") errNameTooLong = errors.New("DNS name exceeds maximum length")
errNoNullTerm = errors.New("DNS name missing null terminator") errNoNullTerm = errors.New("DNS name missing null terminator")
errCalcLen = errors.New("DNS calculated name label length exceeds remaining buffer length") 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") errCantAddLabel = errors.New("long/empty/zterm/escape DNS label or not enough space")
errBaseLen = errors.New("DNS frame length too short") errBaseLen = lneto.ErrShortBuffer
errReserved = errors.New("segment prefix is reserved") errReserved = errors.New("segment prefix is reserved")
errTooManyPtr = errors.New("too many pointers (>10)") errTooManyPtr = errors.New("too many pointers (>10)")
errInvalidPtr = errors.New("invalid pointer") errInvalidPtr = errors.New("invalid pointer")
errInvalidName = errors.New("invalid dns name") errInvalidName = errors.New("invalid dns name")
errNilResouceBody = errors.New("nil resource body") errNilResouceBody = errors.New("nil resource body")
errResourceLen = errors.New("insufficient data for resource body length") errResourceLen = errors.New("insufficient data for resource body length")
errSegTooLong = errors.New("segment length too long") errSegTooLong = errors.New("segment length too long")
errZeroSegLen = errors.New("zero length segment") errZeroSegLen = errors.New("zero length segment")
errResTooLong = errors.New("resource length too long") errResTooLong = errors.New("resource length too long")
errTooManyQuestions = errors.New("too many Questions")
errTooManyAnswers = errors.New("too many Answers") errTooManyQuestions = lneto.ErrBufferFull
errTooManyAuthorities = errors.New("too many Authorities") errTooManyAnswers = lneto.ErrBufferFull
errTooManyAdditionals = errors.New("too many Additionals") errTooManyAuthorities = lneto.ErrBufferFull
errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)") errTooManyAdditionals = lneto.ErrBufferFull
errStringTooLong = errors.New("character string exceeds maximum length (255)")
errCompressedSRV = errors.New("compressed name in SRV resource data") errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)")
errEmptyDomainName = errors.New("empty domain name") 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 // Frame encapsulates the raw data of a DNS packet
+16 -21
View File
@@ -1,30 +1,25 @@
package lneto 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 type errGeneric uint8
// Generic errors common to internet functioning. // Generic errors common to internet functioning.
const ( const (
_ errGeneric = iota // non-initialized err _ errGeneric = iota // non-initialized err
ErrBug // lneto-bug(use build tag "debugheaplog") ErrBug // lneto-bug(use build tag "debugheaplog")
ErrPacketDrop // packet dropped ErrPacketDrop // packet dropped
ErrBadCRC // incorrect checksum ErrBadCRC // incorrect checksum
ErrZeroSource // zero source(port/addr) ErrZeroSource // zero source(port/addr)
ErrZeroDestination // zero destination(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 { func (err errGeneric) Error() string {
+3 -9
View File
@@ -2,7 +2,6 @@ package ethernet
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"github.com/soypat/lneto" "github.com/soypat/lneto"
) )
@@ -13,7 +12,7 @@ import (
// with payload/options of frames to avoid panics. // with payload/options of frames to avoid panics.
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < sizeHeaderNoVLAN { if len(buf) < sizeHeaderNoVLAN {
return Frame{buf: nil}, errShort return Frame{buf: nil}, lneto.ErrShortBuffer
} }
return Frame{buf: buf}, nil return Frame{buf: buf}, nil
} }
@@ -115,19 +114,14 @@ func (frm Frame) ClearHeader() {
// Validation API. // 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 // 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. // the frame. It returns a non-nil error on finding an inconsistency.
func (efrm Frame) ValidateSize(v *lneto.Validator) { func (efrm Frame) ValidateSize(v *lneto.Validator) {
sz := efrm.EtherTypeOrSize() sz := efrm.EtherTypeOrSize()
if sz.IsSize() && len(efrm.buf) < int(sz) { if sz.IsSize() && len(efrm.buf) < int(sz) {
v.AddError(errShort) v.AddError(lneto.ErrInvalidLengthField)
} }
if sz == TypeVLAN && len(efrm.buf) < 18 { 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 ( import (
"encoding/binary" "encoding/binary"
"errors"
)
var ( "github.com/soypat/lneto"
errUnsupportedIP = errors.New("unsupported IP version")
errInvalidIPVersionToSetAddr = errors.New("invalid ip version to setDstAddr")
) )
func GetIPAddr(buf []byte) (src, dst []byte, id, ipEndOff uint16, err error) { 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] dst = buf[24:40]
ipEndOff = 40 ipEndOff = 40
default: default:
err = errUnsupportedIP err = lneto.ErrUnsupported
} }
return src, dst, id, ipEndOff, err 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] srcaddr = buf[8:24]
dstaddr = buf[24:40] dstaddr = buf[24:40]
default: default:
return errUnsupportedIP return lneto.ErrUnsupported
} }
if src != nil && len(srcaddr) != len(src) { 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) { if dst != nil && len(dstaddr) != len(dst) {
return errors.New("mismatched length of ip dst addr") return lneto.ErrMismatchLen
} }
copy(srcaddr, src) copy(srcaddr, src)
copy(dstaddr, dst) copy(dstaddr, dst)
+10 -5
View File
@@ -6,11 +6,16 @@ import (
"io" "io"
"math" "math"
"unsafe" "unsafe"
"github.com/soypat/lneto"
) )
var ( var (
ErrRingBufferFull = errors.New("lneto/ring: buffer full") ErrRingBufferFull = lneto.ErrBufferFull
errRingNoData = errors.New("lneto/ring: empty write") 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. // 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]). // This method panics if amount of bytes is more than buffered (see [Ring.Buffered]).
func (r *Ring) ReadDiscard(n int) error { func (r *Ring) ReadDiscard(n int) error {
if n <= 0 { if n <= 0 {
return errors.New("invalid discard amount") return errInvalidDiscard
} }
buffered := r.Buffered() buffered := r.Buffered()
switch { switch {
case n > buffered: case n > buffered:
return errors.New("discard exceeds length") return errDiscardExceeds
case n == buffered: case n == buffered:
r.Reset() r.Reset()
case n+r.Off > len(r.Buf): 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. // 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) { func (r *Ring) ReadAt(p []byte, off64 int64) (int, error) {
if math.MaxInt != math.MaxInt64 && off64+int64(len(p)) > math.MaxInt32 { 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) off := int(off64)
if off+len(p) > r.Buffered() { if off+len(p) > r.Buffered() {
+1 -5
View File
@@ -93,7 +93,7 @@ func (h *handlers) prepAdd() error {
if h.full() { if h.full() {
h.compact() h.compact()
if h.full() { if h.full() {
return errNodesFull return lneto.ErrBufferFull
} }
} }
return nil return nil
@@ -200,11 +200,7 @@ func (h *handlers) encapsulateAny(buf []byte, offsetIP, offsetThisFrame int) (_
} }
var ( 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") errProtoRegistered = errors.New("protocol already registered")
errNodesFull = errors.New("no more room for new nodes")
_ = net.ErrClosed _ = net.ErrClosed
) )
+26 -25
View File
@@ -28,6 +28,9 @@ const unknownPayloadProto = "payload?"
var ( var (
ErrFieldByClassNotFound = errors.New("pcap: field by class not found") 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 { type PacketBreakdown struct {
@@ -61,13 +64,11 @@ func (pc *PacketBreakdown) initFrames() []Frame {
} }
func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) { func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:eth:start")
if dst == nil { if dst == nil {
dst = pc.initFrames() dst = pc.initFrames()
debuglog("pcap:eth:initframes")
} }
if bitOffset%8 != 0 { 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:]) efrm, err := ethernet.NewFrame(pkt[bitOffset/8:])
if err != nil { 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) { func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:arp:start") debuglog("pcap:arp:start")
if bitOffset%8 != 0 { 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:]) afrm, err := arp.NewFrame(pkt[bitOffset/8:])
if err != nil { 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) { func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:ipv6:start") debuglog("pcap:ipv6:start")
if bitOffset%8 != 0 { 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:]) ifrm6, err := ipv6.NewFrame(pkt[bitOffset/8:])
if err != nil { 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) { func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:ipv4:start") debuglog("pcap:ipv4:start")
if bitOffset%8 != 0 { 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:]) ifrm4, err := ipv4.NewFrame(pkt[bitOffset/8:])
if err != nil { 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) { func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:tcp:start") debuglog("pcap:tcp:start")
if bitOffset%8 != 0 { 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:]) tfrm, err := tcp.NewFrame(pkt[bitOffset/8:])
if err != nil { 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) { func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:udp:start") debuglog("pcap:udp:start")
if bitOffset%8 != 0 { 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:]) ufrm, err := udp.NewFrame(pkt[bitOffset/8:])
if err != nil { 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) { func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
debuglog("pcap:icmp:start") debuglog("pcap:icmp:start")
if bitOffset%8 != 0 { if bitOffset%8 != 0 {
return dst, errors.New("ICMPv4 must be parsed at byte boundary") return dst, errNotByteAligned
} }
icmpData := pkt[bitOffset/8:] icmpData := pkt[bitOffset/8:]
ifrm, err := icmpv4.NewFrame(icmpData) 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) { func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 { if bitOffset%8 != 0 {
return dst, errors.New("DNS must be parsed at byte boundary") return dst, errNotByteAligned
} }
dnsData := pkt[bitOffset/8:] dnsData := pkt[bitOffset/8:]
pc.dmsg.LimitResourceDecoding(20, 20, 20, 20) 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) finfo := reclaimFrame(&dst, "DNS", bitOffset, nil)
if incomplete { 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", Name: "Data",
FrameBitOffset: 0, FrameBitOffset: 0,
BitLength: int(off) * octet, 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) { func (pc *PacketBreakdown) CaptureNTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 { if bitOffset%8 != 0 {
return dst, errors.New("NTP must be parsed at byte boundary") return dst, errNotByteAligned
} }
ntpData := pkt[bitOffset/8:] ntpData := pkt[bitOffset/8:]
_, err := ntp.NewFrame(ntpData) _, 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) { func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 { if bitOffset%8 != 0 {
return dst, errors.New("DHCP must be parsed at byte boundary") return dst, errNotByteAligned
} }
dhcpData := pkt[bitOffset/8:] dhcpData := pkt[bitOffset/8:]
dfrm, err := dhcpv4.NewFrame(dhcpData) 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[:]) finfo := reclaimFrame(&dst, "DHCPv4", bitOffset, baseDHCPv4Fields[:])
magic := dfrm.MagicCookie() magic := dfrm.MagicCookie()
if magic != dhcpv4.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() 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 { err = dfrm.ForEachOption(func(optoff int, opt dhcpv4.OptNum, data []byte) error {
if len(optfield.SubFields) >= pc.SubfieldLimit { 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. // 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} 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") debuglog("pcap:http:start")
const httpProtocol = "HTTP" const httpProtocol = "HTTP"
if bitOffset%8 != 0 { if bitOffset%8 != 0 {
return dst, errors.New("HTTP must be parsed at byte boundary") return dst, errNotByteAligned
} }
const asResponse = true const asResponse = true
const asRequest = false 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 field.Name == "" { // Prioritize "canonical" fields with no name.
if selected >= 0 && frm.Fields[selected].Name == "" { if selected >= 0 && frm.Fields[selected].Name == "" {
return -1, errors.New("multiple class fields with no name") return -1, lneto.ErrMismatch
} }
selected = i selected = i
} else if selected >= 0 { } else if selected >= 0 {
@@ -681,7 +682,7 @@ func (frm Frame) FieldByClass(c FieldClass) (int, error) {
return -1, ErrFieldByClassNotFound return -1, ErrFieldByClassNotFound
} }
if multiple && frm.Fields[selected].Name != "" { 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 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) { func (frm Frame) FieldAsUint(fieldIdx int, pkt []byte) (uint64, error) {
const badUint64 = math.MaxUint64 const badUint64 = math.MaxUint64
if fieldIdx < 0 || fieldIdx >= len(frm.Fields) { if fieldIdx < 0 || fieldIdx >= len(frm.Fields) {
return badUint64, errors.New("invalid field index") return badUint64, errInvalidFieldIdx
} }
field := frm.Fields[fieldIdx] field := frm.Fields[fieldIdx]
return fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.Flags.IsRightAligned()) 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. // 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) { func (frm Frame) AppendField(dst []byte, fieldIdx int, pkt []byte) ([]byte, error) {
if fieldIdx < 0 || fieldIdx >= len(frm.Fields) { if fieldIdx < 0 || fieldIdx >= len(frm.Fields) {
return dst, errors.New("invalid field index") return dst, errInvalidFieldIdx
} }
field := frm.Fields[fieldIdx] field := frm.Fields[fieldIdx]
return appendField(dst, pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.Flags.IsRightAligned()) 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 const badUint64 = math.MaxUint64
octets := (bitlen + 7) / 8 octets := (bitlen + 7) / 8
if octets > 8 { if octets > 8 {
return badUint64, errors.New("field too long to be represented by uint64") return badUint64, lneto.ErrUnsupported
} }
var buf [8]byte var buf [8]byte
_, err := appendField(buf[8-octets:8-octets], pkt, fieldBitStart, bitlen, rightAligned) _, 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. octets := (bitlen + 7) / 8 // total octets needed to represent field.
octetsStart := fieldBitStart / 8 octetsStart := fieldBitStart / 8
if octets+octetsStart > len(pkt) { if octets+octetsStart > len(pkt) {
return dst, errors.New("buffer overflow") return dst, lneto.ErrShortBuffer
} }
firstBitOffset := fieldBitStart % 8 firstBitOffset := fieldBitStart % 8
lastOctetExcessBits := fieldBitEnd % 8 lastOctetExcessBits := fieldBitEnd % 8
if firstBitOffset == 0 { if firstBitOffset == 0 {
if rightAligned { 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. // Optimized path: field starts at byte boundary.
dst = append(dst, pkt[octetsStart:octetsStart+octets]...) 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. // Right aligned with trailing bits. i.e: IPv6 Traffic Class.
// Field spans an extra byte, so need octets+1 bytes from packet. // Field spans an extra byte, so need octets+1 bytes from packet.
if octets+octetsStart+1 > len(pkt) { if octets+octetsStart+1 > len(pkt) {
return dst, errors.New("buffer overflow") return dst, lneto.ErrShortBuffer
} }
for i := 0; i < octets; i++ { for i := 0; i < octets; i++ {
b := (pkt[octetsStart+i] & mask) << (8 - firstBitOffset) b := (pkt[octetsStart+i] & mask) << (8 - firstBitOffset)
+3 -3
View File
@@ -3,7 +3,6 @@ package pcap
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"errors"
"math" "math"
"net/netip" "net/netip"
"slices" "slices"
@@ -13,6 +12,7 @@ import (
_ "time" _ "time"
"unsafe" "unsafe"
"github.com/soypat/lneto"
"github.com/soypat/lneto/ethernet" "github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/ntp" "github.com/soypat/lneto/ntp"
"github.com/soypat/lneto/tcp" "github.com/soypat/lneto/tcp"
@@ -162,7 +162,7 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
// inspired by [time.RFC3339] // inspired by [time.RFC3339]
const littlerfc3339 = "2006-01-02T15:04:05.9999" const littlerfc3339 = "2006-01-02T15:04:05.9999"
if len(f.buf) != 8 { if len(f.buf) != 8 {
return dst, errors.New("only timestamp8 supported") return dst, lneto.ErrUnsupported
} }
ts := ntp.TimestampFromUint64(binary.BigEndian.Uint64(f.buf)) ts := ntp.TimestampFromUint64(binary.BigEndian.Uint64(f.buf))
dst = ts.Time().AppendFormat(dst, littlerfc3339) dst = ts.Time().AppendFormat(dst, littlerfc3339)
@@ -223,7 +223,7 @@ func (f *Formatter) fieldAsUint(pkt []byte, fieldBitStart, bitlen int, rightAlig
const badUint64 = math.MaxUint64 const badUint64 = math.MaxUint64
octets := (bitlen + 7) / 8 octets := (bitlen + 7) / 8
if octets > 8 { if octets > 8 {
return badUint64, errors.New("field too long to be represented by uint64") return badUint64, lneto.ErrUnsupported
} }
f.uintBuf = [8]byte{} f.uintBuf = [8]byte{}
_, err := appendField(f.uintBuf[8-octets:8-octets], pkt, fieldBitStart, bitlen, rightAligned) _, err := appendField(f.uintBuf[8-octets:8-octets], pkt, fieldBitStart, bitlen, rightAligned)
+4 -5
View File
@@ -2,7 +2,6 @@ package internet
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"io" "io"
"log/slog" "log/slog"
"math" "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. // The connection ID is incremented on each call to invalidate existing connections.
func (ls *StackEthernet) Configure(cfg StackEthernetConfig) error { func (ls *StackEthernet) Configure(cfg StackEthernetConfig) error {
if cfg.MTU > (math.MaxUint16-ethernet.MaxOverheadSize) || cfg.MTU < 256 { if cfg.MTU > (math.MaxUint16-ethernet.MaxOverheadSize) || cfg.MTU < 256 {
return errors.New("invalid MTU") return lneto.ErrInvalidConfig
} else if cfg.MaxNodes <= 0 { } else if cfg.MaxNodes <= 0 {
return errZeroMaxNodesArg return lneto.ErrInvalidConfig
} else if cfg.AppendCRC32 && cfg.CRC32Update == nil { } 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.handlers.reset("StackEthernet", cfg.MaxNodes)
*ls = StackEthernet{ *ls = StackEthernet{
@@ -107,7 +106,7 @@ func (ls *StackEthernet) Protocol() uint64 { return 1 }
func (ls *StackEthernet) Register(h StackNode) error { func (ls *StackEthernet) Register(h StackNode) error {
proto := h.Protocol() proto := h.Protocol()
if proto > math.MaxUint16 || proto <= 1500 { if proto > math.MaxUint16 || proto <= 1500 {
return errInvalidProto return lneto.ErrInvalidConfig
} }
return ls.handlers.registerByProto(nodeFromStackNode(h, 0, proto, nil)) return ls.handlers.registerByProto(nodeFromStackNode(h, 0, proto, nil))
} }
+5 -6
View File
@@ -1,7 +1,6 @@
package internet package internet
import ( import (
"errors"
"io" "io"
"log/slog" "log/slog"
"net/netip" "net/netip"
@@ -26,7 +25,7 @@ type StackIP struct {
func (sb *StackIP) Reset(addr netip.Addr, maxNodes int) error { func (sb *StackIP) Reset(addr netip.Addr, maxNodes int) error {
if maxNodes <= 0 { if maxNodes <= 0 {
return errZeroMaxNodesArg return lneto.ErrInvalidConfig
} }
err := sb.SetAddr(addr) err := sb.SetAddr(addr)
if err != nil { 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 { func (sb *StackIP) SetAddr(addr netip.Addr) error {
if !addr.IsValid() { if !addr.IsValid() {
return errors.New("invalid IP") return lneto.ErrInvalidAddr
} else if !addr.Is4() { } else if !addr.Is4() {
return errors.New("require IPv4") return lneto.ErrUnsupported
} }
sb.ip = addr.As4() sb.ip = addr.As4()
return nil return nil
@@ -200,7 +199,7 @@ func (sb *StackIP) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int
func (sb *StackIP) Register(h StackNode) error { func (sb *StackIP) Register(h StackNode) error {
proto := h.Protocol() proto := h.Protocol()
if proto > 255 { if proto > 255 {
return errInvalidProto return lneto.ErrInvalidConfig
} }
return sb.handlers.registerByPortProto(nodeFromStackNode(h, h.LocalPort(), proto, nil)) 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 { func (sb *StackIP) recvicmp(icmpData []byte) error {
var crc lneto.CRC791 var crc lneto.CRC791
if crc.PayloadSum16(icmpData) != 0 { if crc.PayloadSum16(icmpData) != 0 {
return errors.New("ICMP CRC mismatch") return lneto.ErrBadCRC
} }
return nil return nil
} }
+7 -8
View File
@@ -2,7 +2,6 @@ package internet
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"io" "io"
"log/slog" "log/slog"
"math" "math"
@@ -33,9 +32,9 @@ func (ps *StackPorts) ResetTCP(maxNodes int) error {
func (ps *StackPorts) Reset(protocol uint64, dstPortOffset uint16, maxNodes int) error { func (ps *StackPorts) Reset(protocol uint64, dstPortOffset uint16, maxNodes int) error {
if protocol > math.MaxUint16 { if protocol > math.MaxUint16 {
return errInvalidProto return lneto.ErrInvalidConfig
} else if maxNodes <= 0 { } else if maxNodes <= 0 {
return errZeroMaxNodesArg return lneto.ErrInvalidConfig
} }
ps.handlers.reset("StackPorts(proto="+strconv.Itoa(int(protocol))+")", maxNodes) ps.handlers.reset("StackPorts(proto="+strconv.Itoa(int(protocol))+")", maxNodes)
*ps = StackPorts{ *ps = StackPorts{
@@ -92,9 +91,9 @@ func (ps *StackPorts) Register(h StackNode) error {
port := h.LocalPort() port := h.LocalPort()
proto := h.Protocol() proto := h.Protocol()
if port <= 0 { if port <= 0 {
return errZeroPort return lneto.ErrZeroSource
} else if proto != uint64(ps.protocol) { } else if proto != uint64(ps.protocol) {
return errInvalidProto return lneto.ErrInvalidConfig
} }
return ps.handlers.registerByPortProto(nodeFromStackNode(h, port, proto, nil)) 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() port := h.LocalPort()
proto := h.Protocol() proto := h.Protocol()
if port <= 0 { if port <= 0 {
return errZeroPort return lneto.ErrZeroSource
} else if proto != uint64(mfsp.sp.protocol) { } else if proto != uint64(mfsp.sp.protocol) {
return errInvalidProto return lneto.ErrInvalidConfig
} else if addr != nil && len(addr) != 6 { } 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)) return mfsp.sp.handlers.registerByPortProto(nodeFromStackNode(h, port, proto, addr))
} }
+6 -15
View File
@@ -2,7 +2,6 @@ package ipv4
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"net/netip" "net/netip"
@@ -15,7 +14,7 @@ import (
// with payload/options of frames to avoid panics. // with payload/options of frames to avoid panics.
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < sizeHeader { if len(buf) < sizeHeader {
return Frame{buf: nil}, errors.New("ipv4: short buffer") return Frame{buf: nil}, lneto.ErrShortBuffer
} }
return Frame{buf: buf}, nil return Frame{buf: buf}, nil
} }
@@ -191,27 +190,19 @@ func (ifrm Frame) ClearHeader() {
// Validation API. // 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 // 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. // the frame. It returns a non-nil error on finding an inconsistency.
func (ifrm Frame) ValidateSize(v *lneto.Validator) { func (ifrm Frame) ValidateSize(v *lneto.Validator) {
ihl := ifrm.ihl() ihl := ifrm.ihl()
tl := ifrm.TotalLength() tl := ifrm.TotalLength()
if tl < sizeHeader { if tl < sizeHeader {
v.AddError(errBadTL) v.AddError(lneto.ErrInvalidLengthField)
} }
if int(tl) > len(ifrm.RawData()) { if int(tl) > len(ifrm.RawData()) {
v.AddError(errShort) v.AddError(lneto.ErrShortBuffer)
} }
if ihl < 5 || uint16(ihl)*4 > tl { 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) ifrm.ValidateSize(v)
flags := ifrm.Flags() flags := ifrm.Flags()
if ifrm.version() != 4 { if ifrm.version() != 4 {
v.AddError(errBadVersion) v.AddError(lneto.ErrInvalidField)
} }
if v.Flags()&lneto.ValidateEvilBit != 0 && flags.IsEvil() { 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 ( import (
"encoding/binary" "encoding/binary"
"errors"
"github.com/soypat/lneto"
) )
type Type uint8 type Type uint8
@@ -52,13 +53,9 @@ const (
CodeRedirectToSAndHost // redirect for ToS+host CodeRedirectToSAndHost // redirect for ToS+host
) )
var (
errShortFrame = errors.New("icmpv4: short frame")
)
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < 8 { if len(buf) < 8 {
return Frame{}, errShortFrame return Frame{}, lneto.ErrShortBuffer
} }
return Frame{buf: buf}, nil return Frame{buf: buf}, nil
} }
+2 -7
View File
@@ -2,7 +2,6 @@ package ipv6
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"github.com/soypat/lneto" "github.com/soypat/lneto"
) )
@@ -13,7 +12,7 @@ import (
// with payload/options of frames to avoid panics. // with payload/options of frames to avoid panics.
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < sizeHeader { if len(buf) < sizeHeader {
return Frame{buf: nil}, errShortBuf return Frame{buf: nil}, lneto.ErrShortBuffer
} }
return Frame{buf: buf}, nil return Frame{buf: buf}, nil
} }
@@ -119,16 +118,12 @@ func (i6frm Frame) ClearHeader() {
// Validate API. // 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 // 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. // the frame. It returns a non-nil error on finding an inconsistency.
func (i6frm Frame) ValidateSize(v *lneto.Validator) { func (i6frm Frame) ValidateSize(v *lneto.Validator) {
tl := i6frm.PayloadLength() tl := i6frm.PayloadLength()
if int(tl)+sizeHeader > len(i6frm.RawData()) { if int(tl)+sizeHeader > len(i6frm.RawData()) {
v.AddError(errShortFrame) v.AddError(lneto.ErrInvalidLengthField)
} }
} }
+3 -2
View File
@@ -1,8 +1,9 @@
package ntp package ntp
import ( import (
"errors"
"time" "time"
"github.com/soypat/lneto"
) )
type state uint8 type state uint8
@@ -100,7 +101,7 @@ func (c *Client) Demux(carrierData []byte, frameOffset int) error {
xmt := frm.TransmitTime() xmt := frm.TransmitTime()
orig := frm.OriginTime() orig := frm.OriginTime()
if xmt == orig || orig != c.t[0] { if xmt == orig || orig != c.t[0] {
return errors.New("bogus NTP packet") return lneto.ErrPacketDrop
} }
txelapsed := c.now().Sub(c.start) txelapsed := c.now().Sub(c.start)
+6 -5
View File
@@ -3,11 +3,12 @@ package ntp
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"math" "math"
"math/bits" "math/bits"
"sync" "sync"
"time" "time"
"github.com/soypat/lneto"
) )
// NTP Global Parameters. // NTP Global Parameters.
@@ -26,7 +27,7 @@ const (
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < SizeHeader { 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 return Frame{buf: buf}, nil
} }
@@ -189,12 +190,12 @@ func TimestampFromUint64(ts uint64) Timestamp {
func TimestampFromTime(t time.Time) (Timestamp, error) { func TimestampFromTime(t time.Time) (Timestamp, error) {
t = t.UTC() t = t.UTC()
if t.Before(baseTime) { if t.Before(baseTime) {
return Timestamp{}, errors.New("ntp.TimestampFromTime: time is before baseTime") return Timestamp{}, lneto.ErrUnsupported
} }
off := t.Sub(baseTime) off := t.Sub(baseTime)
sec := uint64(off / time.Second) sec := uint64(off / time.Second)
if sec > math.MaxUint32 { 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) fra := uint64(off%time.Second) * math.MaxUint32 / uint64(time.Second)
return Timestamp{ return Timestamp{
@@ -254,7 +255,7 @@ func (d Date) Time() (time.Time, error) {
} }
hi, seclo := bits.Mul64(uint64(sec), uint64(time.Second)) hi, seclo := bits.Mul64(uint64(sec), uint64(time.Second))
if hi != 0 || seclo > math.MaxInt64-uint64(time.Second)-1 { 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.Duration(seclo)
off += time.Second * time.Duration(d.frac>>32) / math.MaxUint32 off += time.Second * time.Duration(d.frac>>32) / math.MaxUint32
+6 -6
View File
@@ -9,6 +9,8 @@ package phy
import ( import (
"errors" "errors"
"time" "time"
"github.com/soypat/lneto"
) )
// MDIOBus is a HAL for MDIO bus access supporting both Clause 22 and Clause 45 devices. // 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 maxAddr = 31
const regBasicStatus = 0x01 const regBasicStatus = 0x01
if len(dst) < 32 { if len(dst) < 32 {
return -1, errors.New("require buffer length 32 for FindPHYs") return -1, lneto.ErrShortBuffer
} }
n = 0 n = 0
for addr := uint8(0); addr <= maxAddr; addr++ { for addr := uint8(0); addr <= maxAddr; addr++ {
@@ -56,9 +58,7 @@ func FindClause22PHYs(mdio MDIOBus, dst []uint8) (n int, err error) {
return n, err return n, err
} }
var ( var errInvalidPhyAddr error = lneto.ErrInvalidAddr
errInvalidPhyAddr = errors.New("invalid phy addr")
)
type Device struct { type Device struct {
mdio MDIOBus mdio MDIOBus
@@ -73,7 +73,7 @@ func (phy *Device) ConfigureAs22(mdio MDIOBus, phyAddr uint8) error {
return errInvalidPhyAddr return errInvalidPhyAddr
} else if mdio == nil { } else if mdio == nil {
return errors.New("nil mdio bus") return lneto.ErrInvalidConfig
} }
phy.mdio = mdio phy.mdio = mdio
phy.phyaddr = phyAddr phy.phyaddr = phyAddr
@@ -176,7 +176,7 @@ func (phy *Device) SetupForced(mode LinkMode) error {
case 10: case 10:
// No speed bits = 10Mbps // No speed bits = 10Mbps
default: default:
return errors.New("unsupported forced link mode") return lneto.ErrUnsupported
} }
if mode.IsFullDuplex() { if mode.IsFullDuplex() {
ctl |= BMCRFullDuplex ctl |= BMCRFullDuplex
+12 -2
View File
@@ -199,11 +199,21 @@ func _() {
_ = x[ErrBadCRC-3] _ = x[ErrBadCRC-3]
_ = x[ErrZeroSource-4] _ = x[ErrZeroSource-4]
_ = x[ErrZeroDestination-5] _ = 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 { func (i errGeneric) String() string {
i -= 1 i -= 1
+6 -10
View File
@@ -15,12 +15,8 @@ import (
) )
var ( var (
errDeadlineExceeded = os.ErrDeadlineExceeded errDeadlineExceeded = os.ErrDeadlineExceeded
errNoRemoteAddr = errors.New("tcp: no remote address established") 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")
) )
// Conn builds on the [Handler] abstraction and adds IP header knowledge, time management, and familiar user facing API // 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() conn.mu.Lock()
defer conn.mu.Unlock() defer conn.mu.Unlock()
if !remote.IsValid() { if !remote.IsValid() {
return errInvalidIP return lneto.ErrInvalidAddr
} }
rport := remote.Port() rport := remote.Port()
err := conn.h.OpenActive(localPort, rport, iss) err := conn.h.OpenActive(localPort, rport, iss)
@@ -330,14 +326,14 @@ func (conn *Conn) Demux(buf []byte, off int) (err error) {
conn.mu.Lock() conn.mu.Lock()
defer conn.mu.Unlock() defer conn.mu.Unlock()
if off >= len(buf) { if off >= len(buf) {
return errBadDemuxOffset return lneto.ErrShortBuffer
} }
raddr, _, id, _, err := internal.GetIPAddr(buf[:off]) raddr, _, id, _, err := internal.GetIPAddr(buf[:off])
if err != nil { if err != nil {
return err return err
} }
if conn.isRaddrSet() && !internal.BytesEqual(conn.remoteAddr, raddr) { 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))) conn.trace("tcpconn.Recv", slog.Uint64("lport", uint64(conn.h.LocalPort())), slog.Uint64("rport", uint64(conn.h.remotePort)))
err = conn.h.Recv(buf[off:]) err = conn.h.Recv(buf[off:])
@@ -365,7 +361,7 @@ func (conn *Conn) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int)
if err != nil { if err != nil {
return 0, err return 0, err
} else if len(raddr) != len(conn.remoteAddr) { } else if len(raddr) != len(conn.remoteAddr) {
return 0, errMismatchedIPVersion return 0, lneto.ErrMismatchLen
} }
n, err = conn.h.Send(carrierData[offsetToFrame:]) n, err = conn.h.Send(carrierData[offsetToFrame:])
if err != nil || n == 0 { if err != nil || n == 0 {
+12 -12
View File
@@ -6,24 +6,24 @@ import (
"math/bits" "math/bits"
"strconv" "strconv"
"unsafe" "unsafe"
"github.com/soypat/lneto"
) )
//go:generate stringer -type=State,OptionKind -linecomment -output stringers.go . //go:generate stringer -type=State,OptionKind -linecomment -output stringers.go .
var ( var (
// errDropSegment is a flag that signals to drop a segment silently. errDropSegment error = lneto.ErrPacketDrop
errDropSegment = errors.New("drop segment") errWindowTooLarge = errors.New("invalid window size > 2**16")
errWindowTooLarge = errors.New("invalid window size > 2**16")
errBufferTooSmall = errors.New("tcp buffer too small") errBufferTooSmall error = lneto.ErrShortBuffer
errNeedClosedTCBToOpen = errors.New("need closed TCB to call open") errNeedClosedTCBToOpen = errors.New("need closed TCB to call open")
errInvalidState = errors.New("invalid state") errInvalidState = errors.New("invalid state")
errConnNotExist = errors.New("connection does not exist") errConnNotExist = errors.New("connection does not exist")
errConnectionClosing = errors.New("connection closing") errConnectionClosing = errors.New("connection closing")
errExpectedSYN = errors.New("seqs:expected SYN") errExpectedSYN = errors.New("seqs:expected SYN")
errBadSegack = errors.New("seqs:bad segack") errBadSegack = errors.New("seqs:bad segack")
errFinwaitExpectedACK = errors.New("seqs:finwait1 expected ACK") errFinwaitExpectedACK = errors.New("seqs:finwait1 expected ACK")
errFinwaitExpectedFinack = errors.New("seqs:finwait2 expected FINACK")
errWindowOverflow = newRejectErr("wnd > 2**16") errWindowOverflow = newRejectErr("wnd > 2**16")
errSeqNotInWindow = newRejectErr("seq not in snd/rcv.wnd") errSeqNotInWindow = newRejectErr("seq not in snd/rcv.wnd")
+5 -13
View File
@@ -2,7 +2,6 @@ package tcp
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"math" "math"
@@ -19,7 +18,7 @@ const (
// with payload/options of frames to avoid panics. // with payload/options of frames to avoid panics.
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < sizeHeaderTCP { 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 return Frame{buf: buf}, nil
} }
@@ -178,13 +177,6 @@ func (tfrm Frame) String() string {
// Validation API // 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) { // func (tfrm Frame) Validate(v *lneto.Validator) {
// tfrm.ValidateSize(v) // tfrm.ValidateSize(v)
@@ -196,19 +188,19 @@ var (
func (tfrm Frame) ValidateSize(v *lneto.Validator) { func (tfrm Frame) ValidateSize(v *lneto.Validator) {
off := tfrm.HeaderLength() off := tfrm.HeaderLength()
if off < sizeHeaderTCP { if off < sizeHeaderTCP {
v.AddBitPosErr(12*8, 4, errBadTCPOff) v.AddBitPosErr(12*8, 4, lneto.ErrInvalidLengthField)
} }
if off > len(tfrm.RawData()) { 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) { func (tfrm Frame) ValidateExceptCRC(v *lneto.Validator) {
tfrm.ValidateSize(v) tfrm.ValidateSize(v)
if tfrm.DestinationPort() == 0 { if tfrm.DestinationPort() == 0 {
v.AddBitPosErr(2*8, 16, errZeroDstPort) v.AddBitPosErr(2*8, 16, lneto.ErrZeroDestination)
} }
if tfrm.SourcePort() == 0 { 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 package tcp
import ( import (
"errors"
"io" "io"
"net" "net"
@@ -11,11 +10,6 @@ import (
"github.com/soypat/lneto/internal" "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 // Handler is a low level TCP handling data structure. It implements logic
// related to data buffering, frame sequencing and connection state handling. // 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. // 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. // 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 { func (h *Handler) SetBuffers(txbuf, rxbuf []byte, packets int) error {
if h.bufRx.Buf == nil && (len(rxbuf) < minBufferSize || len(txbuf) < minBufferSize) { 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() { if !h.scb.State().IsClosed() {
return errors.New("tcp.Handler must be closed before setting buffers") return lneto.ErrInvalidConfig
} }
if rxbuf != nil { if rxbuf != nil {
h.bufRx.Buf = rxbuf h.bufRx.Buf = rxbuf
@@ -156,15 +150,15 @@ func (h *Handler) Recv(incomingPacket []byte) error {
remotePort := tfrm.SourcePort() remotePort := tfrm.SourcePort()
if h.remotePort != 0 && remotePort != h.remotePort { if h.remotePort != 0 && remotePort != h.remotePort {
return errMismatchedSrcPort return lneto.ErrMismatch
} }
dstPort := tfrm.DestinationPort() dstPort := tfrm.DestinationPort()
if h.localPort != dstPort { if h.localPort != dstPort {
return errMismatchedDstPort return lneto.ErrMismatch
} }
payload := tfrm.Payload() payload := tfrm.Payload()
if len(payload) > h.bufRx.Free() { if len(payload) > h.bufRx.Free() {
return errors.New("rx buffer full") return lneto.ErrBufferFull
} }
segIncoming := tfrm.Segment(len(payload)) segIncoming := tfrm.Segment(len(payload))
if h.scb.IncomingIsKeepalive(segIncoming) { if h.scb.IncomingIsKeepalive(segIncoming) {
+5 -6
View File
@@ -1,7 +1,6 @@
package tcp package tcp
import ( import (
"errors"
"log/slog" "log/slog"
"net" "net"
"sync" "sync"
@@ -70,7 +69,7 @@ func (listener *Listener) Close() error {
listener.mu.Lock() listener.mu.Lock()
defer listener.mu.Unlock() defer listener.mu.Unlock()
if listener.isClosed() { if listener.isClosed() {
return errors.New("already closed") return net.ErrClosed
} }
listener.debug("listener:reset", slog.Uint64("port", uint64(listener.port))) listener.debug("listener:reset", slog.Uint64("port", uint64(listener.port)))
listener.connID++ listener.connID++
@@ -80,9 +79,9 @@ func (listener *Listener) Close() error {
func (listener *Listener) Reset(port uint16, pool pool) error { func (listener *Listener) Reset(port uint16, pool pool) error {
if port == 0 { if port == 0 {
return errZeroDstPort return lneto.ErrZeroSource
} else if pool == nil { } else if pool == nil {
return errors.New("nil TCP pool") return lneto.ErrInvalidConfig
} }
listener.mu.Lock() listener.mu.Lock()
defer listener.mu.Unlock() defer listener.mu.Unlock()
@@ -126,7 +125,7 @@ func (listener *Listener) TryAccept() (*Conn, any, error) {
listener.incoming[i] = handler{} // discard from ready. listener.incoming[i] = handler{} // discard from ready.
return conn, userData, nil return conn, userData, nil
} }
return nil, nil, errors.New("no conns available") return nil, nil, lneto.ErrExhausted
} }
// Encapsulate implements [StackNode]. // Encapsulate implements [StackNode].
@@ -199,7 +198,7 @@ func (listener *Listener) Demux(carrierData []byte, tcpFrameOffset int) error {
} }
dst := tfrm.DestinationPort() dst := tfrm.DestinationPort()
if dst != listener.port { if dst != listener.port {
return errors.New("not our port") return lneto.ErrMismatch
} }
src := tfrm.SourcePort() src := tfrm.SourcePort()
+8 -8
View File
@@ -1,9 +1,9 @@
package tcp package tcp
import ( import (
"errors"
"fmt"
"strings" "strings"
"github.com/soypat/lneto"
) )
type OptionKind uint8 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) { func (op OptionCodec) PutOption(dst []byte, kind OptionKind, data ...byte) (int, error) {
putSize := 2 + len(data) putSize := 2 + len(data)
if len(dst) < putSize { if len(dst) < putSize {
return -1, errBufferTooSmall return -1, lneto.ErrShortBuffer
} else if putSize > 255 { } else if putSize > 255 {
return -1, errors.New("option data too large") return -1, lneto.ErrInvalidLengthField
} else if kind == OptNop || kind == OptEnd { } 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[0] = byte(kind)
dst[1] = byte(putSize) dst[1] = byte(putSize)
@@ -111,13 +111,13 @@ func (op OptionCodec) ForEachOption(opts []byte, fn func(OptionKind, []byte) err
continue continue
} }
if len(opts[off:]) < 1 { 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. size := int(opts[off]) // Total option length including kind and length bytes.
off++ off++
dataLen := size - 2 // Data bytes after kind and length. dataLen := size - 2 // Data bytes after kind and length.
if dataLen < 0 || len(opts[off:]) < dataLen { 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 { if !skipSizeValidation {
@@ -133,7 +133,7 @@ func (op OptionCodec) ForEachOption(opts []byte, fn func(OptionKind, []byte) err
expectSize = 2 expectSize = 2
} }
if expectSize != -1 && size != expectSize { 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()) { if !(skipObsolete && kind.IsObsolete()) {
+4 -5
View File
@@ -2,8 +2,9 @@ package tcp
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"io" "io"
"github.com/soypat/lneto"
) )
// Embed low 5 bits of counter into cookie for efficient validation. // Embed low 5 bits of counter into cookie for efficient validation.
@@ -45,15 +46,13 @@ type SYNCookieConfig struct {
MaxCounterDelta uint32 MaxCounterDelta uint32
} }
var ( var errInvalidCookie error = lneto.ErrMismatch
errInvalidCookie = errors.New("tcp: invalid SYN cookie")
)
// Reset initializes or reinitializes the SYNCookie with the given configuration. // Reset initializes or reinitializes the SYNCookie with the given configuration.
// The counter is preserved across resets to maintain cookie validity during secret rotation. // The counter is preserved across resets to maintain cookie validity during secret rotation.
func (sc *SYNCookieJar) Reset(config SYNCookieConfig) error { func (sc *SYNCookieJar) Reset(config SYNCookieConfig) error {
if config.Rand == nil { if config.Rand == nil {
return errors.New("need rand function") return lneto.ErrInvalidConfig
} }
_, err := io.ReadFull(config.Rand, sc.secret[:]) _, err := io.ReadFull(config.Rand, sc.secret[:])
if err != nil { if err != nil {
+9 -16
View File
@@ -1,20 +1,12 @@
package tcp package tcp
import ( import (
"errors" "log/slog"
"github.com/soypat/lneto"
"github.com/soypat/lneto/internal" "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 ( const (
// this must be at least 2 for buffer to work. // this must be at least 2 for buffer to work.
minBufferSize = 2 minBufferSize = 2
@@ -60,9 +52,9 @@ type ringidx struct {
func (rtx *ringTx) Reset(buf []byte, maxqueuedPackets int, iss Value) error { func (rtx *ringTx) Reset(buf []byte, maxqueuedPackets int, iss Value) error {
buf = buf[:len(buf):len(buf)] // safely omit capacity section. buf = buf[:len(buf):len(buf)] // safely omit capacity section.
if maxqueuedPackets <= 0 { if maxqueuedPackets <= 0 {
return errQueuedPacketsLEZ return lneto.ErrInvalidConfig
} else if len(buf) < minBufferSize || len(buf) < maxqueuedPackets { } else if len(buf) < minBufferSize || len(buf) < maxqueuedPackets {
return errInvalidBufSize return lneto.ErrShortBuffer
} }
*rtx = ringTx{ *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) { func (rtx *ringTx) MakePacket(b []byte, currentSeq Value) (int, error) {
free := rtx.slist.Free() free := rtx.slist.Free()
if free == 0 { if free == 0 {
return 0, errPacketQueueFull return 0, lneto.ErrBufferFull
} }
endSeq, ok := rtx.sentEndSeq() endSeq, ok := rtx.sentEndSeq()
if ok && currentSeq.LessThan(endSeq) { 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". // Reading unsent ring consumes unsent and converts it to "sent".
unsent, _ := rtx.unsentRing() 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 { func (sl *sentlist) RecvAck(ack Value, bufsize int) error {
newest := sl.Newest() newest := sl.Newest()
if newest == nil { if newest == nil {
return errNoPacketToAck return lneto.ErrPacketDrop
} }
endseq := newest.endSeq() endseq := newest.endSeq()
if endseq.LessThan(ack) { if endseq.LessThan(ack) {
return errAckUnsent return lneto.ErrPacketDrop
} }
// Mark fully acked. // Mark fully acked.
for i := 0; i < len(sl.pkts); i++ { for i := 0; i < len(sl.pkts); i++ {
+3 -9
View File
@@ -2,7 +2,6 @@ package udp
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"github.com/soypat/lneto" "github.com/soypat/lneto"
) )
@@ -13,7 +12,7 @@ import (
// with payload/options of frames to avoid panics. // with payload/options of frames to avoid panics.
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < sizeHeader { 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 return Frame{buf: buf}, nil
} }
@@ -90,19 +89,14 @@ func (frm Frame) ClearHeader() {
// Validation API. // 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 // 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. // the frame. It returns a non-nil error on finding an inconsistency.
func (ufrm Frame) ValidateSize(v *lneto.Validator) { func (ufrm Frame) ValidateSize(v *lneto.Validator) {
ul := ufrm.Length() ul := ufrm.Length()
if ul < sizeHeader { if ul < sizeHeader {
v.AddError(errBadLen) v.AddError(lneto.ErrInvalidLengthField)
} }
if int(ul) > len(ufrm.RawData()) { 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 addr := cfg.StaticAddress
s.prng = uint32(cfg.RandSeed) s.prng = uint32(cfg.RandSeed)
if s.prng == 0 { if s.prng == 0 {
return errors.New("zero random seed") return lneto.ErrInvalidConfig
} }
s.hostname = cfg.Hostname s.hostname = cfg.Hostname
if !addr.IsValid() { if !addr.IsValid() {
addr = netip.AddrFrom4([4]byte{}) // If static not set DHCP will be performed and address will be zero. addr = netip.AddrFrom4([4]byte{}) // If static not set DHCP will be performed and address will be zero.
} else if addr.Is6() { } else if addr.Is6() {
return errors.New("IPv6 unsupported as of yet") return lneto.ErrUnsupported
} }
const linkNodes = 2 // ARP and IP nodes const linkNodes = 2 // ARP and IP nodes
ecfg := internet.StackEthernetConfig{ ecfg := internet.StackEthernetConfig{
@@ -171,13 +171,11 @@ func (s *StackAsync) Reset(cfg StackConfig) error {
return nil return nil
} }
var errInvalidIPAddr = errors.New("invaldi IP address")
func (s *StackAsync) resetARP() error { func (s *StackAsync) resetARP() error {
mac := s.link.HardwareAddr6() mac := s.link.HardwareAddr6()
addr := s.ip.Addr() addr := s.ip.Addr()
if !addr.IsValid() { if !addr.IsValid() {
return errInvalidIPAddr return lneto.ErrInvalidAddr
} }
proto := ethernet.TypeIPv4 proto := ethernet.TypeIPv4
if addr.Is6() { if addr.Is6() {
@@ -384,7 +382,7 @@ func (s *StackAsync) ResultLookupIP(host string) ([]netip.Addr, bool, error) {
} else if len(data) == 16 { } else if len(data) == 16 {
addrs = append(addrs, netip.AddrFrom16([16]byte(data))) addrs = append(addrs, netip.AddrFrom16([16]byte(data)))
} else { } else {
err = errors.New("bogus IP") err = lneto.ErrInvalidAddr
} }
} }
if err == nil && len(addrs) == 0 { if err == nil && len(addrs) == 0 {
@@ -441,7 +439,7 @@ func (s *StackAsync) StartResolveHardwareAddress6(ip netip.Addr) error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if !ip.Is4() { if !ip.Is4() {
return errors.New("unsupported or invalid IP address") return lneto.ErrUnsupported
} }
addr := ip.As4() addr := ip.As4()
return s.arp.StartQuery(nil, addr[:]) return s.arp.StartQuery(nil, addr[:])
@@ -452,7 +450,7 @@ func (s *StackAsync) ResultResolveHardwareAddress6(ip netip.Addr) (hw [6]byte, e
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if !ip.Is4() { if !ip.Is4() {
return hw, errors.New("unsupported or invalid IP address") return hw, lneto.ErrUnsupported
} }
addr := ip.As4() addr := ip.As4()
hwslice, err := s.arp.QueryResult(addr[:]) hwslice, err := s.arp.QueryResult(addr[:])
@@ -469,7 +467,7 @@ func (s *StackAsync) DiscardResolveHardwareAddress6(ip netip.Addr) error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if !ip.Is4() { if !ip.Is4() {
return errors.New("unsupported or invalid IP address") return lneto.ErrUnsupported
} }
addr := ip.As4() addr := ip.As4()
return s.arp.DiscardQuery(addr[:]) return s.arp.DiscardQuery(addr[:])
@@ -526,7 +524,7 @@ func (stack *StackAsync) AssimilateDHCPResults(results *DHCPResults) error {
} }
if len(results.DNSServers) > 0 { if len(results.DNSServers) > 0 {
if !results.DNSServers[0].IsValid() || !results.DNSServers[0].Is4() { 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] stack.dnssv = results.DNSServers[0]
} }
+5 -5
View File
@@ -2,12 +2,12 @@ package xnet
import ( import (
"context" "context"
"errors"
"net" "net"
"net/netip" "net/netip"
"syscall" "syscall"
"time" "time"
"github.com/soypat/lneto"
"github.com/soypat/lneto/tcp" "github.com/soypat/lneto/tcp"
) )
@@ -36,7 +36,7 @@ func (s StackBerkeley) Socket(ctx context.Context, network string, family, sotyp
switch family { switch family {
case syscall.AF_INET: case syscall.AF_INET:
default: default:
return nil, errors.New("unsupported address family") return nil, lneto.ErrUnsupported
} }
var local, remote netip.AddrPort var local, remote netip.AddrPort
if laddr != nil { if laddr != nil {
@@ -54,10 +54,10 @@ func (s StackBerkeley) Socket(ctx context.Context, network string, family, sotyp
switch network { switch network {
case "udp", "udp4": case "udp", "udp4":
return nil, errors.New("udp not yet supported") return nil, lneto.ErrUnsupported
case "tcp", "tcp4": case "tcp", "tcp4":
if sotype != sockSTREAM { if sotype != sockSTREAM {
return nil, errors.New("unsupported socket type") return nil, lneto.ErrUnsupported
} }
if raddr != nil { if raddr != nil {
@@ -106,7 +106,7 @@ func (s StackBerkeley) Socket(ctx context.Context, network string, family, sotyp
return &l, nil return &l, nil
} }
} }
return nil, errors.New("unsupported network") return nil, lneto.ErrUnsupported
} }
type tcplistener struct { type tcplistener struct {
+2 -2
View File
@@ -2,11 +2,11 @@ package xnet
import ( import (
"context" "context"
"errors"
"log/slog" "log/slog"
"sync" "sync"
"time" "time"
"github.com/soypat/lneto"
"github.com/soypat/lneto/tcp" "github.com/soypat/lneto/tcp"
) )
@@ -52,7 +52,7 @@ type TCPPoolConfig struct {
func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) { func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) {
if cfg.EstablishedTimeout <= 0 || cfg.ClosingTimeout <= 0 { if cfg.EstablishedTimeout <= 0 || cfg.ClosingTimeout <= 0 {
return nil, errors.New("invalid timeout") return nil, lneto.ErrInvalidConfig
} }
n := cfg.PoolSize n := cfg.PoolSize
pool := &TCPPool{ pool := &TCPPool{