diff --git a/examples/tap/main.go b/examples/tap/main.go index 8831190..c3e780a 100644 --- a/examples/tap/main.go +++ b/examples/tap/main.go @@ -63,22 +63,24 @@ func run() error { } defer sv.Close() 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) { captime := time.Now() frames, err := cap.CaptureEthernet(nil, pkt, 0) if err == nil { - flags, src, dst := getTCPData(frames, pkt) - if src != 0 { - if flags != 0 { - fmt.Println(channel, captime.Format("15:04:05.000"), frames, flags.String(), src, "->", dst) - } else { - fmt.Println(channel, captime.Format("15:04:05.000"), frames, src, "->", dst) - } + pfbuf = append(pfbuf[:0], '[') + pfbuf, err = pf.FormatFrames(pfbuf, frames, pkt) + pfbuf = append(pfbuf, ']') + if err != nil { + fmt.Printf("%d %s !err:%s\n", channel, captime.Format("15:04:05.000"), err) } 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 { - 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() diff --git a/internet/pcap/capture.go b/internet/pcap/capture.go index 4c62c5f..71284e3 100644 --- a/internet/pcap/capture.go +++ b/internet/pcap/capture.go @@ -3,9 +3,14 @@ package pcap //go:generate stringer -type=FieldClass -linecomment -output stringers.go . import ( "encoding/binary" + "encoding/hex" "errors" "fmt" "math" + "net/netip" + "slices" + "strconv" + "strings" "github.com/soypat/lneto" "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") } field := frm.Fields[fieldIdx] - octets := (field.BitLength + 7) / 8 - 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 + return fieldAsUint(pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.RightAligned) } 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") } field := frm.Fields[fieldIdx] - fieldBitStart := frm.PacketBitOffset + field.FrameBitOffset - fieldBitEnd := fieldBitStart + field.BitLength - octets := (field.BitLength + 7) / 8 // total octets needed to represent field. + return appendField(dst, pkt, frm.PacketBitOffset+field.FrameBitOffset, field.BitLength, field.RightAligned) +} + +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 if octets+octetsStart > len(pkt) { 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 lastOctetExcessBits := fieldBitEnd % 8 if firstBitOffset == 0 { - if field.RightAligned { + if rightAligned { return dst, errors.New("invalid right aligned set for fully aligned field") } // 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<= 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 { return string(frm.AppendString(nil)) }