add pcap formatting

This commit is contained in:
Patricio Whittingslow
2025-12-31 02:17:44 -03:00
parent 8307403c5c
commit 5f0edf62a9
2 changed files with 163 additions and 25 deletions
+11 -9
View File
@@ -63,22 +63,24 @@ func run() error {
} }
defer sv.Close() defer sv.Close()
var cap pcap.PacketBreakdown var cap pcap.PacketBreakdown
pf := pcap.Formatter{
FilterClasses: []pcap.FieldClass{pcap.FieldClassDst, pcap.FieldClassSrc, pcap.FieldClassSize, pcap.FieldClassFlags},
}
var pfbuf []byte
sv.OnTransfer(func(channel int, pkt []byte) { sv.OnTransfer(func(channel int, pkt []byte) {
captime := time.Now() captime := time.Now()
frames, err := cap.CaptureEthernet(nil, pkt, 0) frames, err := cap.CaptureEthernet(nil, pkt, 0)
if err == nil { if err == nil {
flags, src, dst := getTCPData(frames, pkt) pfbuf = append(pfbuf[:0], '[')
if src != 0 { pfbuf, err = pf.FormatFrames(pfbuf, frames, pkt)
if flags != 0 { pfbuf = append(pfbuf, ']')
fmt.Println(channel, captime.Format("15:04:05.000"), frames, flags.String(), src, "->", dst) if err != nil {
} else { fmt.Printf("%d %s !err:%s\n", channel, captime.Format("15:04:05.000"), err)
fmt.Println(channel, captime.Format("15:04:05.000"), frames, src, "->", dst)
}
} else { } else {
fmt.Println(channel, captime.Format("15:04:05.000"), frames) fmt.Printf("%d %s %s\n", channel, captime.Format("15:04:05.000"), pfbuf)
} }
} else { } else {
fmt.Println(channel, captime.Format("15:04:05.000"), "ERR", frames, err.Error()) fmt.Println(channel, captime.Format("15:04:05.000"), "cap ERR", frames, err.Error())
} }
}) })
hwaddr, err := sv.HardwareAddress6() hwaddr, err := sv.HardwareAddress6()
+152 -16
View File
@@ -3,9 +3,14 @@ package pcap
//go:generate stringer -type=FieldClass -linecomment -output stringers.go . //go:generate stringer -type=FieldClass -linecomment -output stringers.go .
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math" "math"
"net/netip"
"slices"
"strconv"
"strings"
"github.com/soypat/lneto" "github.com/soypat/lneto"
"github.com/soypat/lneto/arp" "github.com/soypat/lneto/arp"
@@ -530,17 +535,7 @@ func (frm Frame) FieldAsUint(fieldIdx int, pkt []byte) (uint64, error) {
return badUint64, errors.New("invalid field index") return badUint64, errors.New("invalid field index")
} }
field := frm.Fields[fieldIdx] field := frm.Fields[fieldIdx]
octets := (field.BitLength + 7) / 8 return fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.RightAligned)
if octets > 8 {
return badUint64, errors.New("field too long to be represented by uint64")
}
var buf [8]byte
_, err := frm.AppendField(buf[8-octets:8-octets], fieldIdx, pkt)
if err != nil {
return badUint64, err
}
v := binary.BigEndian.Uint64(buf[:])
return v, nil
} }
func (frm Frame) AppendField(dst []byte, fieldIdx int, pkt []byte) ([]byte, error) { func (frm Frame) AppendField(dst []byte, fieldIdx int, pkt []byte) ([]byte, error) {
@@ -548,9 +543,27 @@ func (frm Frame) AppendField(dst []byte, fieldIdx int, pkt []byte) ([]byte, erro
return dst, errors.New("invalid field index") return dst, errors.New("invalid field index")
} }
field := frm.Fields[fieldIdx] field := frm.Fields[fieldIdx]
fieldBitStart := frm.PacketBitOffset + field.FrameBitOffset return appendField(dst, pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.RightAligned)
fieldBitEnd := fieldBitStart + field.BitLength }
octets := (field.BitLength + 7) / 8 // total octets needed to represent field.
func fieldAsUint(pkt []byte, fieldBitStart, bitlen int, rightAligned bool) (uint64, error) {
const badUint64 = math.MaxUint64
octets := (bitlen + 7) / 8
if octets > 8 {
return badUint64, errors.New("field too long to be represented by uint64")
}
var buf [8]byte
_, err := appendField(buf[8-octets:8-octets], pkt, fieldBitStart, bitlen, rightAligned)
if err != nil {
return badUint64, err
}
v := binary.BigEndian.Uint64(buf[:])
return v, nil
}
func appendField(dst, pkt []byte, fieldBitStart, bitlen int, rightAligned bool) ([]byte, error) {
fieldBitEnd := fieldBitStart + bitlen
octets := (bitlen + 7) / 8 // total octets needed to represent field.
octetsStart := fieldBitStart / 8 octetsStart := fieldBitStart / 8
if octets+octetsStart > len(pkt) { if octets+octetsStart > len(pkt) {
return dst, errors.New("buffer overflow") return dst, errors.New("buffer overflow")
@@ -558,7 +571,7 @@ func (frm Frame) AppendField(dst []byte, fieldIdx int, pkt []byte) ([]byte, erro
firstBitOffset := fieldBitStart % 8 firstBitOffset := fieldBitStart % 8
lastOctetExcessBits := fieldBitEnd % 8 lastOctetExcessBits := fieldBitEnd % 8
if firstBitOffset == 0 { if firstBitOffset == 0 {
if field.RightAligned { if rightAligned {
return dst, errors.New("invalid right aligned set for fully aligned field") return dst, errors.New("invalid right aligned set for fully aligned field")
} }
// Optimized path: field starts at byte boundary. // Optimized path: field starts at byte boundary.
@@ -570,7 +583,7 @@ func (frm Frame) AppendField(dst []byte, fieldIdx int, pkt []byte) ([]byte, erro
} }
mask := byte(1<<firstBitOffset) - 1 mask := byte(1<<firstBitOffset) - 1
if field.RightAligned { if rightAligned {
if lastOctetExcessBits == 0 { if lastOctetExcessBits == 0 {
// Right aligned with no loose trailing bits. i.e: TCP flags. // Right aligned with no loose trailing bits. i.e: TCP flags.
dst = append(dst, pkt[octetsStart]&mask) dst = append(dst, pkt[octetsStart]&mask)
@@ -599,6 +612,129 @@ func (frm Frame) AppendField(dst []byte, fieldIdx int, pkt []byte) ([]byte, erro
return dst, nil return dst, nil
} }
type Formatter struct {
FieldSep string
FrameSep string
FilterClasses []FieldClass
buf []byte
}
func (f *Formatter) FormatFrames(dst []byte, frms []Frame, pkt []byte) (_ []byte, err error) {
sep := f.FrameSep
if sep == "" {
sep = " | "
}
for ifrm := range frms {
if ifrm != 0 {
dst = append(dst, sep...)
}
dst, err = f.FormatFrame(dst, frms[ifrm], pkt)
if err != nil {
return dst, err
}
}
return dst, nil
}
func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, err error) {
sep := f.FieldSep
if sep == "" {
sep = "; " // default field separator
}
bitlen := frm.LenBits()
if bitlen%8 == 0 {
dst = fmt.Appendf(dst, "%s len=%d", frm.Protocol, bitlen/8)
} else {
dst = fmt.Appendf(dst, "%s bitlen=%d", frm.Protocol, bitlen)
}
for ifield := range frm.Fields {
field := frm.Fields[ifield]
if f.filterField(field) {
continue
}
dst = append(dst, sep...)
if field.Class == FieldClassFlags && frm.Protocol == lneto.IPProtoTCP {
dst = append(dst, "flags="...)
v, err := fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.RightAligned)
if err != nil {
return dst, err
}
dst = tcp.Flags(v).AppendFormat(dst)
continue
}
dst, err = f.formatField(dst, frm.PacketBitOffset, field, pkt)
if err != nil {
return dst, err
}
}
return dst, nil
}
func (f *Formatter) filterField(field FrameField) bool {
return f.FilterClasses != nil && !slices.Contains(f.FilterClasses, field.Class)
}
func (f *Formatter) FormatField(dst []byte, pktStartOff int, field FrameField, pkt []byte) (_ []byte, err error) {
return f.formatField(dst, pktStartOff, field, pkt)
}
func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, pkt []byte) (_ []byte, err error) {
name := field.Name
if name == "" {
name = field.Class.String()
}
hasSpaces := strings.IndexByte(name, ' ') >= 0
if hasSpaces {
dst = append(dst, '(')
}
dst = append(dst, name...)
if hasSpaces {
dst = append(dst, ')')
}
dst = append(dst, '=')
f.buf, err = appendField(f.buf[:0], pkt, field.FrameBitOffset+pktStartOff, field.BitLength, field.RightAligned)
if err != nil {
return dst, err
}
fieldBitStart := pktStartOff + field.FrameBitOffset
switch field.Class {
default:
fallthrough
case FieldClassChecksum, FieldClassID, FieldClassFlags, FieldClassOptions, FieldClassAddress:
// Binary data to be printed as hexadecimal.
dst = append(dst, "0x"...)
dst = hex.AppendEncode(dst, f.buf)
case FieldClassDst, FieldClassSrc, FieldClassSize:
// IP, MAC addresses and ports.
if field.BitLength <= 16 {
v, err := fieldAsUint(pkt, fieldBitStart, field.BitLength, field.RightAligned)
if err != nil {
return dst, err
}
dst = strconv.AppendUint(dst, v, 10)
} else if field.BitLength == 4*8 {
dst = netip.AddrFrom4([4]byte(f.buf)).AppendTo(dst)
} else if field.BitLength == 6*8 {
for i := range f.buf {
if i != 0 {
dst = append(dst, ':')
}
if f.buf[i] < 16 {
dst = append(dst, '0')
}
dst = strconv.AppendUint(dst, uint64(f.buf[i]), 16)
}
} else if field.BitLength == 16*8 {
dst = netip.AddrFrom16([16]byte(f.buf)).AppendTo(dst)
} else {
dst = append(dst, "0x"...)
dst = hex.AppendEncode(dst, f.buf)
}
}
return dst, err
}
func (frm Frame) String() string { func (frm Frame) String() string {
return string(frm.AppendString(nil)) return string(frm.AppendString(nil))
} }