mirror of
https://github.com/soypat/lneto.git
synced 2026-07-26 10:38:47 +00:00
Reduce heap allocs 2 (#46)
* reduce heap allocations in tcp logging; omit use of AppendFloat which allocates a metric sh*tton * debugheaplog: better heap statistic logging * heap: use string for pcap.Frame.Protocol * add potential to eliminate Flags.String heap alloc, remove incorrect HEAP comments * add StackAsync.DebugErr and httpraw.SetBytes * many heap alloc reductions and replacement of bytes.Equal with internal.BytesEqual
This commit is contained in:
+4
-5
@@ -1,7 +1,6 @@
|
||||
package arp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log/slog"
|
||||
|
||||
@@ -101,7 +100,7 @@ func (h *Handler) expectSize() int {
|
||||
|
||||
func (h *Handler) QueryResult(protoAddr []byte) (hwAddr []byte, err error) {
|
||||
for i := range h.queries {
|
||||
if bytes.Equal(protoAddr, h.queries[i].protoaddr) {
|
||||
if internal.BytesEqual(protoAddr, h.queries[i].protoaddr) {
|
||||
if !h.queries[i].querysent {
|
||||
return nil, errors.New("query not yet sent")
|
||||
}
|
||||
@@ -118,7 +117,7 @@ func (h *Handler) QueryResult(protoAddr []byte) (hwAddr []byte, err error) {
|
||||
func (h *Handler) DiscardQuery(protoAddr []byte) error {
|
||||
for i := range h.queries {
|
||||
q := &h.queries[i]
|
||||
if bytes.Equal(protoAddr, q.protoaddr) {
|
||||
if internal.BytesEqual(protoAddr, q.protoaddr) {
|
||||
q.destroy()
|
||||
return nil
|
||||
}
|
||||
@@ -240,7 +239,7 @@ func (h *Handler) Demux(ethFrame []byte, frameOffset int) error {
|
||||
switch afrm.Operation() {
|
||||
case OpRequest:
|
||||
_, protoaddr := afrm.Target()
|
||||
if !bytes.Equal(protoaddr, h.ourProtoAddr) {
|
||||
if !internal.BytesEqual(protoaddr, h.ourProtoAddr) {
|
||||
return nil // Not for us.
|
||||
}
|
||||
h.pendingResponse = h.pendingResponse[:len(h.pendingResponse)+1] // Extend pending buffer.
|
||||
@@ -251,7 +250,7 @@ func (h *Handler) Demux(ethFrame []byte, frameOffset int) error {
|
||||
for i := range h.queries {
|
||||
q := &h.queries[i]
|
||||
mac := q.response()
|
||||
if mac == nil && bytes.Equal(q.protoaddr, protoaddr) {
|
||||
if mac == nil && internal.BytesEqual(q.protoaddr, protoaddr) {
|
||||
q.hwaddr = append(q.hwaddr, hwaddr...)
|
||||
if q.dstHw != nil {
|
||||
if !internal.IsZeroed(q.dstHw...) {
|
||||
|
||||
@@ -2,7 +2,6 @@ package httpraw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Cookie implements cookie key-value parsing. Methods function similarly to eponymous [Header] methods.
|
||||
@@ -55,7 +54,7 @@ func (dst *Cookie) CopyFrom(c Cookie) {
|
||||
// Parse parses the cookie's buffer in place.
|
||||
func (c *Cookie) Parse() error {
|
||||
if len(c.kvs) > 0 {
|
||||
return errors.New("cookies already parsed, reset before parsing again")
|
||||
return errCookiesParsed
|
||||
}
|
||||
off := 0
|
||||
for {
|
||||
|
||||
+20
-5
@@ -2,9 +2,9 @@ package httpraw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"slices"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -75,7 +75,9 @@ func (h *Header) ParseBytes(asResponse bool, b []byte) error {
|
||||
// Parse parses accumulated data in-place with no copying. One can set HTTP header data buffer with [Header.Reset].
|
||||
// It fails if HTTP data is incomplete.
|
||||
func (h *Header) Parse(asResponse bool) error {
|
||||
debuglog("http:parse:reset")
|
||||
h.Reset(h.hbuf.buf)
|
||||
debuglog("http:parse:start")
|
||||
return h.parse(asResponse)
|
||||
}
|
||||
|
||||
@@ -97,7 +99,7 @@ func (h *Header) Parse(asResponse bool) error {
|
||||
// }
|
||||
func (h *Header) TryParse(asResponse bool) (needMoreData bool, err error) {
|
||||
if h.flags.hasAny(flagDoneParsingHeader) {
|
||||
return false, errors.New("TryParse called after header parsed")
|
||||
return false, errAlreadyParsed
|
||||
} else if h.flags.hasAny(flagMangledBuffer) {
|
||||
return false, errMangledBuffer
|
||||
}
|
||||
@@ -225,15 +227,18 @@ func (h *Header) Reset(buf []byte) {
|
||||
panic("small buffer and flagNoBufferGrow set")
|
||||
}
|
||||
const persistentFlags = flagNoBufferGrow
|
||||
debuglog("http:reset:hbuf")
|
||||
h.hbuf.reset(buf)
|
||||
*h = Header{
|
||||
hbuf: h.hbuf,
|
||||
flags: h.flags & persistentFlags,
|
||||
}
|
||||
debuglog("http:reset:done")
|
||||
}
|
||||
|
||||
// Body returns the surplus data following headers. It is only valid as long as Parse* or Reset methods are not called.
|
||||
func (h *Header) Body() ([]byte, error) {
|
||||
debuglog("http:body")
|
||||
if h.flags.hasAny(flagMangledBuffer) {
|
||||
return nil, errMangledBuffer
|
||||
} else if h.flags.hasAny(flagDoneParsingHeader) {
|
||||
@@ -242,7 +247,14 @@ func (h *Header) Body() ([]byte, error) {
|
||||
return nil, errUnparsed
|
||||
}
|
||||
|
||||
// Set sets a key-value pair in the HTTP header. Calling Set mangles the buffer.
|
||||
// SetBytes is equivalent to [Header.Set] but with a []byte value. Does not keep reference to value slice.
|
||||
// Calling SetBytes Mangles the buffer.
|
||||
func (h *Header) SetBytes(key string, value []byte) {
|
||||
h.Set(key, unsafe.String(&value[0], len(value)))
|
||||
}
|
||||
|
||||
// Set sets a key-value pair in the HTTP header.
|
||||
// Calling Set mangles the buffer.
|
||||
func (h *Header) Set(key, value string) {
|
||||
hb := &h.hbuf
|
||||
var useKv *argsKV
|
||||
@@ -269,10 +281,13 @@ func (h *Header) Set(key, value string) {
|
||||
|
||||
// Get gets the first value of a key found in the headers. Use [Header.ForEach] to find multiple values corresponding to same key.
|
||||
func (h *Header) Get(key string) []byte {
|
||||
debuglog("http:get:start")
|
||||
kv := h.peekHeader(key)
|
||||
if kv.isValid() {
|
||||
debuglog("http:get:found")
|
||||
return h.hbuf.musttoken(kv.value)
|
||||
}
|
||||
debuglog("http:get:notfound")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -338,7 +353,7 @@ func (h *Header) AppendRequest(dst []byte) ([]byte, error) {
|
||||
if h.flags.hasAny(flagOOMReached) {
|
||||
return dst, errOOM
|
||||
} else if h.requestURI.len == 0 || h.method.len == 0 {
|
||||
return dst, errors.New("need method/request URI to create request header")
|
||||
return dst, errNeedMethodURI
|
||||
} else if len(proto) == 0 {
|
||||
return dst, errNoProto
|
||||
}
|
||||
@@ -368,7 +383,7 @@ func (h *Header) AppendResponse(dst []byte) ([]byte, error) {
|
||||
if h.flags.hasAny(flagOOMReached) {
|
||||
return dst, errOOM
|
||||
} else if h.statusCode.len == 0 || h.statusText.len == 0 {
|
||||
return dst, errors.New("invalid status code or text")
|
||||
return dst, errBadStatusCodeTxt
|
||||
} else if len(proto) == 0 {
|
||||
return dst, errNoProto
|
||||
}
|
||||
|
||||
+52
-21
@@ -3,8 +3,11 @@ package httpraw
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"unsafe"
|
||||
|
||||
"github.com/soypat/lneto/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -16,8 +19,15 @@ var (
|
||||
errOOM = errors.New("httpraw: buffer out of memory")
|
||||
// Header.Set and Header.Add mangles the buffer.
|
||||
// Call them after retrieving the Body. Do not call them before parsing the header (why would you even do that?).
|
||||
errMangledBuffer = errors.New("httpraw: mangled buffer")
|
||||
errNoCookies = errors.New("no cookie found")
|
||||
errMangledBuffer = errors.New("httpraw: mangled buffer")
|
||||
errNoCookies = errors.New("no cookie found")
|
||||
errEmptyURI = errors.New("empty URI")
|
||||
errLongStatusCode = errors.New("long status code")
|
||||
errBadStatusCode = errors.New("invalid status code")
|
||||
errAlreadyParsed = errors.New("TryParse called after header parsed")
|
||||
errNeedMethodURI = errors.New("need method/request URI to create request header")
|
||||
errBadStatusCodeTxt = errors.New("invalid status code or text")
|
||||
errCookiesParsed = errors.New("cookies already parsed, reset before parsing again")
|
||||
)
|
||||
|
||||
type headerBuf struct {
|
||||
@@ -29,6 +39,20 @@ type headerBuf struct {
|
||||
headers []argsKV
|
||||
}
|
||||
|
||||
// reset sets the buffer data and discards all parsed data.
|
||||
func (h *headerBuf) reset(buf []byte) {
|
||||
if buf == nil {
|
||||
buf = h.buf[:0] // Reuse buffer but discard raw data on nil input.
|
||||
}
|
||||
if cap(h.headers) == 0 {
|
||||
h.headers = make([]argsKV, 16)
|
||||
}
|
||||
*h = headerBuf{
|
||||
buf: buf,
|
||||
headers: h.headers[:0],
|
||||
}
|
||||
}
|
||||
|
||||
type tokint = uint16
|
||||
|
||||
type headerSlice struct {
|
||||
@@ -56,11 +80,16 @@ type scannerState struct {
|
||||
}
|
||||
|
||||
func (h *Header) parse(asResponse bool) (err error) {
|
||||
debuglog("http:firstline:start")
|
||||
err = h.parseFirstLine(asResponse)
|
||||
if err != nil {
|
||||
debuglog("http:firstline:err")
|
||||
return err
|
||||
}
|
||||
return h.parseNextHeaders()
|
||||
debuglog("http:firstline:done")
|
||||
err = h.parseNextHeaders()
|
||||
debuglog("http:headers:done")
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Header) parseFirstLine(asResponse bool) (err error) {
|
||||
@@ -90,9 +119,11 @@ func (hb *headerBuf) readFromBytes(b []byte) {
|
||||
func (hb *headerBuf) free() int { return cap(hb.buf) - len(hb.buf) }
|
||||
|
||||
func (hb *headerBuf) parseNextHeaders(ss *scannerState) {
|
||||
debuglog("http:nexthdr:loop")
|
||||
for kv := hb.next(ss); kv.isValid(); kv = hb.next(ss) {
|
||||
hb.headers = append(hb.headers, kv)
|
||||
hb.headers = append(hb.headers, kv) // TODO(HEAP): inc=16B slice growth when capacity exceeded
|
||||
}
|
||||
debuglog("http:nexthdr:done")
|
||||
}
|
||||
|
||||
func (hb *headerBuf) offBuf() []byte {
|
||||
@@ -127,6 +158,7 @@ func (hb *headerBuf) scanUntilByte(c byte) []byte {
|
||||
}
|
||||
|
||||
func (hb *headerBuf) parseFirstLineRequest(initFlags flags) (method, uri, proto headerSlice, flags flags, err error) {
|
||||
debuglog("http:req:scan")
|
||||
hb.off = 0 // Parsing first line resets offset.
|
||||
var b []byte
|
||||
hb.skipLeadingCRLF()
|
||||
@@ -135,6 +167,7 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags flags) (method, uri, proto
|
||||
if len(b) < 5 {
|
||||
return method, uri, proto, flags, errNeedMore
|
||||
}
|
||||
debuglog("http:req:parse")
|
||||
|
||||
methodEnd := max(0, bytes.IndexByte(b, ' '))
|
||||
reqURIEnd := bytes.IndexByte(b[methodEnd+1:], ' ')
|
||||
@@ -145,7 +178,7 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags flags) (method, uri, proto
|
||||
flags |= flagNoHTTP11
|
||||
}
|
||||
} else if reqURIEnd == 0 {
|
||||
return method, uri, proto, flags, errors.New("empty URI")
|
||||
return method, uri, proto, flags, errEmptyURI
|
||||
} else {
|
||||
// No version provided.
|
||||
reqURIEnd = methodEnd + 1
|
||||
@@ -158,6 +191,7 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags flags) (method, uri, proto
|
||||
}
|
||||
|
||||
func (hb *headerBuf) parseFirstLineResponse(initFlags flags) (statusCode, statusText headerSlice, flags flags, err error) {
|
||||
debuglog("http:resp:scan")
|
||||
hb.off = 0 // Parsing first line resets offset.
|
||||
var b []byte
|
||||
hb.skipLeadingCRLF()
|
||||
@@ -166,23 +200,23 @@ func (hb *headerBuf) parseFirstLineResponse(initFlags flags) (statusCode, status
|
||||
if len(b) < 5 {
|
||||
return statusCode, statusText, flags, errNeedMore
|
||||
}
|
||||
debuglog("http:resp:parse")
|
||||
|
||||
statusCodeEnd := max(0, bytes.IndexByte(b, ' '))
|
||||
if statusCodeEnd < 0 {
|
||||
return statusCode, statusText, flags, errors.New("missing status code")
|
||||
}
|
||||
code := b[:statusCodeEnd]
|
||||
text := b[statusCodeEnd:]
|
||||
if len(code) > 3 {
|
||||
return statusCode, statusText, flags, errors.New("long status code")
|
||||
return statusCode, statusText, flags, errLongStatusCode
|
||||
}
|
||||
for i := range code {
|
||||
if code[i] > '9' || code[i] < '0' {
|
||||
return statusCode, statusText, flags, errors.New("invalid status code")
|
||||
debuglog("http:resp:invalid-code")
|
||||
return statusCode, statusText, flags, errBadStatusCode
|
||||
}
|
||||
}
|
||||
statusCode = hb.slice(code)
|
||||
statusText = hb.slice(text)
|
||||
debuglog("http:resp:done")
|
||||
return statusCode, statusText, flags, nil
|
||||
}
|
||||
|
||||
@@ -360,17 +394,6 @@ func (hb *headerBuf) next(ss *scannerState) argsKV {
|
||||
return resultKV
|
||||
}
|
||||
|
||||
// reset sets the buffer data and discards all parsed data.
|
||||
func (h *headerBuf) reset(buf []byte) {
|
||||
if buf == nil {
|
||||
buf = h.buf[:0] // Reuse buffer but discard raw data on nil input.
|
||||
}
|
||||
*h = headerBuf{
|
||||
buf: buf,
|
||||
headers: h.headers[:0],
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionClose returns true if 'Connection: close' header is set or if a invalid header was found.
|
||||
func (h *Header) ConnectionClose() bool {
|
||||
closed := h.flags.hasAny(flagConnClose) ||
|
||||
@@ -402,3 +425,11 @@ func bytes2tok(buf, value []byte) headerSlice {
|
||||
len: tokint(len(value)),
|
||||
}
|
||||
}
|
||||
|
||||
const enableDebug = false
|
||||
|
||||
func debuglog(msg string) {
|
||||
if enableDebug {
|
||||
internal.LogAttrs(nil, slog.LevelDebug, msg)
|
||||
}
|
||||
}
|
||||
|
||||
+80
-1
@@ -1,7 +1,86 @@
|
||||
package internal
|
||||
|
||||
import "log/slog"
|
||||
import (
|
||||
"log/slog"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
LevelTrace slog.Level = slog.LevelDebug - 2
|
||||
|
||||
usePrintLogAllocs = true
|
||||
)
|
||||
|
||||
var (
|
||||
memstats runtime.MemStats
|
||||
lastAllocs uint64
|
||||
lastMallocs uint64
|
||||
allocmu sync.Mutex
|
||||
allocbuf [256]byte
|
||||
)
|
||||
|
||||
func LogAllocs(msg string) {
|
||||
allocmu.Lock()
|
||||
runtime.ReadMemStats(&memstats)
|
||||
if memstats.TotalAlloc == lastAllocs {
|
||||
allocmu.Unlock()
|
||||
return
|
||||
}
|
||||
if usePrintLogAllocs {
|
||||
print("[ALLOC] ", msg)
|
||||
print(" inc=", int64(memstats.TotalAlloc)-int64(lastAllocs))
|
||||
print(" n=", int64(memstats.Mallocs)-int64(lastMallocs))
|
||||
print(" heap=", memstats.HeapAlloc)
|
||||
print(" tot=", memstats.TotalAlloc)
|
||||
println()
|
||||
} else {
|
||||
n := copy(allocbuf[:], "[ALLOC] ")
|
||||
n += copy(allocbuf[n:], msg)
|
||||
n += copyValueInt(allocbuf[n:], "inc", int64(memstats.TotalAlloc)-int64(lastAllocs))
|
||||
n += copyValueInt(allocbuf[n:], "n", int64(memstats.Mallocs)-int64(lastMallocs))
|
||||
n += copyValueUint(allocbuf[n:], "heap", memstats.HeapAlloc)
|
||||
n += copyValueUint(allocbuf[n:], "tot", memstats.TotalAlloc)
|
||||
println(unsafe.String(&allocbuf[0], n))
|
||||
}
|
||||
lastAllocs = memstats.TotalAlloc
|
||||
lastMallocs = memstats.Mallocs
|
||||
allocmu.Unlock()
|
||||
}
|
||||
|
||||
func copyValueInt(buf []byte, key string, v int64) int {
|
||||
// ' ' + key + '=' + up to 20 chars for int64
|
||||
if len(buf) < 2+len(key)+20 {
|
||||
return 0
|
||||
}
|
||||
buf[0] = ' '
|
||||
n := 1 + copy(buf[1:], key)
|
||||
buf[n] = '='
|
||||
n++
|
||||
return n + copyInt(buf[n:], v)
|
||||
}
|
||||
|
||||
func copyValueUint(buf []byte, key string, v uint64) int {
|
||||
if len(buf) < 2+len(key)+20 {
|
||||
return 0
|
||||
}
|
||||
buf[0] = ' '
|
||||
n := 1 + copy(buf[1:], key)
|
||||
buf[n] = '='
|
||||
n++
|
||||
return n + copyUint(buf[n:], v)
|
||||
}
|
||||
|
||||
// copyInt formats v into buf and returns the number of bytes written.
|
||||
// Caller must ensure cap(buf) >= 20.
|
||||
func copyInt(buf []byte, v int64) int {
|
||||
return len(strconv.AppendInt(buf[:0], v, 10))
|
||||
}
|
||||
|
||||
// copyUint formats v into buf and returns the number of bytes written.
|
||||
// Caller must ensure cap(buf) >= 20.
|
||||
func copyUint(buf []byte, v uint64) int {
|
||||
return len(strconv.AppendUint(buf[:0], v, 10))
|
||||
}
|
||||
|
||||
@@ -15,9 +15,6 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
memstats runtime.MemStats
|
||||
lastAllocs uint64
|
||||
|
||||
timebuf [len(timefmt) * 2]byte
|
||||
)
|
||||
|
||||
@@ -28,12 +25,7 @@ func LogEnabled(l *slog.Logger, lvl slog.Level) bool {
|
||||
func LogAttrs(_ *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr) {
|
||||
now := time.Now()
|
||||
n := len(now.AppendFormat(timebuf[:0], timefmt))
|
||||
runtime.ReadMemStats(&memstats)
|
||||
if memstats.TotalAlloc != lastAllocs {
|
||||
print("[ALLOC] inc=", int64(memstats.TotalAlloc)-int64(lastAllocs))
|
||||
print(" tot=", memstats.TotalAlloc, " seqs")
|
||||
println()
|
||||
}
|
||||
LogAllocs(msg)
|
||||
print("time=", unsafe.String(&timebuf[0], n), " ")
|
||||
if level == LevelTrace {
|
||||
print("TRACE ")
|
||||
@@ -57,8 +49,12 @@ func LogAttrs(_ *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr)
|
||||
}
|
||||
}
|
||||
println()
|
||||
allocmu.Lock()
|
||||
runtime.ReadMemStats(&memstats)
|
||||
if memstats.TotalAlloc != lastAllocs {
|
||||
lastAllocs = memstats.TotalAlloc
|
||||
if lastAllocs != memstats.TotalAlloc {
|
||||
print("alloc increase in heaplog")
|
||||
}
|
||||
lastAllocs = memstats.TotalAlloc
|
||||
lastMallocs = memstats.Mallocs
|
||||
allocmu.Unlock()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package ltesto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/rand"
|
||||
|
||||
"github.com/soypat/lneto"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/internal"
|
||||
"github.com/soypat/lneto/ipv4"
|
||||
"github.com/soypat/lneto/ipv4/icmpv4"
|
||||
"github.com/soypat/lneto/tcp"
|
||||
@@ -134,9 +134,9 @@ func (gen *PacketGen) AppendRandomIPv4TCPPacket(dst []byte, rng *rand.Rand, seg
|
||||
switch {
|
||||
case gen.SrcTCP != tfrm.SourcePort():
|
||||
panic("IP options overwrite TCP header")
|
||||
case !bytes.Equal(ifrm.Options(), ipOpts):
|
||||
case !internal.BytesEqual(ifrm.Options(), ipOpts):
|
||||
panic("bad ip options written, ensure ip options length is multiple of 4")
|
||||
case !bytes.Equal(tfrm.Options(), tcpOpts):
|
||||
case !internal.BytesEqual(tfrm.Options(), tcpOpts):
|
||||
panic("bad tcp options written, ensure tcp options length is multiple of 4")
|
||||
case *ifrm.DestinationAddr() != gen.DstIPv4:
|
||||
panic("IP options overwrite own header")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package internal
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// IsZeroed returns true if all arguments are set to their zero value.
|
||||
func IsZeroed[T comparable](a ...T) bool {
|
||||
var z T
|
||||
@@ -65,3 +67,15 @@ func SliceReclaim[T any](ptr *[]T) *T {
|
||||
}
|
||||
return &(*ptr)[n]
|
||||
}
|
||||
|
||||
// BytesEqual is heapless replacement of [bytes.Equal] since it allocates in tinygo.
|
||||
// https://github.com/tinygo-org/tinygo/issues/4045
|
||||
func BytesEqual(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
if len(a) == 0 {
|
||||
return true
|
||||
}
|
||||
return unsafe.String(&a[0], len(a)) == unsafe.String(&b[0], len(b))
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ func (s *cbnode) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int)
|
||||
}
|
||||
|
||||
func (s *cbnode) Demux(carrierData []byte, frameOffset int) error {
|
||||
debugLog("cbnode:pre-demux")
|
||||
return s._demux(carrierData, frameOffset)
|
||||
}
|
||||
|
||||
|
||||
+53
-22
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
|
||||
"github.com/soypat/lneto"
|
||||
@@ -29,12 +30,6 @@ var (
|
||||
ErrFieldByClassNotFound = errors.New("pcap: field by class not found")
|
||||
)
|
||||
|
||||
type proto string
|
||||
|
||||
const (
|
||||
ProtoEthernet proto = "Ethernet"
|
||||
)
|
||||
|
||||
type PacketBreakdown struct {
|
||||
hdr httpraw.Header
|
||||
dmsg dns.Message
|
||||
@@ -66,8 +61,10 @@ func (pc *PacketBreakdown) initFrames() []Frame {
|
||||
}
|
||||
|
||||
func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||
debuglog("pcap:eth:start")
|
||||
if dst == nil {
|
||||
dst = pc.initFrames()
|
||||
debuglog("pcap:eth:initframes")
|
||||
}
|
||||
if bitOffset%8 != 0 {
|
||||
return dst, errors.New("ethernet must be parsed at byte boundary")
|
||||
@@ -80,8 +77,10 @@ func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset in
|
||||
if pc.validator().HasError() {
|
||||
return dst, pc.validator().ErrPop()
|
||||
}
|
||||
debuglog("pcap:eth:validated")
|
||||
|
||||
finfo := reclaimFrame(&dst, ProtoEthernet, bitOffset, baseEthernetFields[:])
|
||||
finfo := reclaimFrame(&dst, "Ethernet", bitOffset, baseEthernetFields[:])
|
||||
debuglog("pcap:eth:reclaimed")
|
||||
etype := efrm.EtherTypeOrSize()
|
||||
end := 14*octet + bitOffset
|
||||
if etype.IsSize() {
|
||||
@@ -102,12 +101,14 @@ func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset in
|
||||
case ethernet.TypeIPv6:
|
||||
dst, err = pc.CaptureIPv6(dst, pkt, end)
|
||||
default:
|
||||
reclaimRemainingFrame(&dst, etype, FieldClassPayload, end, octet*len(pkt))
|
||||
reclaimRemainingFrame(&dst, "Unknown Ethertype", FieldClassPayload, end, octet*len(pkt))
|
||||
}
|
||||
debuglog("pcap:eth:done")
|
||||
return dst, err
|
||||
}
|
||||
|
||||
func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||
debuglog("pcap:arp:start")
|
||||
if bitOffset%8 != 0 {
|
||||
return dst, errors.New("ARP must be parsed at byte boundary")
|
||||
}
|
||||
@@ -120,7 +121,7 @@ func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([
|
||||
return dst, pc.validator().ErrPop()
|
||||
}
|
||||
|
||||
finfo := reclaimFrame(&dst, ethernet.TypeARP, bitOffset, baseARPFields[:])
|
||||
finfo := reclaimFrame(&dst, "ARP", bitOffset, baseARPFields[:])
|
||||
const varstart = 8 * octet
|
||||
_, hlen := afrm.Hardware()
|
||||
_, plen := afrm.Protocol()
|
||||
@@ -154,6 +155,7 @@ func (pc *PacketBreakdown) CaptureARP(dst []Frame, pkt []byte, bitOffset int) ([
|
||||
}
|
||||
|
||||
func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||
debuglog("pcap:ipv6:start")
|
||||
if bitOffset%8 != 0 {
|
||||
return dst, errors.New("IPv6 must be parsed at byte boundary")
|
||||
}
|
||||
@@ -165,7 +167,8 @@ func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) (
|
||||
if pc.validator().HasError() {
|
||||
return dst, pc.validator().ErrPop()
|
||||
}
|
||||
reclaimFrame(&dst, ethernet.TypeIPv6, bitOffset, baseIPv6Fields[:])
|
||||
reclaimFrame(&dst, "IPv6", bitOffset, baseIPv6Fields[:])
|
||||
debuglog("pcap:ipv6:reclaimed")
|
||||
proto := ifrm6.NextHeader()
|
||||
end := bitOffset + 40*octet
|
||||
var protoErrs []error
|
||||
@@ -196,6 +199,7 @@ func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) (
|
||||
}
|
||||
|
||||
func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||
debuglog("pcap:ipv4:start")
|
||||
if bitOffset%8 != 0 {
|
||||
return dst, errors.New("IPv4 must be parsed at byte boundary")
|
||||
}
|
||||
@@ -207,9 +211,11 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
|
||||
if pc.validator().HasError() {
|
||||
return dst, pc.validator().ErrPop()
|
||||
}
|
||||
debuglog("pcap:ipv4:validated")
|
||||
// limit packet to the actual IPv4 frame size
|
||||
pkt = pkt[:bitOffset/8+int(ifrm4.TotalLength())]
|
||||
finfo := reclaimFrame(&dst, ethernet.TypeIPv4, bitOffset, baseIPv4Fields[:])
|
||||
finfo := reclaimFrame(&dst, "IPv4", bitOffset, baseIPv4Fields[:])
|
||||
debuglog("pcap:ipv4:reclaimed")
|
||||
options := ifrm4.Options()
|
||||
if len(options) > 0 {
|
||||
finfo.Fields = append(finfo.Fields, FrameField{
|
||||
@@ -221,6 +227,7 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
|
||||
if ifrm4.CalculateHeaderCRC() != 0 {
|
||||
finfo.Errors = append(finfo.Errors, lneto.ErrBadCRC)
|
||||
}
|
||||
debuglog("pcap:ipv4:crc-done")
|
||||
proto := ifrm4.Protocol()
|
||||
end := bitOffset + octet*ifrm4.HeaderLength()
|
||||
var protoErrs []error
|
||||
@@ -262,10 +269,12 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
|
||||
}
|
||||
}
|
||||
}
|
||||
debuglog("pcap:ipv4:proto-crc-done")
|
||||
return pc.captureIPProto(proto, dst, pkt, end, protoErrs...)
|
||||
}
|
||||
|
||||
func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt []byte, bitOffset int, ipProtoErrs ...error) (_ []Frame, err error) {
|
||||
debuglog("pcap:ipproto:start")
|
||||
nextFrame := len(dst)
|
||||
switch proto {
|
||||
case lneto.IPProtoTCP:
|
||||
@@ -275,20 +284,22 @@ func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt
|
||||
case lneto.IPProtoUDPLite:
|
||||
dst, err = pc.CaptureUDP(dst, pkt, bitOffset)
|
||||
if len(dst) > nextFrame {
|
||||
dst[nextFrame].Protocol = lneto.IPProtoUDPLite
|
||||
dst[nextFrame].Protocol = "UDPLite"
|
||||
}
|
||||
case lneto.IPProtoICMP:
|
||||
dst, err = pc.CaptureICMPv4(dst, pkt, bitOffset)
|
||||
default:
|
||||
reclaimRemainingFrame(&dst, proto, 0, bitOffset, octet*len(pkt))
|
||||
reclaimRemainingFrame(&dst, "unknown proto", 0, bitOffset, octet*len(pkt))
|
||||
}
|
||||
if len(ipProtoErrs) > 0 && len(dst) > nextFrame {
|
||||
dst[nextFrame].Errors = append(dst[nextFrame].Errors, ipProtoErrs...)
|
||||
}
|
||||
debuglog("pcap:ipproto:done")
|
||||
return dst, err
|
||||
}
|
||||
|
||||
func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||
debuglog("pcap:tcp:start")
|
||||
if bitOffset%8 != 0 {
|
||||
return dst, errors.New("TCP must be parsed at byte boundary")
|
||||
}
|
||||
@@ -300,8 +311,10 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
|
||||
if pc.validator().HasError() {
|
||||
return dst, pc.validator().ErrPop()
|
||||
}
|
||||
debuglog("pcap:tcp:validated")
|
||||
end := bitOffset + octet*tfrm.HeaderLength()
|
||||
finfo := reclaimFrame(&dst, lneto.IPProtoTCP, bitOffset, baseTCPFields[:])
|
||||
finfo := reclaimFrame(&dst, "TCP", bitOffset, baseTCPFields[:])
|
||||
debuglog("pcap:tcp:reclaimed")
|
||||
options := tfrm.Options()
|
||||
if len(options) > 0 {
|
||||
finfo.Fields = append(finfo.Fields, FrameField{
|
||||
@@ -312,15 +325,19 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
|
||||
}
|
||||
payload := tfrm.Payload()
|
||||
if len(payload) > 0 {
|
||||
debuglog("pcap:tcp:http-start")
|
||||
dst, err = pc.CaptureHTTP(dst, pkt, end)
|
||||
debuglog("pcap:tcp:http-done")
|
||||
if err != nil {
|
||||
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
|
||||
}
|
||||
}
|
||||
debuglog("pcap:tcp:done")
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||
debuglog("pcap:udp:start")
|
||||
if bitOffset%8 != 0 {
|
||||
return dst, errors.New("UDP must be parsed at byte boundary")
|
||||
}
|
||||
@@ -332,7 +349,8 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
|
||||
if pc.validator().HasError() {
|
||||
return dst, pc.validator().ErrPop()
|
||||
}
|
||||
reclaimFrame(&dst, lneto.IPProtoUDP, bitOffset, baseUDPFields[:])
|
||||
reclaimFrame(&dst, "UDP", bitOffset, baseUDPFields[:])
|
||||
debuglog("pcap:udp:reclaimed")
|
||||
end := bitOffset + 8*octet
|
||||
payload := ufrm.Payload()
|
||||
dstport := ufrm.DestinationPort()
|
||||
@@ -347,10 +365,12 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
|
||||
if err != nil {
|
||||
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
|
||||
}
|
||||
debuglog("pcap:udp:done")
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||
debuglog("pcap:icmp:start")
|
||||
if bitOffset%8 != 0 {
|
||||
return dst, errors.New("ICMPv4 must be parsed at byte boundary")
|
||||
}
|
||||
@@ -360,7 +380,7 @@ func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int)
|
||||
return dst, err
|
||||
}
|
||||
|
||||
finfo := reclaimFrame(&dst, lneto.IPProtoICMP, bitOffset, baseICMPv4Fields[:])
|
||||
finfo := reclaimFrame(&dst, "ICMP", bitOffset, baseICMPv4Fields[:])
|
||||
|
||||
// Add type-specific fields.
|
||||
switch ifrm.Type() {
|
||||
@@ -541,9 +561,9 @@ func httpBodyClass(contentType, body []byte) FieldClass {
|
||||
switch {
|
||||
case bytes.HasPrefix(mediaType, []byte("text/")):
|
||||
return FieldClassText
|
||||
case bytes.Equal(mediaType, []byte("application/json")),
|
||||
bytes.Equal(mediaType, []byte("application/javascript")),
|
||||
bytes.Equal(mediaType, []byte("application/xml")):
|
||||
case internal.BytesEqual(mediaType, []byte("application/json")),
|
||||
internal.BytesEqual(mediaType, []byte("application/javascript")),
|
||||
internal.BytesEqual(mediaType, []byte("application/xml")):
|
||||
return FieldClassText
|
||||
case bytes.HasSuffix(mediaType, []byte("+json")),
|
||||
bytes.HasSuffix(mediaType, []byte("+xml")):
|
||||
@@ -562,6 +582,7 @@ func httpBodyClass(contentType, body []byte) FieldClass {
|
||||
}
|
||||
|
||||
func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||
debuglog("pcap:http:start")
|
||||
const httpProtocol = "HTTP"
|
||||
if bitOffset%8 != 0 {
|
||||
return dst, errors.New("HTTP must be parsed at byte boundary")
|
||||
@@ -578,6 +599,7 @@ func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) (
|
||||
if err != nil {
|
||||
return dst, err
|
||||
}
|
||||
debuglog("pcap:http:parsed")
|
||||
hdrLen := pc.hdr.BufferParsed()
|
||||
body, _ := pc.hdr.Body()
|
||||
bodyClass := httpBodyClass(pc.hdr.Get("Content-Type"), body)
|
||||
@@ -596,6 +618,7 @@ func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) (
|
||||
BitLength: len(body) * octet,
|
||||
},
|
||||
)
|
||||
debuglog("pcap:http:done")
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
@@ -626,7 +649,7 @@ func (ff Flags) IsRightAligned() bool { return ff&FlagRightAligned != 0 }
|
||||
|
||||
type Frame struct {
|
||||
PacketBitOffset int
|
||||
Protocol any
|
||||
Protocol string
|
||||
Fields []FrameField
|
||||
Errors []error
|
||||
}
|
||||
@@ -1306,7 +1329,7 @@ var baseNTPFields = [...]FrameField{
|
||||
// reclaimFrame extends dst via [internal.SliceReclaim], resets the reclaimed Frame
|
||||
// with the given protocol and bit offset while preserving Fields and Errors
|
||||
// backing arrays, and appends baseFields. Returns the Frame for further modification.
|
||||
func reclaimFrame(dst *[]Frame, proto any, bitOffset int, baseFields []FrameField) *Frame {
|
||||
func reclaimFrame(dst *[]Frame, proto string, bitOffset int, baseFields []FrameField) *Frame {
|
||||
finfo := internal.SliceReclaim(dst)
|
||||
*finfo = Frame{
|
||||
PacketBitOffset: bitOffset,
|
||||
@@ -1319,10 +1342,18 @@ func reclaimFrame(dst *[]Frame, proto any, bitOffset int, baseFields []FrameFiel
|
||||
|
||||
// reclaimRemainingFrame extends dst via SliceReclaim and populates the reclaimed
|
||||
// Frame as a single-field "remaining payload" frame, reusing the old Fields backing array.
|
||||
func reclaimRemainingFrame(dst *[]Frame, proto any, class FieldClass, pktBitOffset, pktBitLen int) {
|
||||
func reclaimRemainingFrame(dst *[]Frame, proto string, class FieldClass, pktBitOffset, pktBitLen int) {
|
||||
finfo := reclaimFrame(dst, proto, pktBitOffset, nil)
|
||||
finfo.Fields = append(finfo.Fields, FrameField{
|
||||
Class: class,
|
||||
BitLength: pktBitLen - pktBitOffset,
|
||||
})
|
||||
}
|
||||
|
||||
const enableDebug = false
|
||||
|
||||
func debuglog(msg string) {
|
||||
if enableDebug {
|
||||
internal.LogAttrs(nil, slog.LevelDebug, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ func TestRightAlignedFields(t *testing.T) {
|
||||
// Find IPv6 frame.
|
||||
var ipv6Frame *Frame
|
||||
for i := range frames {
|
||||
if frames[i].Protocol == ethernet.TypeIPv6 {
|
||||
if frames[i].Protocol == "IPv6" {
|
||||
ipv6Frame = &frames[i]
|
||||
break
|
||||
}
|
||||
@@ -476,15 +476,16 @@ func ExampleFormatter_dhcp() {
|
||||
// Output:
|
||||
// Ethernet len=14; destination=ff:ff:ff:ff:ff:ff; source=de:ad:be:ef:ca:fe; protocol=0x0800
|
||||
// IPv4 len=20; version=0x04; (Header Length)=5; (Type of Service)=0x00; (Total Length)=312; identification=0x6043; flags=0x4000; (Time to live)=0x40; protocol=0x11; checksum=0xd972; source=0.0.0.0; destination=255.255.255.255
|
||||
// UDP [RFC768] len=8; (Source port)=68; (Destination port)=67; size=292; checksum=0x0000
|
||||
// UDP len=8; (Source port)=68; (Destination port)=67; size=292; checksum=0x0000
|
||||
// DHCPv4 len=240; op=1; (Hardware Address Type)=0x01; (Hardware Address Length)=6; Hops=0x00; (Transaction ID)=0xdeadbeef; (Start Time)=0x0001; Flags=0x0000; (Client Address)=0.0.0.0; (Offered Address)=0.0.0.0; (Server Next Address)=255.255.255.255; (Relay Agent Address)=0.0.0.0; (Client Hardware Address)=de:ad:be:ef:ca:fe; options
|
||||
// (DHCP message type.)=1
|
||||
// (Parameter request list)=0x0102031a1c060f2a
|
||||
// (DHCP maximum message size)=558
|
||||
// (Requested IP address)=192.168.1.100
|
||||
// (Client identifier)="lneto-test"
|
||||
// (Hostname string)="myhost"
|
||||
// (DHCP message type.)=1
|
||||
// (Parameter request list)=0x0102031a1c060f2a
|
||||
// (DHCP maximum message size)=558
|
||||
// (Requested IP address)=192.168.1.100
|
||||
// (Client identifier)="lneto-test"
|
||||
// (Hostname string)="myhost"
|
||||
}
|
||||
|
||||
func writeOpt(dst []byte, opt dhcpv4.OptNum, data ...byte) int {
|
||||
dst[0] = byte(opt)
|
||||
dst[1] = byte(len(data))
|
||||
|
||||
+18
-22
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/netip"
|
||||
"slices"
|
||||
@@ -12,8 +11,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
_ "time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/soypat/lneto"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/ntp"
|
||||
"github.com/soypat/lneto/tcp"
|
||||
@@ -38,6 +37,7 @@ type Formatter struct {
|
||||
// FormatFrames appends the formatted frame data to the destination buffer according the Formatter state.
|
||||
// Is equivalent to [Formatter.FormatFrame] called on each Frame with the FrameSep inserted between frames.
|
||||
func (f *Formatter) FormatFrames(dst []byte, frms []Frame, pkt []byte) (_ []byte, err error) {
|
||||
debuglog("pcap:fmt:start")
|
||||
sep := f.frameSep()
|
||||
for ifrm := range frms {
|
||||
if ifrm != 0 {
|
||||
@@ -48,14 +48,16 @@ func (f *Formatter) FormatFrames(dst []byte, frms []Frame, pkt []byte) (_ []byte
|
||||
return dst, err
|
||||
}
|
||||
}
|
||||
debuglog("pcap:fmt:done")
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// FormatFrame formats a single frame's protocol, fields, and errors into dst.
|
||||
func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, err error) {
|
||||
debuglog("pcap:fmtframe:start")
|
||||
sep := f.fieldSep()
|
||||
bitlen := frm.LenBits()
|
||||
dst = appendProtocol(dst, frm.Protocol)
|
||||
dst = append(dst, frm.Protocol...)
|
||||
if bitlen%8 == 0 {
|
||||
dst = append(dst, " len="...)
|
||||
dst = strconv.AppendInt(dst, int64(bitlen/8), 10)
|
||||
@@ -70,7 +72,7 @@ func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, er
|
||||
continue
|
||||
}
|
||||
dst = append(dst, sep...)
|
||||
if field.Class == FieldClassFlags && frm.Protocol == lneto.IPProtoTCP {
|
||||
if field.Class == FieldClassFlags && frm.Protocol == "TCP" {
|
||||
// TCP flags pretty print special case.
|
||||
dst = append(dst, "flags="...)
|
||||
v, err := f.fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.Flags.IsRightAligned())
|
||||
@@ -95,6 +97,7 @@ func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, er
|
||||
}
|
||||
dst = append(dst, ')')
|
||||
}
|
||||
debuglog("pcap:fmtframe:done")
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
@@ -135,6 +138,9 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
||||
if hasSpaces {
|
||||
dst = append(dst, ')')
|
||||
}
|
||||
if field.BitLength == 0 {
|
||||
return dst, nil
|
||||
}
|
||||
dst = append(dst, '=')
|
||||
f.mubuf.Lock()
|
||||
defer f.mubuf.Unlock()
|
||||
@@ -142,6 +148,7 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
||||
if err != nil {
|
||||
return dst, err
|
||||
}
|
||||
debuglog("pcap:fmtfield:append-done")
|
||||
fieldBitStart := pktStartOff + field.FrameBitOffset
|
||||
switch field.Class {
|
||||
default:
|
||||
@@ -151,6 +158,7 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
||||
dst = append(dst, "0x"...)
|
||||
dst = hex.AppendEncode(dst, f.buf)
|
||||
case FieldClassTimestamp:
|
||||
debuglog("pcap:fmtfield:timestamp")
|
||||
// inspired by [time.RFC3339]
|
||||
const littlerfc3339 = "2006-01-02T15:04:05.9999"
|
||||
if len(f.buf) != 8 {
|
||||
@@ -158,8 +166,13 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
||||
}
|
||||
ts := ntp.TimestampFromUint64(binary.BigEndian.Uint64(f.buf))
|
||||
dst = ts.Time().AppendFormat(dst, littlerfc3339)
|
||||
debuglog("pcap:fmtfield:timestamp-done")
|
||||
case FieldClassText:
|
||||
dst = strconv.AppendQuote(dst, string(f.buf))
|
||||
debuglog("pcap:fmtfield:text")
|
||||
if len(f.buf) > 0 {
|
||||
dst = strconv.AppendQuote(dst, unsafe.String(&f.buf[0], len(f.buf)))
|
||||
}
|
||||
debuglog("pcap:fmtfield:text-done")
|
||||
case FieldClassDst, FieldClassSrc, FieldClassSize, FieldClassAddress, FieldClassOperation:
|
||||
// IP, MAC addresses and ports.
|
||||
if field.BitLength <= 16 {
|
||||
@@ -219,20 +232,3 @@ func (f *Formatter) fieldAsUint(pkt []byte, fieldBitStart, bitlen int, rightAlig
|
||||
}
|
||||
return binary.BigEndian.Uint64(f.uintBuf[:]), nil
|
||||
}
|
||||
|
||||
// appendProtocol appends the string representation of a Frame.Protocol value
|
||||
// to dst without going through fmt.Appendf reflect machinery.
|
||||
func appendProtocol(dst []byte, p any) []byte {
|
||||
switch p := p.(type) {
|
||||
case proto:
|
||||
return append(dst, string(p)...)
|
||||
case string:
|
||||
return append(dst, p...)
|
||||
case ethernet.Type:
|
||||
return append(dst, p.String()...)
|
||||
case lneto.IPProto:
|
||||
return append(dst, p.String()...)
|
||||
default:
|
||||
return fmt.Appendf(dst, "%v", p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,3 +232,11 @@ func (l logger) debug(msg string, attrs ...slog.Attr) {
|
||||
func (l logger) trace(msg string, attrs ...slog.Attr) {
|
||||
internal.LogAttrs(l.log, internal.LevelTrace, msg, attrs...)
|
||||
}
|
||||
|
||||
const enableAllocLog = false
|
||||
|
||||
func debugLog(msg string) {
|
||||
if enableAllocLog {
|
||||
internal.LogAllocs(msg)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,7 +1,6 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net"
|
||||
@@ -20,6 +19,8 @@ var (
|
||||
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
|
||||
@@ -329,14 +330,14 @@ func (conn *Conn) Demux(buf []byte, off int) (err error) {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
if off >= len(buf) {
|
||||
return errors.New("bad offset in TCPConn.Recv")
|
||||
return errBadDemuxOffset
|
||||
}
|
||||
raddr, _, id, _, err := internal.GetIPAddr(buf[:off])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn.isRaddrSet() && !bytes.Equal(conn.remoteAddr, raddr) {
|
||||
return errors.New("IP addr mismatch on TCPConn")
|
||||
if conn.isRaddrSet() && !internal.BytesEqual(conn.remoteAddr, raddr) {
|
||||
return errIPAddrMismatch
|
||||
}
|
||||
conn.trace("tcpconn.Recv", slog.Uint64("lport", uint64(conn.h.LocalPort())), slog.Uint64("rport", uint64(conn.h.remotePort)))
|
||||
err = conn.h.Recv(buf[off:])
|
||||
|
||||
+27
-16
@@ -183,6 +183,8 @@ func (flags Flags) HasAny(mask Flags) bool { return flags&mask != 0 }
|
||||
// Mask returns the flags with non-flag bits unset.
|
||||
func (flags Flags) Mask() Flags { return flags & flagMask }
|
||||
|
||||
func (flags Flags) Invalid() bool { return flags&flagMask != flags }
|
||||
|
||||
// StringFlags returns human readable flag string. i.e:
|
||||
//
|
||||
// "[SYN,ACK]"
|
||||
@@ -201,6 +203,8 @@ func (flags Flags) String() string {
|
||||
return "[FIN,ACK]"
|
||||
case pshack:
|
||||
return "[PSH,ACK]"
|
||||
case FlagFIN | FlagPSH | FlagACK:
|
||||
return "[FIN,PSH,ACK]"
|
||||
case FlagACK:
|
||||
return "[ACK]"
|
||||
case FlagSYN:
|
||||
@@ -210,41 +214,48 @@ func (flags Flags) String() string {
|
||||
case FlagRST:
|
||||
return "[RST]"
|
||||
}
|
||||
if flags&flagMask != flags {
|
||||
if flags.Invalid() {
|
||||
return strInvalidTCPFlags
|
||||
}
|
||||
buf := make([]byte, 0, 2+3*bits.OnesCount16(uint16(flags)))
|
||||
buf = append(buf, '[')
|
||||
buf = flags.AppendFormat(buf)
|
||||
buf = append(buf, ']')
|
||||
return string(buf)
|
||||
// Since Go 1.26 this should not allocate if returned string does not escape and is smaller than 32 bytes.
|
||||
// https://go.dev/blog/allocation-optimizations
|
||||
var buf [2 + 4*9]byte
|
||||
buf[0] = '['
|
||||
n := flags.format((*[36]byte)(buf[1:]))
|
||||
buf[1+n] = ']'
|
||||
return string(buf[:2+n])
|
||||
}
|
||||
|
||||
// AppendFormat appends a human readable flag string to b returning the extended buffer.
|
||||
func (flags Flags) AppendFormat(b []byte) []byte {
|
||||
var buf [36]byte
|
||||
n := flags.format(&buf)
|
||||
return append(b, buf[:n]...)
|
||||
}
|
||||
|
||||
const strInvalidTCPFlags = "<invalid TCP flags>"
|
||||
|
||||
// AppendFormat appends a human readable flag string to b returning the extended buffer.
|
||||
func (flags Flags) AppendFormat(b []byte) []byte {
|
||||
func (flags Flags) format(buf *[4 * 9]byte) (n int) {
|
||||
if flags == 0 {
|
||||
return b
|
||||
} else if flags&flagMask != flags {
|
||||
return append(b, strInvalidTCPFlags...)
|
||||
return 0
|
||||
} else if flags.Invalid() {
|
||||
return copy(buf[:], strInvalidTCPFlags)
|
||||
}
|
||||
|
||||
// String Flag const
|
||||
const flaglen = 3
|
||||
const strflags = "FINSYNRSTPSHACKURGECECWRNS "
|
||||
var addcommas bool
|
||||
for flags != 0 { // written by Github Copilot- looks OK.
|
||||
i := bits.TrailingZeros16(uint16(flags))
|
||||
if addcommas {
|
||||
b = append(b, ',')
|
||||
buf[n] = ','
|
||||
n++
|
||||
} else {
|
||||
addcommas = true
|
||||
}
|
||||
b = append(b, strflags[i*flaglen:i*flaglen+flaglen]...)
|
||||
n += copy(buf[n:], strflags[i*flaglen:i*flaglen+flaglen])
|
||||
flags &= ^(1 << i)
|
||||
}
|
||||
return b
|
||||
return n
|
||||
}
|
||||
|
||||
// State enumerates states a TCP connection progresses through during its lifetime as per RFC9293.
|
||||
|
||||
+7
-1
@@ -216,7 +216,13 @@ func (h *Handler) Recv(incomingPacket []byte) error {
|
||||
}
|
||||
}
|
||||
if h.logenabled(internal.LevelTrace) {
|
||||
h.trace("tcp.Handler:rx-done", slog.Uint64("port", uint64(h.localPort)), slog.Uint64("remoteport", uint64(remotePort)), slog.String("seg", segIncoming.String()))
|
||||
h.trace("tcp.Handler:rx-done",
|
||||
slog.Uint64("lport", uint64(h.localPort)),
|
||||
slog.Uint64("rport", uint64(remotePort)),
|
||||
slog.Uint64("seg.seq", uint64(segIncoming.SEQ)),
|
||||
slog.Uint64("seg.ack", uint64(segIncoming.ACK)),
|
||||
slog.Uint64("seg.datalen", uint64(segIncoming.DATALEN)),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net"
|
||||
@@ -293,7 +292,7 @@ func getConn(conns []handler, remotePort uint16, remoteAddr []byte) int {
|
||||
}
|
||||
gotPort := conn.RemotePort()
|
||||
gotaddr := conn.RemoteAddr()
|
||||
if remotePort == gotPort && bytes.Equal(remoteAddr, gotaddr) {
|
||||
if remotePort == gotPort && internal.BytesEqual(remoteAddr, gotaddr) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,3 +788,99 @@ func FuzzTCBActions(f *testing.F) {
|
||||
// hasPanicked = false
|
||||
})
|
||||
}
|
||||
|
||||
func TestFlagsString(t *testing.T) {
|
||||
tests := []struct {
|
||||
flags tcp.Flags
|
||||
want string
|
||||
}{
|
||||
{flags: 0, want: "[]"},
|
||||
{flags: tcp.FlagSYN, want: "[SYN]"},
|
||||
{flags: tcp.FlagACK, want: "[ACK]"},
|
||||
{flags: tcp.FlagFIN, want: "[FIN]"},
|
||||
{flags: tcp.FlagRST, want: "[RST]"},
|
||||
{flags: tcp.FlagSYN | tcp.FlagACK, want: "[SYN,ACK]"},
|
||||
{flags: tcp.FlagFIN | tcp.FlagACK, want: "[FIN,ACK]"},
|
||||
{flags: tcp.FlagPSH | tcp.FlagACK, want: "[PSH,ACK]"},
|
||||
// Multi-flag combinations.
|
||||
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagACK, want: "[FIN,SYN,ACK]"},
|
||||
{flags: tcp.FlagURG | tcp.FlagACK, want: "[ACK,URG]"},
|
||||
{flags: tcp.FlagECE | tcp.FlagCWR, want: "[ECE,CWR]"},
|
||||
{flags: tcp.FlagNS | tcp.FlagACK, want: "[ACK,NS ]"},
|
||||
// All flags set.
|
||||
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS, want: "[FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,NS ]"},
|
||||
// Invalid flags (bits above mask).
|
||||
{flags: 0xFFFF, want: "<invalid TCP flags>"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
got := tc.flags.String()
|
||||
if got != tc.want {
|
||||
t.Errorf("Flags(%#x).String() = %q; want %q", uint16(tc.flags), got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagsAppendFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
flags tcp.Flags
|
||||
want string
|
||||
}{
|
||||
{flags: 0, want: ""},
|
||||
{flags: tcp.FlagSYN, want: "SYN"},
|
||||
{flags: tcp.FlagACK, want: "ACK"},
|
||||
{flags: tcp.FlagFIN, want: "FIN"},
|
||||
{flags: tcp.FlagRST, want: "RST"},
|
||||
{flags: tcp.FlagSYN | tcp.FlagACK, want: "SYN,ACK"},
|
||||
{flags: tcp.FlagFIN | tcp.FlagACK, want: "FIN,ACK"},
|
||||
{flags: tcp.FlagPSH | tcp.FlagACK, want: "PSH,ACK"},
|
||||
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagACK, want: "FIN,SYN,ACK"},
|
||||
{flags: tcp.FlagURG | tcp.FlagACK, want: "ACK,URG"},
|
||||
{flags: tcp.FlagECE | tcp.FlagCWR, want: "ECE,CWR"},
|
||||
{flags: tcp.FlagNS | tcp.FlagACK, want: "ACK,NS "},
|
||||
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS, want: "FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,NS "},
|
||||
{flags: 0xFFFF, want: "<invalid TCP flags>"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
got := string(tc.flags.AppendFormat(nil))
|
||||
if got != tc.want {
|
||||
t.Errorf("Flags(%#x).AppendFormat(nil) = %q; want %q", uint16(tc.flags), got, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that AppendFormat appends to existing data.
|
||||
prefix := []byte("flags=")
|
||||
got := string(SYNACK.AppendFormat(prefix))
|
||||
if want := "flags=SYN,ACK"; got != want {
|
||||
t.Errorf("AppendFormat with prefix = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagsStringAppendFormatConsistency(t *testing.T) {
|
||||
// Verify String() and AppendFormat() produce consistent results
|
||||
// for all single-flag and common multi-flag values.
|
||||
allFlags := []tcp.Flags{
|
||||
0,
|
||||
tcp.FlagFIN, tcp.FlagSYN, tcp.FlagRST, tcp.FlagPSH,
|
||||
tcp.FlagACK, tcp.FlagURG, tcp.FlagECE, tcp.FlagCWR, tcp.FlagNS,
|
||||
SYNACK, FINACK, PSHACK,
|
||||
tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS,
|
||||
}
|
||||
for _, flags := range allFlags {
|
||||
str := flags.String()
|
||||
appended := string(flags.AppendFormat(nil))
|
||||
// String() wraps in brackets, AppendFormat() does not.
|
||||
if flags == 0 {
|
||||
if str != "[]" {
|
||||
t.Errorf("Flags(0).String() = %q; want %q", str, "[]")
|
||||
}
|
||||
if appended != "" {
|
||||
t.Errorf("Flags(0).AppendFormat() = %q; want empty", appended)
|
||||
}
|
||||
continue
|
||||
}
|
||||
wantStr := "[" + appended + "]"
|
||||
if str != wantStr {
|
||||
t.Errorf("Flags(%#x): String()=%q inconsistent with AppendFormat()=%q (want %q)", uint16(flags), str, appended, wantStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -7,7 +7,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errPacketQueueFull = errors.New("packet queue full")
|
||||
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 (
|
||||
@@ -55,9 +60,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 errors.New("queued packets <=0")
|
||||
return errQueuedPacketsLEZ
|
||||
} else if len(buf) < minBufferSize || len(buf) < maxqueuedPackets {
|
||||
return errors.New("invalid buffer size")
|
||||
return errInvalidBufSize
|
||||
}
|
||||
|
||||
*rtx = ringTx{
|
||||
@@ -126,7 +131,7 @@ func (rtx *ringTx) MakePacket(b []byte, currentSeq Value) (int, error) {
|
||||
}
|
||||
endSeq, ok := rtx.sentEndSeq()
|
||||
if ok && currentSeq.LessThan(endSeq) {
|
||||
return 0, errors.New("sequence number less than last sequence number")
|
||||
return 0, errSeqLessThanLast
|
||||
}
|
||||
// Reading unsent ring consumes unsent and converts it to "sent".
|
||||
unsent, _ := rtx.unsentRing()
|
||||
@@ -316,11 +321,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 errors.New("no packet to ack")
|
||||
return errNoPacketToAck
|
||||
}
|
||||
endseq := newest.endSeq()
|
||||
if endseq.LessThan(ack) {
|
||||
return errors.New("ack of unsent packet")
|
||||
return errAckUnsent
|
||||
}
|
||||
// Mark fully acked.
|
||||
for i := 0; i < len(sl.pkts); i++ {
|
||||
|
||||
+27
-1
@@ -62,7 +62,33 @@ func (stack *CapturePrinter) PrintPacket(prefix string, pkt []byte) {
|
||||
if err == nil {
|
||||
if useTimestamps {
|
||||
diff := captime.Sub(stack.origin)
|
||||
fmtbuf = strconv.AppendFloat(fmtbuf, diff.Seconds(), 'f', stack.timeprec, 32)
|
||||
ms := diff.Milliseconds()
|
||||
fmtbuf = strconv.AppendInt(fmtbuf, ms/1000, 10)
|
||||
fmtbuf = append(fmtbuf, '.')
|
||||
frac := ms % 1000
|
||||
if frac < 0 {
|
||||
frac = -frac
|
||||
}
|
||||
// Pad fractional part to timeprec digits (up to 3).
|
||||
switch {
|
||||
case stack.timeprec >= 3:
|
||||
if frac < 100 {
|
||||
fmtbuf = append(fmtbuf, '0')
|
||||
}
|
||||
if frac < 10 {
|
||||
fmtbuf = append(fmtbuf, '0')
|
||||
}
|
||||
fmtbuf = strconv.AppendInt(fmtbuf, frac, 10)
|
||||
case stack.timeprec == 2:
|
||||
frac /= 10 // truncate to centiseconds
|
||||
if frac < 10 {
|
||||
fmtbuf = append(fmtbuf, '0')
|
||||
}
|
||||
fmtbuf = strconv.AppendInt(fmtbuf, frac, 10)
|
||||
case stack.timeprec == 1:
|
||||
frac /= 100 // truncate to deciseconds
|
||||
fmtbuf = strconv.AppendInt(fmtbuf, frac, 10)
|
||||
}
|
||||
fmtbuf = append(fmtbuf, ' ')
|
||||
}
|
||||
fmtbuf = append(fmtbuf, prefix...)
|
||||
|
||||
@@ -2,6 +2,7 @@ package xnet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/soypat/lneto/dhcpv4"
|
||||
"github.com/soypat/lneto/dns"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/internal"
|
||||
"github.com/soypat/lneto/internet"
|
||||
"github.com/soypat/lneto/ntp"
|
||||
"github.com/soypat/lneto/tcp"
|
||||
@@ -566,3 +568,28 @@ func addr4(addr [4]byte, ok bool) netip.Addr {
|
||||
}
|
||||
return netip.AddrFrom4(addr)
|
||||
}
|
||||
|
||||
// Debug prints debugging information. Very useful for users when coupled with
|
||||
// the debugheaplog build tag. See [internal.LogAttrs] debugheaplog version.
|
||||
//
|
||||
// go build -tags=debugheaplog ./yourprogram
|
||||
func (s *StackAsync) Debug(msg string) {
|
||||
internal.LogAttrs(slog.Default(), slog.LevelDebug, "stackasync",
|
||||
slog.String("umsg", msg),
|
||||
slog.Uint64("sent", s.totalsent),
|
||||
slog.Uint64("recv", s.totalrecv),
|
||||
)
|
||||
}
|
||||
|
||||
// DebugErr prints debugging and error info. Very useful for users when coupled with
|
||||
// the debugheaplog build tag. See [internal.LogAttrs] debugheaplog version.
|
||||
//
|
||||
// go build -tags=debugheaplog ./yourprogram
|
||||
func (s *StackAsync) DebugErr(msg, err string) {
|
||||
internal.LogAttrs(slog.Default(), slog.LevelError, "stackasync",
|
||||
slog.String("umsg", msg),
|
||||
slog.String("err", err),
|
||||
slog.Uint64("sent", s.totalsent),
|
||||
slog.Uint64("recv", s.totalrecv),
|
||||
)
|
||||
}
|
||||
|
||||
+40
-33
@@ -439,22 +439,22 @@ func (tst *tester) TCPExchange(expect tcpExpectExchange, stack1, stack2 *StackAs
|
||||
}
|
||||
srcEth := src.HardwareAddress()
|
||||
dstEth := dst.HardwareAddress()
|
||||
if !bytes.Equal(srcEth[:], tst.getData(pcap.ProtoEthernet, pcap.FieldClassSrc)) {
|
||||
t.Errorf("mismatched ethernet src addr %x", tst.getData(pcap.ProtoEthernet, pcap.FieldClassSrc))
|
||||
if !bytes.Equal(srcEth[:], tst.getData(protoEthernet, pcap.FieldClassSrc)) {
|
||||
t.Errorf("mismatched ethernet src addr %x", tst.getData(protoEthernet, pcap.FieldClassSrc))
|
||||
}
|
||||
if !bytes.Equal(dstEth[:], tst.getData(pcap.ProtoEthernet, pcap.FieldClassDst)) {
|
||||
t.Errorf("mismatched ethernet dst addr %x", tst.getData(pcap.ProtoEthernet, pcap.FieldClassDst))
|
||||
if !bytes.Equal(dstEth[:], tst.getData(protoEthernet, pcap.FieldClassDst)) {
|
||||
t.Errorf("mismatched ethernet dst addr %x", tst.getData(protoEthernet, pcap.FieldClassDst))
|
||||
}
|
||||
if tst.getInt(ethernet.TypeIPv4, pcap.FieldClassVersion) != 4 {
|
||||
t.Errorf("did not get IP version=4, got=%d", tst.getInt(ethernet.TypeIPv4, pcap.FieldClassVersion))
|
||||
if tst.getInt(protoIPv4, pcap.FieldClassVersion) != 4 {
|
||||
t.Errorf("did not get IP version=4, got=%d", tst.getInt(protoIPv4, pcap.FieldClassVersion))
|
||||
}
|
||||
srcAddr := src.Addr()
|
||||
dstAddr := dst.Addr()
|
||||
if !bytes.Equal(srcAddr.AsSlice(), tst.getData(ethernet.TypeIPv4, pcap.FieldClassSrc)) {
|
||||
t.Errorf("mismatched ip src addr %d", tst.getData(ethernet.TypeIPv4, pcap.FieldClassSrc))
|
||||
if !bytes.Equal(srcAddr.AsSlice(), tst.getData(protoIPv4, pcap.FieldClassSrc)) {
|
||||
t.Errorf("mismatched ip src addr %d", tst.getData(protoIPv4, pcap.FieldClassSrc))
|
||||
}
|
||||
if !bytes.Equal(dstAddr.AsSlice(), tst.getData(ethernet.TypeIPv4, pcap.FieldClassDst)) {
|
||||
t.Errorf("mismatched ip dst addr %d", tst.getData(ethernet.TypeIPv4, pcap.FieldClassDst))
|
||||
if !bytes.Equal(dstAddr.AsSlice(), tst.getData(protoIPv4, pcap.FieldClassDst)) {
|
||||
t.Errorf("mismatched ip dst addr %d", tst.getData(protoIPv4, pcap.FieldClassDst))
|
||||
}
|
||||
tfrm := tst.getTCPFrame()
|
||||
|
||||
@@ -502,11 +502,11 @@ func (tst *tester) ARPExchangeOnly(querying, target *StackAsync) {
|
||||
tgtIP := target.Addr()
|
||||
|
||||
// Validate Ethernet layer (request is broadcast)
|
||||
if !bytes.Equal(qHw[:], tst.getData(pcap.ProtoEthernet, pcap.FieldClassSrc)) {
|
||||
t.Errorf("request: mismatched ethernet src addr %x", tst.getData(pcap.ProtoEthernet, pcap.FieldClassSrc))
|
||||
if !bytes.Equal(qHw[:], tst.getData(protoEthernet, pcap.FieldClassSrc)) {
|
||||
t.Errorf("request: mismatched ethernet src addr %x", tst.getData(protoEthernet, pcap.FieldClassSrc))
|
||||
}
|
||||
if !bytes.Equal(broadcast[:], tst.getData(pcap.ProtoEthernet, pcap.FieldClassDst)) {
|
||||
t.Errorf("request: expected broadcast ethernet dst addr, got %x", tst.getData(pcap.ProtoEthernet, pcap.FieldClassDst))
|
||||
if !bytes.Equal(broadcast[:], tst.getData(protoEthernet, pcap.FieldClassDst)) {
|
||||
t.Errorf("request: expected broadcast ethernet dst addr, got %x", tst.getData(protoEthernet, pcap.FieldClassDst))
|
||||
}
|
||||
|
||||
// Validate ARP request fields
|
||||
@@ -515,13 +515,13 @@ func (tst *tester) ARPExchangeOnly(querying, target *StackAsync) {
|
||||
if tst.getARPOperation() != arp.OpRequest {
|
||||
t.Errorf("request: expected ARP OpRequest, got %d", tst.getARPOperation())
|
||||
}
|
||||
if !bytes.Equal(qHw[:], tst.getFieldByClassLen(ethernet.TypeARP, pcap.FieldClassSrc, 6, 0)) {
|
||||
if !bytes.Equal(qHw[:], tst.getFieldByClassLen(protoARP, pcap.FieldClassSrc, 6, 0)) {
|
||||
t.Errorf("request: mismatched ARP sender HW")
|
||||
}
|
||||
if !bytes.Equal(qIP.AsSlice(), tst.getFieldByClassLen(ethernet.TypeARP, pcap.FieldClassSrc, 4, 0)) {
|
||||
if !bytes.Equal(qIP.AsSlice(), tst.getFieldByClassLen(protoARP, pcap.FieldClassSrc, 4, 0)) {
|
||||
t.Errorf("request: mismatched ARP sender proto")
|
||||
}
|
||||
if !bytes.Equal(tgtIP.AsSlice(), tst.getFieldByClassLen(ethernet.TypeARP, pcap.FieldClassSrc, 4, 1)) {
|
||||
if !bytes.Equal(tgtIP.AsSlice(), tst.getFieldByClassLen(protoARP, pcap.FieldClassSrc, 4, 1)) {
|
||||
t.Errorf("request: mismatched ARP target proto")
|
||||
}
|
||||
|
||||
@@ -549,27 +549,27 @@ func (tst *tester) ARPExchangeOnly(querying, target *StackAsync) {
|
||||
tst.buf = tst.buf[:n]
|
||||
|
||||
// Validate Ethernet layer (reply is unicast to querying)
|
||||
if !bytes.Equal(tgtHw[:], tst.getData(pcap.ProtoEthernet, pcap.FieldClassSrc)) {
|
||||
t.Errorf("reply: mismatched ethernet src addr %x", tst.getData(pcap.ProtoEthernet, pcap.FieldClassSrc))
|
||||
if !bytes.Equal(tgtHw[:], tst.getData(protoEthernet, pcap.FieldClassSrc)) {
|
||||
t.Errorf("reply: mismatched ethernet src addr %x", tst.getData(protoEthernet, pcap.FieldClassSrc))
|
||||
}
|
||||
if !bytes.Equal(qHw[:], tst.getData(pcap.ProtoEthernet, pcap.FieldClassDst)) {
|
||||
t.Errorf("reply: expected unicast to querying, got %x", tst.getData(pcap.ProtoEthernet, pcap.FieldClassDst))
|
||||
if !bytes.Equal(qHw[:], tst.getData(protoEthernet, pcap.FieldClassDst)) {
|
||||
t.Errorf("reply: expected unicast to querying, got %x", tst.getData(protoEthernet, pcap.FieldClassDst))
|
||||
}
|
||||
|
||||
// Validate ARP reply fields
|
||||
if tst.getARPOperation() != arp.OpReply {
|
||||
t.Errorf("reply: expected ARP OpReply, got %d", tst.getARPOperation())
|
||||
}
|
||||
if !bytes.Equal(tgtHw[:], tst.getFieldByClassLen(ethernet.TypeARP, pcap.FieldClassSrc, 6, 0)) {
|
||||
if !bytes.Equal(tgtHw[:], tst.getFieldByClassLen(protoARP, pcap.FieldClassSrc, 6, 0)) {
|
||||
t.Errorf("reply: mismatched ARP sender HW (should be target's MAC)")
|
||||
}
|
||||
if !bytes.Equal(tgtIP.AsSlice(), tst.getFieldByClassLen(ethernet.TypeARP, pcap.FieldClassSrc, 4, 0)) {
|
||||
if !bytes.Equal(tgtIP.AsSlice(), tst.getFieldByClassLen(protoARP, pcap.FieldClassSrc, 4, 0)) {
|
||||
t.Errorf("reply: mismatched ARP sender proto (should be target's IP)")
|
||||
}
|
||||
if !bytes.Equal(qHw[:], tst.getFieldByClassLen(ethernet.TypeARP, pcap.FieldClassSrc, 6, 1)) {
|
||||
if !bytes.Equal(qHw[:], tst.getFieldByClassLen(protoARP, pcap.FieldClassSrc, 6, 1)) {
|
||||
t.Errorf("reply: mismatched ARP target HW (should be querying's MAC)")
|
||||
}
|
||||
if !bytes.Equal(qIP.AsSlice(), tst.getFieldByClassLen(ethernet.TypeARP, pcap.FieldClassSrc, 4, 1)) {
|
||||
if !bytes.Equal(qIP.AsSlice(), tst.getFieldByClassLen(protoARP, pcap.FieldClassSrc, 4, 1)) {
|
||||
t.Errorf("reply: mismatched ARP target proto (should be querying's IP)")
|
||||
}
|
||||
|
||||
@@ -593,7 +593,7 @@ func (tst *tester) ARPExchangeOnly(querying, target *StackAsync) {
|
||||
func (tst *tester) getTCPFrame() tcp.Frame {
|
||||
tst.t.Helper()
|
||||
// Find the IP frame's position in the captured packet buffer.
|
||||
ipFrm := getProtoFrame(tst.frmbuf, ethernet.TypeIPv4)
|
||||
ipFrm := getProtoFrame(tst.frmbuf, protoIPv4)
|
||||
if ipFrm == nil {
|
||||
tst.t.Fatal("no IP frame in capture")
|
||||
}
|
||||
@@ -615,7 +615,7 @@ func (tst *tester) getTCPFrame() tcp.Frame {
|
||||
return frame
|
||||
}
|
||||
|
||||
func (tst *tester) getPayload(proto any) []byte {
|
||||
func (tst *tester) getPayload(proto string) []byte {
|
||||
tst.t.Helper()
|
||||
i := 0
|
||||
for i = 0; i < len(tst.frmbuf); i++ {
|
||||
@@ -633,7 +633,7 @@ func (tst *tester) getPayload(proto any) []byte {
|
||||
return tst.getData(proto, pcap.FieldClassPayload)
|
||||
}
|
||||
|
||||
func (tst *tester) getData(proto any, field pcap.FieldClass) []byte {
|
||||
func (tst *tester) getData(proto string, field pcap.FieldClass) []byte {
|
||||
tst.t.Helper()
|
||||
frm := getProtoFrame(tst.frmbuf, proto)
|
||||
if frm == nil {
|
||||
@@ -654,7 +654,7 @@ func (tst *tester) getData(proto any, field pcap.FieldClass) []byte {
|
||||
return tst.buf[bitoff/8 : bitoff/8+bitlen/8]
|
||||
}
|
||||
|
||||
func (tst *tester) getInt(proto any, field pcap.FieldClass) uint64 {
|
||||
func (tst *tester) getInt(proto string, field pcap.FieldClass) uint64 {
|
||||
tst.t.Helper()
|
||||
frm := getProtoFrame(tst.frmbuf, proto)
|
||||
if frm == nil {
|
||||
@@ -671,7 +671,7 @@ func (tst *tester) getInt(proto any, field pcap.FieldClass) uint64 {
|
||||
return v
|
||||
}
|
||||
|
||||
func getProtoFrame(frms []pcap.Frame, proto any) *pcap.Frame {
|
||||
func getProtoFrame(frms []pcap.Frame, proto string) *pcap.Frame {
|
||||
for i := range frms {
|
||||
if frms[i].Protocol == proto {
|
||||
return &frms[i]
|
||||
@@ -690,7 +690,7 @@ func setzero[T ~[]E, E any](s T) {
|
||||
// getFieldByClassLen finds a field by protocol, class, and octet length.
|
||||
// occurrence specifies which match to return (0 = first, 1 = second, etc.)
|
||||
// This is needed for ARP where sender and target fields share the same class.
|
||||
func (tst *tester) getFieldByClassLen(proto any, class pcap.FieldClass, octetLen, occurrence int) []byte {
|
||||
func (tst *tester) getFieldByClassLen(proto string, class pcap.FieldClass, octetLen, occurrence int) []byte {
|
||||
tst.t.Helper()
|
||||
frm := getProtoFrame(tst.frmbuf, proto)
|
||||
if frm == nil {
|
||||
@@ -712,7 +712,7 @@ func (tst *tester) getFieldByClassLen(proto any, class pcap.FieldClass, octetLen
|
||||
|
||||
func (tst *tester) getARPOperation() arp.Operation {
|
||||
tst.t.Helper()
|
||||
return arp.Operation(tst.getInt(ethernet.TypeARP, pcap.FieldClassOperation))
|
||||
return arp.Operation(tst.getInt(protoARP, pcap.FieldClassOperation))
|
||||
}
|
||||
|
||||
// TestTCPConn_BufferNotClearedOnPassiveClose tests that data remains readable after
|
||||
@@ -981,7 +981,7 @@ func TestStackAsync_ICMPEchoChecksum(t *testing.T) {
|
||||
SequenceNumber: 4,
|
||||
Payload: icmpPayload,
|
||||
})
|
||||
pkt[len(pkt)-1] ^= 0xFF // Corrupt ICMP payload.
|
||||
pkt[len(pkt)-1] ^= 0xFF // Corrupt ICMP payload.
|
||||
pkt = append(pkt, 0xDE, 0xAD, 0xBE, 0xEF) // Simulate Ethernet FCS.
|
||||
err = stack.Demux(pkt, 0)
|
||||
if err == nil {
|
||||
@@ -989,3 +989,10 @@ func TestStackAsync_ICMPEchoChecksum(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const (
|
||||
protoEthernet = "Ethernet"
|
||||
protoARP = "ARP"
|
||||
protoIPv4 = "IPv4"
|
||||
protoTCP = "TCP"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user