mirror of
https://github.com/soypat/lneto.git
synced 2026-09-10 16:49:37 +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
|
package arp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
@@ -101,7 +100,7 @@ func (h *Handler) expectSize() int {
|
|||||||
|
|
||||||
func (h *Handler) QueryResult(protoAddr []byte) (hwAddr []byte, err error) {
|
func (h *Handler) QueryResult(protoAddr []byte) (hwAddr []byte, err error) {
|
||||||
for i := range h.queries {
|
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 {
|
if !h.queries[i].querysent {
|
||||||
return nil, errors.New("query not yet sent")
|
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 {
|
func (h *Handler) DiscardQuery(protoAddr []byte) error {
|
||||||
for i := range h.queries {
|
for i := range h.queries {
|
||||||
q := &h.queries[i]
|
q := &h.queries[i]
|
||||||
if bytes.Equal(protoAddr, q.protoaddr) {
|
if internal.BytesEqual(protoAddr, q.protoaddr) {
|
||||||
q.destroy()
|
q.destroy()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -240,7 +239,7 @@ func (h *Handler) Demux(ethFrame []byte, frameOffset int) error {
|
|||||||
switch afrm.Operation() {
|
switch afrm.Operation() {
|
||||||
case OpRequest:
|
case OpRequest:
|
||||||
_, protoaddr := afrm.Target()
|
_, protoaddr := afrm.Target()
|
||||||
if !bytes.Equal(protoaddr, h.ourProtoAddr) {
|
if !internal.BytesEqual(protoaddr, h.ourProtoAddr) {
|
||||||
return nil // Not for us.
|
return nil // Not for us.
|
||||||
}
|
}
|
||||||
h.pendingResponse = h.pendingResponse[:len(h.pendingResponse)+1] // Extend pending buffer.
|
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 {
|
for i := range h.queries {
|
||||||
q := &h.queries[i]
|
q := &h.queries[i]
|
||||||
mac := q.response()
|
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...)
|
q.hwaddr = append(q.hwaddr, hwaddr...)
|
||||||
if q.dstHw != nil {
|
if q.dstHw != nil {
|
||||||
if !internal.IsZeroed(q.dstHw...) {
|
if !internal.IsZeroed(q.dstHw...) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package httpraw
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cookie implements cookie key-value parsing. Methods function similarly to eponymous [Header] methods.
|
// 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.
|
// Parse parses the cookie's buffer in place.
|
||||||
func (c *Cookie) Parse() error {
|
func (c *Cookie) Parse() error {
|
||||||
if len(c.kvs) > 0 {
|
if len(c.kvs) > 0 {
|
||||||
return errors.New("cookies already parsed, reset before parsing again")
|
return errCookiesParsed
|
||||||
}
|
}
|
||||||
off := 0
|
off := 0
|
||||||
for {
|
for {
|
||||||
|
|||||||
+20
-5
@@ -2,9 +2,9 @@ package httpraw
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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].
|
// 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.
|
// It fails if HTTP data is incomplete.
|
||||||
func (h *Header) Parse(asResponse bool) error {
|
func (h *Header) Parse(asResponse bool) error {
|
||||||
|
debuglog("http:parse:reset")
|
||||||
h.Reset(h.hbuf.buf)
|
h.Reset(h.hbuf.buf)
|
||||||
|
debuglog("http:parse:start")
|
||||||
return h.parse(asResponse)
|
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) {
|
func (h *Header) TryParse(asResponse bool) (needMoreData bool, err error) {
|
||||||
if h.flags.hasAny(flagDoneParsingHeader) {
|
if h.flags.hasAny(flagDoneParsingHeader) {
|
||||||
return false, errors.New("TryParse called after header parsed")
|
return false, errAlreadyParsed
|
||||||
} else if h.flags.hasAny(flagMangledBuffer) {
|
} else if h.flags.hasAny(flagMangledBuffer) {
|
||||||
return false, errMangledBuffer
|
return false, errMangledBuffer
|
||||||
}
|
}
|
||||||
@@ -225,15 +227,18 @@ func (h *Header) Reset(buf []byte) {
|
|||||||
panic("small buffer and flagNoBufferGrow set")
|
panic("small buffer and flagNoBufferGrow set")
|
||||||
}
|
}
|
||||||
const persistentFlags = flagNoBufferGrow
|
const persistentFlags = flagNoBufferGrow
|
||||||
|
debuglog("http:reset:hbuf")
|
||||||
h.hbuf.reset(buf)
|
h.hbuf.reset(buf)
|
||||||
*h = Header{
|
*h = Header{
|
||||||
hbuf: h.hbuf,
|
hbuf: h.hbuf,
|
||||||
flags: h.flags & persistentFlags,
|
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.
|
// 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) {
|
func (h *Header) Body() ([]byte, error) {
|
||||||
|
debuglog("http:body")
|
||||||
if h.flags.hasAny(flagMangledBuffer) {
|
if h.flags.hasAny(flagMangledBuffer) {
|
||||||
return nil, errMangledBuffer
|
return nil, errMangledBuffer
|
||||||
} else if h.flags.hasAny(flagDoneParsingHeader) {
|
} else if h.flags.hasAny(flagDoneParsingHeader) {
|
||||||
@@ -242,7 +247,14 @@ func (h *Header) Body() ([]byte, error) {
|
|||||||
return nil, errUnparsed
|
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) {
|
func (h *Header) Set(key, value string) {
|
||||||
hb := &h.hbuf
|
hb := &h.hbuf
|
||||||
var useKv *argsKV
|
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.
|
// 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 {
|
func (h *Header) Get(key string) []byte {
|
||||||
|
debuglog("http:get:start")
|
||||||
kv := h.peekHeader(key)
|
kv := h.peekHeader(key)
|
||||||
if kv.isValid() {
|
if kv.isValid() {
|
||||||
|
debuglog("http:get:found")
|
||||||
return h.hbuf.musttoken(kv.value)
|
return h.hbuf.musttoken(kv.value)
|
||||||
}
|
}
|
||||||
|
debuglog("http:get:notfound")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +353,7 @@ func (h *Header) AppendRequest(dst []byte) ([]byte, error) {
|
|||||||
if h.flags.hasAny(flagOOMReached) {
|
if h.flags.hasAny(flagOOMReached) {
|
||||||
return dst, errOOM
|
return dst, errOOM
|
||||||
} else if h.requestURI.len == 0 || h.method.len == 0 {
|
} 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 {
|
} else if len(proto) == 0 {
|
||||||
return dst, errNoProto
|
return dst, errNoProto
|
||||||
}
|
}
|
||||||
@@ -368,7 +383,7 @@ func (h *Header) AppendResponse(dst []byte) ([]byte, error) {
|
|||||||
if h.flags.hasAny(flagOOMReached) {
|
if h.flags.hasAny(flagOOMReached) {
|
||||||
return dst, errOOM
|
return dst, errOOM
|
||||||
} else if h.statusCode.len == 0 || h.statusText.len == 0 {
|
} 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 {
|
} else if len(proto) == 0 {
|
||||||
return dst, errNoProto
|
return dst, errNoProto
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-21
@@ -3,8 +3,11 @@ package httpraw
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
"slices"
|
"slices"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/soypat/lneto/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -16,8 +19,15 @@ var (
|
|||||||
errOOM = errors.New("httpraw: buffer out of memory")
|
errOOM = errors.New("httpraw: buffer out of memory")
|
||||||
// Header.Set and Header.Add mangles the buffer.
|
// 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?).
|
// 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")
|
errMangledBuffer = errors.New("httpraw: mangled buffer")
|
||||||
errNoCookies = errors.New("no cookie found")
|
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 {
|
type headerBuf struct {
|
||||||
@@ -29,6 +39,20 @@ type headerBuf struct {
|
|||||||
headers []argsKV
|
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 tokint = uint16
|
||||||
|
|
||||||
type headerSlice struct {
|
type headerSlice struct {
|
||||||
@@ -56,11 +80,16 @@ type scannerState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Header) parse(asResponse bool) (err error) {
|
func (h *Header) parse(asResponse bool) (err error) {
|
||||||
|
debuglog("http:firstline:start")
|
||||||
err = h.parseFirstLine(asResponse)
|
err = h.parseFirstLine(asResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
debuglog("http:firstline:err")
|
||||||
return 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) {
|
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) free() int { return cap(hb.buf) - len(hb.buf) }
|
||||||
|
|
||||||
func (hb *headerBuf) parseNextHeaders(ss *scannerState) {
|
func (hb *headerBuf) parseNextHeaders(ss *scannerState) {
|
||||||
|
debuglog("http:nexthdr:loop")
|
||||||
for kv := hb.next(ss); kv.isValid(); kv = hb.next(ss) {
|
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 {
|
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) {
|
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.
|
hb.off = 0 // Parsing first line resets offset.
|
||||||
var b []byte
|
var b []byte
|
||||||
hb.skipLeadingCRLF()
|
hb.skipLeadingCRLF()
|
||||||
@@ -135,6 +167,7 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags flags) (method, uri, proto
|
|||||||
if len(b) < 5 {
|
if len(b) < 5 {
|
||||||
return method, uri, proto, flags, errNeedMore
|
return method, uri, proto, flags, errNeedMore
|
||||||
}
|
}
|
||||||
|
debuglog("http:req:parse")
|
||||||
|
|
||||||
methodEnd := max(0, bytes.IndexByte(b, ' '))
|
methodEnd := max(0, bytes.IndexByte(b, ' '))
|
||||||
reqURIEnd := bytes.IndexByte(b[methodEnd+1:], ' ')
|
reqURIEnd := bytes.IndexByte(b[methodEnd+1:], ' ')
|
||||||
@@ -145,7 +178,7 @@ func (hb *headerBuf) parseFirstLineRequest(initFlags flags) (method, uri, proto
|
|||||||
flags |= flagNoHTTP11
|
flags |= flagNoHTTP11
|
||||||
}
|
}
|
||||||
} else if reqURIEnd == 0 {
|
} else if reqURIEnd == 0 {
|
||||||
return method, uri, proto, flags, errors.New("empty URI")
|
return method, uri, proto, flags, errEmptyURI
|
||||||
} else {
|
} else {
|
||||||
// No version provided.
|
// No version provided.
|
||||||
reqURIEnd = methodEnd + 1
|
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) {
|
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.
|
hb.off = 0 // Parsing first line resets offset.
|
||||||
var b []byte
|
var b []byte
|
||||||
hb.skipLeadingCRLF()
|
hb.skipLeadingCRLF()
|
||||||
@@ -166,23 +200,23 @@ func (hb *headerBuf) parseFirstLineResponse(initFlags flags) (statusCode, status
|
|||||||
if len(b) < 5 {
|
if len(b) < 5 {
|
||||||
return statusCode, statusText, flags, errNeedMore
|
return statusCode, statusText, flags, errNeedMore
|
||||||
}
|
}
|
||||||
|
debuglog("http:resp:parse")
|
||||||
|
|
||||||
statusCodeEnd := max(0, bytes.IndexByte(b, ' '))
|
statusCodeEnd := max(0, bytes.IndexByte(b, ' '))
|
||||||
if statusCodeEnd < 0 {
|
|
||||||
return statusCode, statusText, flags, errors.New("missing status code")
|
|
||||||
}
|
|
||||||
code := b[:statusCodeEnd]
|
code := b[:statusCodeEnd]
|
||||||
text := b[statusCodeEnd:]
|
text := b[statusCodeEnd:]
|
||||||
if len(code) > 3 {
|
if len(code) > 3 {
|
||||||
return statusCode, statusText, flags, errors.New("long status code")
|
return statusCode, statusText, flags, errLongStatusCode
|
||||||
}
|
}
|
||||||
for i := range code {
|
for i := range code {
|
||||||
if code[i] > '9' || code[i] < '0' {
|
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)
|
statusCode = hb.slice(code)
|
||||||
statusText = hb.slice(text)
|
statusText = hb.slice(text)
|
||||||
|
debuglog("http:resp:done")
|
||||||
return statusCode, statusText, flags, nil
|
return statusCode, statusText, flags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,17 +394,6 @@ func (hb *headerBuf) next(ss *scannerState) argsKV {
|
|||||||
return resultKV
|
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.
|
// ConnectionClose returns true if 'Connection: close' header is set or if a invalid header was found.
|
||||||
func (h *Header) ConnectionClose() bool {
|
func (h *Header) ConnectionClose() bool {
|
||||||
closed := h.flags.hasAny(flagConnClose) ||
|
closed := h.flags.hasAny(flagConnClose) ||
|
||||||
@@ -402,3 +425,11 @@ func bytes2tok(buf, value []byte) headerSlice {
|
|||||||
len: tokint(len(value)),
|
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
|
package internal
|
||||||
|
|
||||||
import "log/slog"
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LevelTrace slog.Level = slog.LevelDebug - 2
|
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 (
|
var (
|
||||||
memstats runtime.MemStats
|
|
||||||
lastAllocs uint64
|
|
||||||
|
|
||||||
timebuf [len(timefmt) * 2]byte
|
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) {
|
func LogAttrs(_ *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
n := len(now.AppendFormat(timebuf[:0], timefmt))
|
n := len(now.AppendFormat(timebuf[:0], timefmt))
|
||||||
runtime.ReadMemStats(&memstats)
|
LogAllocs(msg)
|
||||||
if memstats.TotalAlloc != lastAllocs {
|
|
||||||
print("[ALLOC] inc=", int64(memstats.TotalAlloc)-int64(lastAllocs))
|
|
||||||
print(" tot=", memstats.TotalAlloc, " seqs")
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
print("time=", unsafe.String(&timebuf[0], n), " ")
|
print("time=", unsafe.String(&timebuf[0], n), " ")
|
||||||
if level == LevelTrace {
|
if level == LevelTrace {
|
||||||
print("TRACE ")
|
print("TRACE ")
|
||||||
@@ -57,8 +49,12 @@ func LogAttrs(_ *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
println()
|
println()
|
||||||
|
allocmu.Lock()
|
||||||
runtime.ReadMemStats(&memstats)
|
runtime.ReadMemStats(&memstats)
|
||||||
if memstats.TotalAlloc != lastAllocs {
|
if lastAllocs != memstats.TotalAlloc {
|
||||||
lastAllocs = memstats.TotalAlloc
|
print("alloc increase in heaplog")
|
||||||
}
|
}
|
||||||
|
lastAllocs = memstats.TotalAlloc
|
||||||
|
lastMallocs = memstats.Mallocs
|
||||||
|
allocmu.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package ltesto
|
package ltesto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
"github.com/soypat/lneto"
|
"github.com/soypat/lneto"
|
||||||
"github.com/soypat/lneto/ethernet"
|
"github.com/soypat/lneto/ethernet"
|
||||||
|
"github.com/soypat/lneto/internal"
|
||||||
"github.com/soypat/lneto/ipv4"
|
"github.com/soypat/lneto/ipv4"
|
||||||
"github.com/soypat/lneto/ipv4/icmpv4"
|
"github.com/soypat/lneto/ipv4/icmpv4"
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
@@ -134,9 +134,9 @@ func (gen *PacketGen) AppendRandomIPv4TCPPacket(dst []byte, rng *rand.Rand, seg
|
|||||||
switch {
|
switch {
|
||||||
case gen.SrcTCP != tfrm.SourcePort():
|
case gen.SrcTCP != tfrm.SourcePort():
|
||||||
panic("IP options overwrite TCP header")
|
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")
|
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")
|
panic("bad tcp options written, ensure tcp options length is multiple of 4")
|
||||||
case *ifrm.DestinationAddr() != gen.DstIPv4:
|
case *ifrm.DestinationAddr() != gen.DstIPv4:
|
||||||
panic("IP options overwrite own header")
|
panic("IP options overwrite own header")
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
// IsZeroed returns true if all arguments are set to their zero value.
|
// IsZeroed returns true if all arguments are set to their zero value.
|
||||||
func IsZeroed[T comparable](a ...T) bool {
|
func IsZeroed[T comparable](a ...T) bool {
|
||||||
var z T
|
var z T
|
||||||
@@ -65,3 +67,15 @@ func SliceReclaim[T any](ptr *[]T) *T {
|
|||||||
}
|
}
|
||||||
return &(*ptr)[n]
|
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 {
|
func (s *cbnode) Demux(carrierData []byte, frameOffset int) error {
|
||||||
|
debugLog("cbnode:pre-demux")
|
||||||
return s._demux(carrierData, frameOffset)
|
return s._demux(carrierData, frameOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+53
-22
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/soypat/lneto"
|
"github.com/soypat/lneto"
|
||||||
@@ -29,12 +30,6 @@ var (
|
|||||||
ErrFieldByClassNotFound = errors.New("pcap: field by class not found")
|
ErrFieldByClassNotFound = errors.New("pcap: field by class not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
type proto string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ProtoEthernet proto = "Ethernet"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PacketBreakdown struct {
|
type PacketBreakdown struct {
|
||||||
hdr httpraw.Header
|
hdr httpraw.Header
|
||||||
dmsg dns.Message
|
dmsg dns.Message
|
||||||
@@ -66,8 +61,10 @@ 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, 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() {
|
if pc.validator().HasError() {
|
||||||
return dst, pc.validator().ErrPop()
|
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()
|
etype := efrm.EtherTypeOrSize()
|
||||||
end := 14*octet + bitOffset
|
end := 14*octet + bitOffset
|
||||||
if etype.IsSize() {
|
if etype.IsSize() {
|
||||||
@@ -102,12 +101,14 @@ func (pc *PacketBreakdown) CaptureEthernet(dst []Frame, pkt []byte, bitOffset in
|
|||||||
case ethernet.TypeIPv6:
|
case ethernet.TypeIPv6:
|
||||||
dst, err = pc.CaptureIPv6(dst, pkt, end)
|
dst, err = pc.CaptureIPv6(dst, pkt, end)
|
||||||
default:
|
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
|
return dst, err
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
if bitOffset%8 != 0 {
|
if bitOffset%8 != 0 {
|
||||||
return dst, errors.New("ARP must be parsed at byte boundary")
|
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()
|
return dst, pc.validator().ErrPop()
|
||||||
}
|
}
|
||||||
|
|
||||||
finfo := reclaimFrame(&dst, ethernet.TypeARP, bitOffset, baseARPFields[:])
|
finfo := reclaimFrame(&dst, "ARP", bitOffset, baseARPFields[:])
|
||||||
const varstart = 8 * octet
|
const varstart = 8 * octet
|
||||||
_, hlen := afrm.Hardware()
|
_, hlen := afrm.Hardware()
|
||||||
_, plen := afrm.Protocol()
|
_, 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) {
|
func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||||
|
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, 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() {
|
if pc.validator().HasError() {
|
||||||
return dst, pc.validator().ErrPop()
|
return dst, pc.validator().ErrPop()
|
||||||
}
|
}
|
||||||
reclaimFrame(&dst, ethernet.TypeIPv6, bitOffset, baseIPv6Fields[:])
|
reclaimFrame(&dst, "IPv6", bitOffset, baseIPv6Fields[:])
|
||||||
|
debuglog("pcap:ipv6:reclaimed")
|
||||||
proto := ifrm6.NextHeader()
|
proto := ifrm6.NextHeader()
|
||||||
end := bitOffset + 40*octet
|
end := bitOffset + 40*octet
|
||||||
var protoErrs []error
|
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) {
|
func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||||
|
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, 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() {
|
if pc.validator().HasError() {
|
||||||
return dst, pc.validator().ErrPop()
|
return dst, pc.validator().ErrPop()
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:ipv4:validated")
|
||||||
// limit packet to the actual IPv4 frame size
|
// limit packet to the actual IPv4 frame size
|
||||||
pkt = pkt[:bitOffset/8+int(ifrm4.TotalLength())]
|
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()
|
options := ifrm4.Options()
|
||||||
if len(options) > 0 {
|
if len(options) > 0 {
|
||||||
finfo.Fields = append(finfo.Fields, FrameField{
|
finfo.Fields = append(finfo.Fields, FrameField{
|
||||||
@@ -221,6 +227,7 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
|
|||||||
if ifrm4.CalculateHeaderCRC() != 0 {
|
if ifrm4.CalculateHeaderCRC() != 0 {
|
||||||
finfo.Errors = append(finfo.Errors, lneto.ErrBadCRC)
|
finfo.Errors = append(finfo.Errors, lneto.ErrBadCRC)
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:ipv4:crc-done")
|
||||||
proto := ifrm4.Protocol()
|
proto := ifrm4.Protocol()
|
||||||
end := bitOffset + octet*ifrm4.HeaderLength()
|
end := bitOffset + octet*ifrm4.HeaderLength()
|
||||||
var protoErrs []error
|
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...)
|
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) {
|
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)
|
nextFrame := len(dst)
|
||||||
switch proto {
|
switch proto {
|
||||||
case lneto.IPProtoTCP:
|
case lneto.IPProtoTCP:
|
||||||
@@ -275,20 +284,22 @@ func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt
|
|||||||
case lneto.IPProtoUDPLite:
|
case lneto.IPProtoUDPLite:
|
||||||
dst, err = pc.CaptureUDP(dst, pkt, bitOffset)
|
dst, err = pc.CaptureUDP(dst, pkt, bitOffset)
|
||||||
if len(dst) > nextFrame {
|
if len(dst) > nextFrame {
|
||||||
dst[nextFrame].Protocol = lneto.IPProtoUDPLite
|
dst[nextFrame].Protocol = "UDPLite"
|
||||||
}
|
}
|
||||||
case lneto.IPProtoICMP:
|
case lneto.IPProtoICMP:
|
||||||
dst, err = pc.CaptureICMPv4(dst, pkt, bitOffset)
|
dst, err = pc.CaptureICMPv4(dst, pkt, bitOffset)
|
||||||
default:
|
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 {
|
if len(ipProtoErrs) > 0 && len(dst) > nextFrame {
|
||||||
dst[nextFrame].Errors = append(dst[nextFrame].Errors, ipProtoErrs...)
|
dst[nextFrame].Errors = append(dst[nextFrame].Errors, ipProtoErrs...)
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:ipproto:done")
|
||||||
return dst, err
|
return dst, err
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
if bitOffset%8 != 0 {
|
if bitOffset%8 != 0 {
|
||||||
return dst, errors.New("TCP must be parsed at byte boundary")
|
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() {
|
if pc.validator().HasError() {
|
||||||
return dst, pc.validator().ErrPop()
|
return dst, pc.validator().ErrPop()
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:tcp:validated")
|
||||||
end := bitOffset + octet*tfrm.HeaderLength()
|
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()
|
options := tfrm.Options()
|
||||||
if len(options) > 0 {
|
if len(options) > 0 {
|
||||||
finfo.Fields = append(finfo.Fields, FrameField{
|
finfo.Fields = append(finfo.Fields, FrameField{
|
||||||
@@ -312,15 +325,19 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
|
|||||||
}
|
}
|
||||||
payload := tfrm.Payload()
|
payload := tfrm.Payload()
|
||||||
if len(payload) > 0 {
|
if len(payload) > 0 {
|
||||||
|
debuglog("pcap:tcp:http-start")
|
||||||
dst, err = pc.CaptureHTTP(dst, pkt, end)
|
dst, err = pc.CaptureHTTP(dst, pkt, end)
|
||||||
|
debuglog("pcap:tcp:http-done")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
|
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:tcp:done")
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
if bitOffset%8 != 0 {
|
if bitOffset%8 != 0 {
|
||||||
return dst, errors.New("UDP must be parsed at byte boundary")
|
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() {
|
if pc.validator().HasError() {
|
||||||
return dst, pc.validator().ErrPop()
|
return dst, pc.validator().ErrPop()
|
||||||
}
|
}
|
||||||
reclaimFrame(&dst, lneto.IPProtoUDP, bitOffset, baseUDPFields[:])
|
reclaimFrame(&dst, "UDP", bitOffset, baseUDPFields[:])
|
||||||
|
debuglog("pcap:udp:reclaimed")
|
||||||
end := bitOffset + 8*octet
|
end := bitOffset + 8*octet
|
||||||
payload := ufrm.Payload()
|
payload := ufrm.Payload()
|
||||||
dstport := ufrm.DestinationPort()
|
dstport := ufrm.DestinationPort()
|
||||||
@@ -347,10 +365,12 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
|
reclaimRemainingFrame(&dst, unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:udp:done")
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
if bitOffset%8 != 0 {
|
if bitOffset%8 != 0 {
|
||||||
return dst, errors.New("ICMPv4 must be parsed at byte boundary")
|
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
|
return dst, err
|
||||||
}
|
}
|
||||||
|
|
||||||
finfo := reclaimFrame(&dst, lneto.IPProtoICMP, bitOffset, baseICMPv4Fields[:])
|
finfo := reclaimFrame(&dst, "ICMP", bitOffset, baseICMPv4Fields[:])
|
||||||
|
|
||||||
// Add type-specific fields.
|
// Add type-specific fields.
|
||||||
switch ifrm.Type() {
|
switch ifrm.Type() {
|
||||||
@@ -541,9 +561,9 @@ func httpBodyClass(contentType, body []byte) FieldClass {
|
|||||||
switch {
|
switch {
|
||||||
case bytes.HasPrefix(mediaType, []byte("text/")):
|
case bytes.HasPrefix(mediaType, []byte("text/")):
|
||||||
return FieldClassText
|
return FieldClassText
|
||||||
case bytes.Equal(mediaType, []byte("application/json")),
|
case internal.BytesEqual(mediaType, []byte("application/json")),
|
||||||
bytes.Equal(mediaType, []byte("application/javascript")),
|
internal.BytesEqual(mediaType, []byte("application/javascript")),
|
||||||
bytes.Equal(mediaType, []byte("application/xml")):
|
internal.BytesEqual(mediaType, []byte("application/xml")):
|
||||||
return FieldClassText
|
return FieldClassText
|
||||||
case bytes.HasSuffix(mediaType, []byte("+json")),
|
case bytes.HasSuffix(mediaType, []byte("+json")),
|
||||||
bytes.HasSuffix(mediaType, []byte("+xml")):
|
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) {
|
func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||||
|
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, 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 {
|
if err != nil {
|
||||||
return dst, err
|
return dst, err
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:http:parsed")
|
||||||
hdrLen := pc.hdr.BufferParsed()
|
hdrLen := pc.hdr.BufferParsed()
|
||||||
body, _ := pc.hdr.Body()
|
body, _ := pc.hdr.Body()
|
||||||
bodyClass := httpBodyClass(pc.hdr.Get("Content-Type"), 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,
|
BitLength: len(body) * octet,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
debuglog("pcap:http:done")
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,7 +649,7 @@ func (ff Flags) IsRightAligned() bool { return ff&FlagRightAligned != 0 }
|
|||||||
|
|
||||||
type Frame struct {
|
type Frame struct {
|
||||||
PacketBitOffset int
|
PacketBitOffset int
|
||||||
Protocol any
|
Protocol string
|
||||||
Fields []FrameField
|
Fields []FrameField
|
||||||
Errors []error
|
Errors []error
|
||||||
}
|
}
|
||||||
@@ -1306,7 +1329,7 @@ var baseNTPFields = [...]FrameField{
|
|||||||
// reclaimFrame extends dst via [internal.SliceReclaim], resets the reclaimed Frame
|
// reclaimFrame extends dst via [internal.SliceReclaim], resets the reclaimed Frame
|
||||||
// with the given protocol and bit offset while preserving Fields and Errors
|
// with the given protocol and bit offset while preserving Fields and Errors
|
||||||
// backing arrays, and appends baseFields. Returns the Frame for further modification.
|
// 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 := internal.SliceReclaim(dst)
|
||||||
*finfo = Frame{
|
*finfo = Frame{
|
||||||
PacketBitOffset: bitOffset,
|
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
|
// reclaimRemainingFrame extends dst via SliceReclaim and populates the reclaimed
|
||||||
// Frame as a single-field "remaining payload" frame, reusing the old Fields backing array.
|
// 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 := reclaimFrame(dst, proto, pktBitOffset, nil)
|
||||||
finfo.Fields = append(finfo.Fields, FrameField{
|
finfo.Fields = append(finfo.Fields, FrameField{
|
||||||
Class: class,
|
Class: class,
|
||||||
BitLength: pktBitLen - pktBitOffset,
|
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.
|
// Find IPv6 frame.
|
||||||
var ipv6Frame *Frame
|
var ipv6Frame *Frame
|
||||||
for i := range frames {
|
for i := range frames {
|
||||||
if frames[i].Protocol == ethernet.TypeIPv6 {
|
if frames[i].Protocol == "IPv6" {
|
||||||
ipv6Frame = &frames[i]
|
ipv6Frame = &frames[i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -476,15 +476,16 @@ func ExampleFormatter_dhcp() {
|
|||||||
// Output:
|
// Output:
|
||||||
// Ethernet len=14; destination=ff:ff:ff:ff:ff:ff; source=de:ad:be:ef:ca:fe; protocol=0x0800
|
// 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
|
// 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
|
// 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
|
// (DHCP message type.)=1
|
||||||
// (Parameter request list)=0x0102031a1c060f2a
|
// (Parameter request list)=0x0102031a1c060f2a
|
||||||
// (DHCP maximum message size)=558
|
// (DHCP maximum message size)=558
|
||||||
// (Requested IP address)=192.168.1.100
|
// (Requested IP address)=192.168.1.100
|
||||||
// (Client identifier)="lneto-test"
|
// (Client identifier)="lneto-test"
|
||||||
// (Hostname string)="myhost"
|
// (Hostname string)="myhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeOpt(dst []byte, opt dhcpv4.OptNum, data ...byte) int {
|
func writeOpt(dst []byte, opt dhcpv4.OptNum, data ...byte) int {
|
||||||
dst[0] = byte(opt)
|
dst[0] = byte(opt)
|
||||||
dst[1] = byte(len(data))
|
dst[1] = byte(len(data))
|
||||||
|
|||||||
+18
-22
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -12,8 +11,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
_ "time"
|
_ "time"
|
||||||
|
"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"
|
||||||
@@ -38,6 +37,7 @@ type Formatter struct {
|
|||||||
// FormatFrames appends the formatted frame data to the destination buffer according the Formatter state.
|
// 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.
|
// 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) {
|
func (f *Formatter) FormatFrames(dst []byte, frms []Frame, pkt []byte) (_ []byte, err error) {
|
||||||
|
debuglog("pcap:fmt:start")
|
||||||
sep := f.frameSep()
|
sep := f.frameSep()
|
||||||
for ifrm := range frms {
|
for ifrm := range frms {
|
||||||
if ifrm != 0 {
|
if ifrm != 0 {
|
||||||
@@ -48,14 +48,16 @@ func (f *Formatter) FormatFrames(dst []byte, frms []Frame, pkt []byte) (_ []byte
|
|||||||
return dst, err
|
return dst, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:fmt:done")
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatFrame formats a single frame's protocol, fields, and errors into dst.
|
// 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) {
|
func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, err error) {
|
||||||
|
debuglog("pcap:fmtframe:start")
|
||||||
sep := f.fieldSep()
|
sep := f.fieldSep()
|
||||||
bitlen := frm.LenBits()
|
bitlen := frm.LenBits()
|
||||||
dst = appendProtocol(dst, frm.Protocol)
|
dst = append(dst, frm.Protocol...)
|
||||||
if bitlen%8 == 0 {
|
if bitlen%8 == 0 {
|
||||||
dst = append(dst, " len="...)
|
dst = append(dst, " len="...)
|
||||||
dst = strconv.AppendInt(dst, int64(bitlen/8), 10)
|
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
|
continue
|
||||||
}
|
}
|
||||||
dst = append(dst, sep...)
|
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.
|
// TCP flags pretty print special case.
|
||||||
dst = append(dst, "flags="...)
|
dst = append(dst, "flags="...)
|
||||||
v, err := f.fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.Flags.IsRightAligned())
|
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, ')')
|
dst = append(dst, ')')
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:fmtframe:done")
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +138,9 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
|||||||
if hasSpaces {
|
if hasSpaces {
|
||||||
dst = append(dst, ')')
|
dst = append(dst, ')')
|
||||||
}
|
}
|
||||||
|
if field.BitLength == 0 {
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
dst = append(dst, '=')
|
dst = append(dst, '=')
|
||||||
f.mubuf.Lock()
|
f.mubuf.Lock()
|
||||||
defer f.mubuf.Unlock()
|
defer f.mubuf.Unlock()
|
||||||
@@ -142,6 +148,7 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return dst, err
|
return dst, err
|
||||||
}
|
}
|
||||||
|
debuglog("pcap:fmtfield:append-done")
|
||||||
fieldBitStart := pktStartOff + field.FrameBitOffset
|
fieldBitStart := pktStartOff + field.FrameBitOffset
|
||||||
switch field.Class {
|
switch field.Class {
|
||||||
default:
|
default:
|
||||||
@@ -151,6 +158,7 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
|||||||
dst = append(dst, "0x"...)
|
dst = append(dst, "0x"...)
|
||||||
dst = hex.AppendEncode(dst, f.buf)
|
dst = hex.AppendEncode(dst, f.buf)
|
||||||
case FieldClassTimestamp:
|
case FieldClassTimestamp:
|
||||||
|
debuglog("pcap:fmtfield:timestamp")
|
||||||
// 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 {
|
||||||
@@ -158,8 +166,13 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
|||||||
}
|
}
|
||||||
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)
|
||||||
|
debuglog("pcap:fmtfield:timestamp-done")
|
||||||
case FieldClassText:
|
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:
|
case FieldClassDst, FieldClassSrc, FieldClassSize, FieldClassAddress, FieldClassOperation:
|
||||||
// IP, MAC addresses and ports.
|
// IP, MAC addresses and ports.
|
||||||
if field.BitLength <= 16 {
|
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
|
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) {
|
func (l logger) trace(msg string, attrs ...slog.Attr) {
|
||||||
internal.LogAttrs(l.log, internal.LevelTrace, msg, attrs...)
|
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
|
package tcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
@@ -20,6 +19,8 @@ var (
|
|||||||
errNoRemoteAddr = errors.New("tcp: no remote address established")
|
errNoRemoteAddr = errors.New("tcp: no remote address established")
|
||||||
errInvalidIP = errors.New("tcp: invalid IP")
|
errInvalidIP = errors.New("tcp: invalid IP")
|
||||||
errMismatchedIPVersion = errors.New("mismatched IP version")
|
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
|
||||||
@@ -329,14 +330,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 errors.New("bad offset in TCPConn.Recv")
|
return errBadDemuxOffset
|
||||||
}
|
}
|
||||||
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() && !bytes.Equal(conn.remoteAddr, raddr) {
|
if conn.isRaddrSet() && !internal.BytesEqual(conn.remoteAddr, raddr) {
|
||||||
return errors.New("IP addr mismatch on TCPConn")
|
return errIPAddrMismatch
|
||||||
}
|
}
|
||||||
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:])
|
||||||
|
|||||||
+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.
|
// Mask returns the flags with non-flag bits unset.
|
||||||
func (flags Flags) Mask() Flags { return flags & flagMask }
|
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:
|
// StringFlags returns human readable flag string. i.e:
|
||||||
//
|
//
|
||||||
// "[SYN,ACK]"
|
// "[SYN,ACK]"
|
||||||
@@ -201,6 +203,8 @@ func (flags Flags) String() string {
|
|||||||
return "[FIN,ACK]"
|
return "[FIN,ACK]"
|
||||||
case pshack:
|
case pshack:
|
||||||
return "[PSH,ACK]"
|
return "[PSH,ACK]"
|
||||||
|
case FlagFIN | FlagPSH | FlagACK:
|
||||||
|
return "[FIN,PSH,ACK]"
|
||||||
case FlagACK:
|
case FlagACK:
|
||||||
return "[ACK]"
|
return "[ACK]"
|
||||||
case FlagSYN:
|
case FlagSYN:
|
||||||
@@ -210,41 +214,48 @@ func (flags Flags) String() string {
|
|||||||
case FlagRST:
|
case FlagRST:
|
||||||
return "[RST]"
|
return "[RST]"
|
||||||
}
|
}
|
||||||
if flags&flagMask != flags {
|
if flags.Invalid() {
|
||||||
return strInvalidTCPFlags
|
return strInvalidTCPFlags
|
||||||
}
|
}
|
||||||
buf := make([]byte, 0, 2+3*bits.OnesCount16(uint16(flags)))
|
// Since Go 1.26 this should not allocate if returned string does not escape and is smaller than 32 bytes.
|
||||||
buf = append(buf, '[')
|
// https://go.dev/blog/allocation-optimizations
|
||||||
buf = flags.AppendFormat(buf)
|
var buf [2 + 4*9]byte
|
||||||
buf = append(buf, ']')
|
buf[0] = '['
|
||||||
return string(buf)
|
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>"
|
const strInvalidTCPFlags = "<invalid TCP flags>"
|
||||||
|
|
||||||
// AppendFormat appends a human readable flag string to b returning the extended buffer.
|
func (flags Flags) format(buf *[4 * 9]byte) (n int) {
|
||||||
func (flags Flags) AppendFormat(b []byte) []byte {
|
|
||||||
if flags == 0 {
|
if flags == 0 {
|
||||||
return b
|
return 0
|
||||||
} else if flags&flagMask != flags {
|
} else if flags.Invalid() {
|
||||||
return append(b, strInvalidTCPFlags...)
|
return copy(buf[:], strInvalidTCPFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String Flag const
|
|
||||||
const flaglen = 3
|
const flaglen = 3
|
||||||
const strflags = "FINSYNRSTPSHACKURGECECWRNS "
|
const strflags = "FINSYNRSTPSHACKURGECECWRNS "
|
||||||
var addcommas bool
|
var addcommas bool
|
||||||
for flags != 0 { // written by Github Copilot- looks OK.
|
for flags != 0 { // written by Github Copilot- looks OK.
|
||||||
i := bits.TrailingZeros16(uint16(flags))
|
i := bits.TrailingZeros16(uint16(flags))
|
||||||
if addcommas {
|
if addcommas {
|
||||||
b = append(b, ',')
|
buf[n] = ','
|
||||||
|
n++
|
||||||
} else {
|
} else {
|
||||||
addcommas = true
|
addcommas = true
|
||||||
}
|
}
|
||||||
b = append(b, strflags[i*flaglen:i*flaglen+flaglen]...)
|
n += copy(buf[n:], strflags[i*flaglen:i*flaglen+flaglen])
|
||||||
flags &= ^(1 << i)
|
flags &= ^(1 << i)
|
||||||
}
|
}
|
||||||
return b
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// State enumerates states a TCP connection progresses through during its lifetime as per RFC9293.
|
// 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) {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,7 +1,6 @@
|
|||||||
package tcp
|
package tcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
@@ -293,7 +292,7 @@ func getConn(conns []handler, remotePort uint16, remoteAddr []byte) int {
|
|||||||
}
|
}
|
||||||
gotPort := conn.RemotePort()
|
gotPort := conn.RemotePort()
|
||||||
gotaddr := conn.RemoteAddr()
|
gotaddr := conn.RemoteAddr()
|
||||||
if remotePort == gotPort && bytes.Equal(remoteAddr, gotaddr) {
|
if remotePort == gotPort && internal.BytesEqual(remoteAddr, gotaddr) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -788,3 +788,99 @@ func FuzzTCBActions(f *testing.F) {
|
|||||||
// hasPanicked = false
|
// 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 (
|
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 (
|
const (
|
||||||
@@ -55,9 +60,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 errors.New("queued packets <=0")
|
return errQueuedPacketsLEZ
|
||||||
} else if len(buf) < minBufferSize || len(buf) < maxqueuedPackets {
|
} else if len(buf) < minBufferSize || len(buf) < maxqueuedPackets {
|
||||||
return errors.New("invalid buffer size")
|
return errInvalidBufSize
|
||||||
}
|
}
|
||||||
|
|
||||||
*rtx = ringTx{
|
*rtx = ringTx{
|
||||||
@@ -126,7 +131,7 @@ func (rtx *ringTx) MakePacket(b []byte, currentSeq Value) (int, error) {
|
|||||||
}
|
}
|
||||||
endSeq, ok := rtx.sentEndSeq()
|
endSeq, ok := rtx.sentEndSeq()
|
||||||
if ok && currentSeq.LessThan(endSeq) {
|
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".
|
// Reading unsent ring consumes unsent and converts it to "sent".
|
||||||
unsent, _ := rtx.unsentRing()
|
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 {
|
func (sl *sentlist) RecvAck(ack Value, bufsize int) error {
|
||||||
newest := sl.Newest()
|
newest := sl.Newest()
|
||||||
if newest == nil {
|
if newest == nil {
|
||||||
return errors.New("no packet to ack")
|
return errNoPacketToAck
|
||||||
}
|
}
|
||||||
endseq := newest.endSeq()
|
endseq := newest.endSeq()
|
||||||
if endseq.LessThan(ack) {
|
if endseq.LessThan(ack) {
|
||||||
return errors.New("ack of unsent packet")
|
return errAckUnsent
|
||||||
}
|
}
|
||||||
// Mark fully acked.
|
// Mark fully acked.
|
||||||
for i := 0; i < len(sl.pkts); i++ {
|
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 err == nil {
|
||||||
if useTimestamps {
|
if useTimestamps {
|
||||||
diff := captime.Sub(stack.origin)
|
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, ' ')
|
||||||
}
|
}
|
||||||
fmtbuf = append(fmtbuf, prefix...)
|
fmtbuf = append(fmtbuf, prefix...)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package xnet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"github.com/soypat/lneto/dhcpv4"
|
"github.com/soypat/lneto/dhcpv4"
|
||||||
"github.com/soypat/lneto/dns"
|
"github.com/soypat/lneto/dns"
|
||||||
"github.com/soypat/lneto/ethernet"
|
"github.com/soypat/lneto/ethernet"
|
||||||
|
"github.com/soypat/lneto/internal"
|
||||||
"github.com/soypat/lneto/internet"
|
"github.com/soypat/lneto/internet"
|
||||||
"github.com/soypat/lneto/ntp"
|
"github.com/soypat/lneto/ntp"
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
@@ -566,3 +568,28 @@ func addr4(addr [4]byte, ok bool) netip.Addr {
|
|||||||
}
|
}
|
||||||
return netip.AddrFrom4(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()
|
srcEth := src.HardwareAddress()
|
||||||
dstEth := dst.HardwareAddress()
|
dstEth := dst.HardwareAddress()
|
||||||
if !bytes.Equal(srcEth[:], tst.getData(pcap.ProtoEthernet, pcap.FieldClassSrc)) {
|
if !bytes.Equal(srcEth[:], tst.getData(protoEthernet, pcap.FieldClassSrc)) {
|
||||||
t.Errorf("mismatched ethernet src addr %x", tst.getData(pcap.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)) {
|
if !bytes.Equal(dstEth[:], tst.getData(protoEthernet, pcap.FieldClassDst)) {
|
||||||
t.Errorf("mismatched ethernet dst addr %x", tst.getData(pcap.ProtoEthernet, pcap.FieldClassDst))
|
t.Errorf("mismatched ethernet dst addr %x", tst.getData(protoEthernet, pcap.FieldClassDst))
|
||||||
}
|
}
|
||||||
if tst.getInt(ethernet.TypeIPv4, pcap.FieldClassVersion) != 4 {
|
if tst.getInt(protoIPv4, pcap.FieldClassVersion) != 4 {
|
||||||
t.Errorf("did not get IP version=4, got=%d", tst.getInt(ethernet.TypeIPv4, pcap.FieldClassVersion))
|
t.Errorf("did not get IP version=4, got=%d", tst.getInt(protoIPv4, pcap.FieldClassVersion))
|
||||||
}
|
}
|
||||||
srcAddr := src.Addr()
|
srcAddr := src.Addr()
|
||||||
dstAddr := dst.Addr()
|
dstAddr := dst.Addr()
|
||||||
if !bytes.Equal(srcAddr.AsSlice(), 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(ethernet.TypeIPv4, 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)) {
|
if !bytes.Equal(dstAddr.AsSlice(), tst.getData(protoIPv4, pcap.FieldClassDst)) {
|
||||||
t.Errorf("mismatched ip dst addr %d", tst.getData(ethernet.TypeIPv4, pcap.FieldClassDst))
|
t.Errorf("mismatched ip dst addr %d", tst.getData(protoIPv4, pcap.FieldClassDst))
|
||||||
}
|
}
|
||||||
tfrm := tst.getTCPFrame()
|
tfrm := tst.getTCPFrame()
|
||||||
|
|
||||||
@@ -502,11 +502,11 @@ func (tst *tester) ARPExchangeOnly(querying, target *StackAsync) {
|
|||||||
tgtIP := target.Addr()
|
tgtIP := target.Addr()
|
||||||
|
|
||||||
// Validate Ethernet layer (request is broadcast)
|
// Validate Ethernet layer (request is broadcast)
|
||||||
if !bytes.Equal(qHw[:], 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(pcap.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)) {
|
if !bytes.Equal(broadcast[:], tst.getData(protoEthernet, pcap.FieldClassDst)) {
|
||||||
t.Errorf("request: expected broadcast ethernet dst addr, got %x", tst.getData(pcap.ProtoEthernet, pcap.FieldClassDst))
|
t.Errorf("request: expected broadcast ethernet dst addr, got %x", tst.getData(protoEthernet, pcap.FieldClassDst))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate ARP request fields
|
// Validate ARP request fields
|
||||||
@@ -515,13 +515,13 @@ func (tst *tester) ARPExchangeOnly(querying, target *StackAsync) {
|
|||||||
if tst.getARPOperation() != arp.OpRequest {
|
if tst.getARPOperation() != arp.OpRequest {
|
||||||
t.Errorf("request: expected ARP OpRequest, got %d", tst.getARPOperation())
|
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")
|
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")
|
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")
|
t.Errorf("request: mismatched ARP target proto")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,27 +549,27 @@ func (tst *tester) ARPExchangeOnly(querying, target *StackAsync) {
|
|||||||
tst.buf = tst.buf[:n]
|
tst.buf = tst.buf[:n]
|
||||||
|
|
||||||
// Validate Ethernet layer (reply is unicast to querying)
|
// Validate Ethernet layer (reply is unicast to querying)
|
||||||
if !bytes.Equal(tgtHw[:], 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(pcap.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)) {
|
if !bytes.Equal(qHw[:], tst.getData(protoEthernet, pcap.FieldClassDst)) {
|
||||||
t.Errorf("reply: expected unicast to querying, got %x", tst.getData(pcap.ProtoEthernet, pcap.FieldClassDst))
|
t.Errorf("reply: expected unicast to querying, got %x", tst.getData(protoEthernet, pcap.FieldClassDst))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate ARP reply fields
|
// Validate ARP reply fields
|
||||||
if tst.getARPOperation() != arp.OpReply {
|
if tst.getARPOperation() != arp.OpReply {
|
||||||
t.Errorf("reply: expected ARP OpReply, got %d", tst.getARPOperation())
|
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)")
|
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)")
|
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)")
|
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)")
|
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 {
|
func (tst *tester) getTCPFrame() tcp.Frame {
|
||||||
tst.t.Helper()
|
tst.t.Helper()
|
||||||
// Find the IP frame's position in the captured packet buffer.
|
// 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 {
|
if ipFrm == nil {
|
||||||
tst.t.Fatal("no IP frame in capture")
|
tst.t.Fatal("no IP frame in capture")
|
||||||
}
|
}
|
||||||
@@ -615,7 +615,7 @@ func (tst *tester) getTCPFrame() tcp.Frame {
|
|||||||
return frame
|
return frame
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tst *tester) getPayload(proto any) []byte {
|
func (tst *tester) getPayload(proto string) []byte {
|
||||||
tst.t.Helper()
|
tst.t.Helper()
|
||||||
i := 0
|
i := 0
|
||||||
for i = 0; i < len(tst.frmbuf); i++ {
|
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)
|
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()
|
tst.t.Helper()
|
||||||
frm := getProtoFrame(tst.frmbuf, proto)
|
frm := getProtoFrame(tst.frmbuf, proto)
|
||||||
if frm == nil {
|
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]
|
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()
|
tst.t.Helper()
|
||||||
frm := getProtoFrame(tst.frmbuf, proto)
|
frm := getProtoFrame(tst.frmbuf, proto)
|
||||||
if frm == nil {
|
if frm == nil {
|
||||||
@@ -671,7 +671,7 @@ func (tst *tester) getInt(proto any, field pcap.FieldClass) uint64 {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProtoFrame(frms []pcap.Frame, proto any) *pcap.Frame {
|
func getProtoFrame(frms []pcap.Frame, proto string) *pcap.Frame {
|
||||||
for i := range frms {
|
for i := range frms {
|
||||||
if frms[i].Protocol == proto {
|
if frms[i].Protocol == proto {
|
||||||
return &frms[i]
|
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.
|
// getFieldByClassLen finds a field by protocol, class, and octet length.
|
||||||
// occurrence specifies which match to return (0 = first, 1 = second, etc.)
|
// 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.
|
// 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()
|
tst.t.Helper()
|
||||||
frm := getProtoFrame(tst.frmbuf, proto)
|
frm := getProtoFrame(tst.frmbuf, proto)
|
||||||
if frm == nil {
|
if frm == nil {
|
||||||
@@ -712,7 +712,7 @@ func (tst *tester) getFieldByClassLen(proto any, class pcap.FieldClass, octetLen
|
|||||||
|
|
||||||
func (tst *tester) getARPOperation() arp.Operation {
|
func (tst *tester) getARPOperation() arp.Operation {
|
||||||
tst.t.Helper()
|
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
|
// TestTCPConn_BufferNotClearedOnPassiveClose tests that data remains readable after
|
||||||
@@ -981,7 +981,7 @@ func TestStackAsync_ICMPEchoChecksum(t *testing.T) {
|
|||||||
SequenceNumber: 4,
|
SequenceNumber: 4,
|
||||||
Payload: icmpPayload,
|
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.
|
pkt = append(pkt, 0xDE, 0xAD, 0xBE, 0xEF) // Simulate Ethernet FCS.
|
||||||
err = stack.Demux(pkt, 0)
|
err = stack.Demux(pkt, 0)
|
||||||
if err == nil {
|
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