mirror of
https://github.com/soypat/lneto.git
synced 2026-08-22 15:39:06 +00:00
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:
+6
-10
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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++ {
|
||||
|
||||
Reference in New Issue
Block a user