mirror of
https://github.com/soypat/lneto.git
synced 2026-09-02 04:49:02 +00:00
+11
-9
@@ -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()
|
||||||
|
|||||||
+160
-20
@@ -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,17 +583,21 @@ 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)
|
||||||
dst = append(dst, pkt[octetsStart+1:octetsStart+octets]...)
|
dst = append(dst, pkt[octetsStart+1:octetsStart+octets]...)
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
// Right aligned with trailing bits. i.e: ???
|
// Right aligned with trailing bits. i.e: IPv6 Traffic Class.
|
||||||
for i := 1; i < octets; i++ {
|
// Field spans an extra byte, so need octets+1 bytes from packet.
|
||||||
b := pkt[octetsStart+i] >> (8 - lastOctetExcessBits)
|
if octets+octetsStart+1 > len(pkt) {
|
||||||
b |= pkt[octetsStart+i-1] & mask
|
return dst, errors.New("buffer overflow")
|
||||||
|
}
|
||||||
|
for i := 0; i < octets; i++ {
|
||||||
|
b := (pkt[octetsStart+i] & mask) << (8 - firstBitOffset)
|
||||||
|
b |= pkt[octetsStart+i+1] >> firstBitOffset
|
||||||
dst = append(dst, b)
|
dst = append(dst, b)
|
||||||
}
|
}
|
||||||
return dst, nil
|
return dst, nil
|
||||||
@@ -599,6 +616,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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package pcap
|
package pcap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"github.com/soypat/lneto/http/httpraw"
|
"github.com/soypat/lneto/http/httpraw"
|
||||||
"github.com/soypat/lneto/internal/ltesto"
|
"github.com/soypat/lneto/internal/ltesto"
|
||||||
"github.com/soypat/lneto/ipv4"
|
"github.com/soypat/lneto/ipv4"
|
||||||
|
"github.com/soypat/lneto/ipv6"
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,3 +123,244 @@ func TestCap(t *testing.T) {
|
|||||||
t.Errorf("want %q HTTP body, got %q", httpBody, gotBody)
|
t.Errorf("want %q HTTP body, got %q", httpBody, gotBody)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRightAlignedFields tests extraction of fields that span byte boundaries
|
||||||
|
// with right-aligned output, such as IPv6 Traffic Class and Flow Label.
|
||||||
|
func TestRightAlignedFields(t *testing.T) {
|
||||||
|
// Build a minimal IPv6 packet with known Traffic Class and Flow Label values.
|
||||||
|
// IPv6 header: 40 bytes minimum.
|
||||||
|
// Byte 0-3: Version (4 bits) + Traffic Class (8 bits) + Flow Label (20 bits)
|
||||||
|
const (
|
||||||
|
wantVersion = 6
|
||||||
|
wantTrafficClass = 0xAB // 8 bits at bit offset 4
|
||||||
|
wantFlowLabel = 0x000C_DEF0 // 20 bits at bit offset 12 (we'll use 0xCDEF0 masked to 20 bits = 0xDEF0)
|
||||||
|
)
|
||||||
|
// Actually flow label is 20 bits, so max is 0xFFFFF. Use 0xDEF01 masked.
|
||||||
|
const wantFlow20 = 0xDEF01 & 0xFFFFF // 0xDEF01
|
||||||
|
|
||||||
|
var pkt [14 + 40 + 20]byte // Ethernet + IPv6 header + TCP header
|
||||||
|
// Set up Ethernet frame.
|
||||||
|
efrm, _ := ethernet.NewFrame(pkt[:])
|
||||||
|
efrm.SetEtherType(ethernet.TypeIPv6)
|
||||||
|
|
||||||
|
// Set up IPv6 header manually.
|
||||||
|
i6frm, _ := ipv6.NewFrame(efrm.Payload())
|
||||||
|
i6frm.SetVersionTrafficAndFlow(wantVersion, ipv6.ToS(wantTrafficClass), wantFlow20)
|
||||||
|
i6frm.SetPayloadLength(20) // TCP header size
|
||||||
|
i6frm.SetNextHeader(lneto.IPProtoTCP)
|
||||||
|
i6frm.SetHopLimit(64)
|
||||||
|
|
||||||
|
// Set up minimal TCP header.
|
||||||
|
tfrm, _ := tcp.NewFrame(i6frm.Payload())
|
||||||
|
tfrm.SetOffsetAndFlags(5, 0) // 5 words = 20 bytes, no flags
|
||||||
|
|
||||||
|
// Verify our setup is correct.
|
||||||
|
gotVer, gotToS, gotFlow := i6frm.VersionTrafficAndFlow()
|
||||||
|
if gotVer != wantVersion {
|
||||||
|
t.Fatalf("setup: version mismatch: got %d, want %d", gotVer, wantVersion)
|
||||||
|
}
|
||||||
|
if uint8(gotToS) != wantTrafficClass {
|
||||||
|
t.Fatalf("setup: traffic class mismatch: got 0x%02x, want 0x%02x", gotToS, wantTrafficClass)
|
||||||
|
}
|
||||||
|
if gotFlow != wantFlow20 {
|
||||||
|
t.Fatalf("setup: flow label mismatch: got 0x%05x, want 0x%05x", gotFlow, wantFlow20)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture the packet.
|
||||||
|
var pbreak PacketBreakdown
|
||||||
|
frames, err := pbreak.CaptureEthernet(nil, pkt[:], 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(frames) < 2 {
|
||||||
|
t.Fatalf("expected at least 2 frames (Ethernet + IPv6), got %d", len(frames))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find IPv6 frame.
|
||||||
|
var ipv6Frame *Frame
|
||||||
|
for i := range frames {
|
||||||
|
if frames[i].Protocol == ethernet.TypeIPv6 {
|
||||||
|
ipv6Frame = &frames[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ipv6Frame == nil {
|
||||||
|
t.Fatal("IPv6 frame not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to get field by name.
|
||||||
|
getByName := func(name string) (uint64, error) {
|
||||||
|
for i, f := range ipv6Frame.Fields {
|
||||||
|
if f.Name == name {
|
||||||
|
return ipv6Frame.FieldAsUint(i, pkt[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Traffic Class (Type of Service) - 8 bits at bit offset 4, right-aligned.
|
||||||
|
gotTrafficClass, err := getByName("Type of Service")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get Traffic Class: %v", err)
|
||||||
|
}
|
||||||
|
if uint8(gotTrafficClass) != wantTrafficClass {
|
||||||
|
t.Errorf("Traffic Class: got 0x%02x, want 0x%02x", gotTrafficClass, wantTrafficClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Flow Label - 20 bits at bit offset 12, right-aligned.
|
||||||
|
gotFlowLabel, err := getByName("Flow Label")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get Flow Label: %v", err)
|
||||||
|
}
|
||||||
|
if uint32(gotFlowLabel) != wantFlow20 {
|
||||||
|
t.Errorf("Flow Label: got 0x%05x, want 0x%05x", gotFlowLabel, wantFlow20)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Version - 4 bits at bit offset 0, not right-aligned.
|
||||||
|
gotVersion, err := getByName("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get Version: %v", err)
|
||||||
|
}
|
||||||
|
if len(ipv6Frame.Fields) > 0 && ipv6Frame.Fields[0].Class == FieldClassVersion {
|
||||||
|
gotVersion, _ = ipv6Frame.FieldAsUint(0, pkt[:])
|
||||||
|
}
|
||||||
|
if uint8(gotVersion) != wantVersion {
|
||||||
|
t.Errorf("Version: got %d, want %d", gotVersion, wantVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAppendFieldRightAligned directly tests the appendField function
|
||||||
|
// with right-aligned fields that have trailing bits.
|
||||||
|
func TestAppendFieldRightAligned(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
pkt []byte
|
||||||
|
fieldBitStart int
|
||||||
|
bitlen int
|
||||||
|
rightAligned bool
|
||||||
|
wantData []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// IPv6 Traffic Class: bits 4-11 (8 bits spanning bytes 0-1)
|
||||||
|
name: "IPv6 Traffic Class 0xAB",
|
||||||
|
pkt: []byte{0x6A, 0xB0, 0x00, 0x00}, // Version=6, TC=0xAB, Flow=0
|
||||||
|
fieldBitStart: 4,
|
||||||
|
bitlen: 8,
|
||||||
|
rightAligned: true,
|
||||||
|
wantData: []byte{0xAB},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// IPv6 Traffic Class with different value
|
||||||
|
name: "IPv6 Traffic Class 0xFF",
|
||||||
|
pkt: []byte{0x6F, 0xF0, 0x00, 0x00}, // Version=6, TC=0xFF, Flow=0
|
||||||
|
fieldBitStart: 4,
|
||||||
|
bitlen: 8,
|
||||||
|
rightAligned: true,
|
||||||
|
wantData: []byte{0xFF},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// IPv6 Traffic Class at minimum
|
||||||
|
name: "IPv6 Traffic Class 0x00",
|
||||||
|
pkt: []byte{0x60, 0x00, 0x00, 0x00}, // Version=6, TC=0x00, Flow=0
|
||||||
|
fieldBitStart: 4,
|
||||||
|
bitlen: 8,
|
||||||
|
rightAligned: true,
|
||||||
|
wantData: []byte{0x00},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// IPv6 Flow Label: bits 12-31 (20 bits spanning bytes 1-3)
|
||||||
|
name: "IPv6 Flow Label 0xDEF01",
|
||||||
|
pkt: []byte{0x60, 0x0D, 0xEF, 0x01}, // Version=6, TC=0, Flow=0xDEF01
|
||||||
|
fieldBitStart: 12,
|
||||||
|
bitlen: 20,
|
||||||
|
rightAligned: true,
|
||||||
|
wantData: []byte{0x0D, 0xEF, 0x01}, // 20 bits right-aligned in 3 bytes
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Flow Label max value
|
||||||
|
name: "IPv6 Flow Label 0xFFFFF",
|
||||||
|
pkt: []byte{0x60, 0xFF, 0xFF, 0xFF}, // Version=6, TC=0, Flow=0xFFFFF
|
||||||
|
fieldBitStart: 12,
|
||||||
|
bitlen: 20,
|
||||||
|
rightAligned: true,
|
||||||
|
wantData: []byte{0x0F, 0xFF, 0xFF}, // 20 bits right-aligned in 3 bytes
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got, err := appendField(nil, tc.pkt, tc.fieldBitStart, tc.bitlen, tc.rightAligned)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("appendField error: %v", err)
|
||||||
|
}
|
||||||
|
if len(got) != len(tc.wantData) {
|
||||||
|
t.Fatalf("length mismatch: got %d bytes, want %d bytes", len(got), len(tc.wantData))
|
||||||
|
}
|
||||||
|
for i := range got {
|
||||||
|
if got[i] != tc.wantData[i] {
|
||||||
|
t.Errorf("byte %d: got 0x%02x, want 0x%02x", i, got[i], tc.wantData[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Also verify as uint64 for single/double byte cases.
|
||||||
|
if len(tc.wantData) <= 8 {
|
||||||
|
gotVal, err := fieldAsUint(tc.pkt, tc.fieldBitStart, tc.bitlen, tc.rightAligned)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("fieldAsUint error: %v", err)
|
||||||
|
}
|
||||||
|
var wantVal uint64
|
||||||
|
for _, b := range tc.wantData {
|
||||||
|
wantVal = wantVal<<8 | uint64(b)
|
||||||
|
}
|
||||||
|
if gotVal != wantVal {
|
||||||
|
t.Errorf("as uint: got 0x%x, want 0x%x", gotVal, wantVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFieldAsUintRightAligned tests fieldAsUint with the same buffer
|
||||||
|
// used in the appendField fix, ensuring consistency.
|
||||||
|
func TestFieldAsUintRightAligned(t *testing.T) {
|
||||||
|
// Build IPv6 first 4 bytes with known values.
|
||||||
|
// Format: VVVV TTTT TTTT FFFF FFFF FFFF FFFF FFFF
|
||||||
|
// V=version (4 bits), T=traffic class (8 bits), F=flow label (20 bits)
|
||||||
|
var buf [4]byte
|
||||||
|
const version = 6
|
||||||
|
const trafficClass = 0xAB
|
||||||
|
const flowLabel = 0xCDEF0
|
||||||
|
|
||||||
|
// Encode: version in bits 0-3, traffic class in bits 4-11, flow label in bits 12-31
|
||||||
|
val := uint32(version)<<28 | uint32(trafficClass)<<20 | flowLabel
|
||||||
|
binary.BigEndian.PutUint32(buf[:], val)
|
||||||
|
|
||||||
|
// Verify encoding.
|
||||||
|
t.Logf("Encoded bytes: %02x %02x %02x %02x", buf[0], buf[1], buf[2], buf[3])
|
||||||
|
|
||||||
|
// Test version extraction (bits 0-3, 4 bits, left-aligned)
|
||||||
|
gotVersion, err := fieldAsUint(buf[:], 0, 4, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("version extraction failed: %v", err)
|
||||||
|
}
|
||||||
|
if gotVersion != version {
|
||||||
|
t.Errorf("version: got %d, want %d", gotVersion, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test traffic class extraction (bits 4-11, 8 bits, right-aligned)
|
||||||
|
gotTC, err := fieldAsUint(buf[:], 4, 8, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("traffic class extraction failed: %v", err)
|
||||||
|
}
|
||||||
|
if gotTC != trafficClass {
|
||||||
|
t.Errorf("traffic class: got 0x%02x, want 0x%02x", gotTC, trafficClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test flow label extraction (bits 12-31, 20 bits, right-aligned)
|
||||||
|
gotFlow, err := fieldAsUint(buf[:], 12, 20, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("flow label extraction failed: %v", err)
|
||||||
|
}
|
||||||
|
if gotFlow != flowLabel {
|
||||||
|
t.Errorf("flow label: got 0x%05x, want 0x%05x", gotFlow, flowLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user