package inet import ( "bytes" "encoding/hex" "strings" "testing" "pcap-web-tools/internal/pcap" ) func mustHex(t *testing.T, s string) []byte { t.Helper() b, err := hex.DecodeString(strings.ReplaceAll(s, " ", "")) if err != nil { t.Fatal(err) } return b } // The want packets were generated by Fix and validated with tshark's checksum checks. // Each test zeroes the length/checksum fields and checks Fix restores them. func TestFix(t *testing.T) { tests := []struct { name string want string hdrLen int // header length passed to Fix (0 = unknown) zero [][2]int // byte ranges cleared before calling Fix changes int // expected number of changes, if not len(zero) notes []string // expected substrings of the notes, in order }{ { name: "IPv4 UDP", want: "45000028000140004011b970c0a80001c0a80002303904d20014117668656c6c6f2c207063617021", zero: [][2]int{{2, 4}, {10, 12}, {24, 26}, {26, 28}}, }, { name: "IPv4 with options, TCP, odd length payload", want: "46000036123440004006118b0a0000010a00000201010100c350005000000001000000005018fffff9700000474554202f0d0a0d0a2e", hdrLen: 44, zero: [][2]int{{2, 4}, {10, 12}, {36, 37}, {40, 42}}, }, { name: "IPv4 ICMP", want: "45000021000100004001aa22c0a80001080808080800f54babcd00017071727374", zero: [][2]int{{2, 4}, {10, 12}, {22, 24}}, }, { name: "IPv4 SCTP", want: "45000034000040004084b8f2c0a80001c0a800020b5904d200000000a05c7b6a0100001400000001000100000001000100000001", zero: [][2]int{{2, 4}, {10, 12}, {28, 32}}, notes: []string{"only recalculated when the layer 3 field is used"}, }, { name: "IPv6 hop-by-hop UDP", want: "600000000016000020010db800000000000000000000000120010db800000000000000000000000211000000000000001f900035000e1bfcdeadbeefcafe", zero: [][2]int{{4, 6}, {52, 54}, {54, 56}}, }, { name: "IPv6 ICMPv6", want: "60000000000d3a40fe800000000000000000000000000001fe800000000000000000000000000002800046b7123400016162636465", zero: [][2]int{{4, 6}, {42, 44}}, }, { name: "IPv6 segment routing header UDP uses final destination", want: "6000000000242b4020010db800000000000000000000000120010db80000000000000000000000aa110204010000000020010db80000000000000000000000ff1f900035000c7f9901020304", zero: [][2]int{{4, 6}, {68, 70}, {70, 72}}, }, { name: "IPv4 options, TCP SYN with options and no payload", want: "460000401234400040067f7d0a0000010a00000294040000c35000500000000100000000a002ffff705b0000020405b40402080a000000010000000001030307", hdrLen: 64, zero: [][2]int{{2, 4}, {10, 12}, {36, 37}, {40, 42}}, }, { name: "IPv4 options, TCP with options and payload", want: "4600003d1234400040067f800a0000010a00000294040000c350005000000001000000008018ffffd4a500000101080a0000000100000000474554202f", hdrLen: 56, zero: [][2]int{{2, 4}, {10, 12}, {36, 37}, {40, 42}}, }, { name: "IPv4 options, SCTP DATA without padding", want: "460000391234400040847f060a0000010a00000294040000c35004d200000001866854b30003001500000001000100000000000068656c6c6f", hdrLen: 52, zero: [][2]int{{2, 4}, {10, 12}, {32, 36}, {38, 40}}, notes: []string{"not padded"}, }, { name: "IPv4 options, SCTP DATA with padding keeps correct chunk length", want: "4600003c1234400040847f030a0000010a00000294040000c35004d200000001ef89c5b10003001500000001000100000000000068656c6c6f000000", hdrLen: 52, zero: [][2]int{{2, 4}, {10, 12}, {32, 36}}, changes: 4, }, { name: "SCTP INIT entirely in layer 3", want: "460000381234400040847f070a0000010a00000294040000c35004d200000001c3d6cafe0100001400000001000100000001000100000001", hdrLen: 56, zero: [][2]int{{2, 4}, {10, 12}, {32, 36}, {38, 40}}, }, { name: "IPv6 SCTP SACK bundled before DATA", want: "600000000034844020010db800000000000000000000000120010db8000000000000000000000002c35004d200000001634380c903000010000000000000ffff00000000000300180000000100010000000000000102030405060708", hdrLen: 84, zero: [][2]int{{4, 6}, {48, 52}, {70, 72}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { want := mustHex(t, tt.want) pkt := bytes.Clone(want) for _, z := range tt.zero { clear(pkt[z[0]:z[1]]) } res, err := Fix(pkt, tt.hdrLen) if err != nil { t.Fatal(err) } if !bytes.Equal(pkt, want) { t.Errorf("got %x\nwant %x", pkt, want) } changes := tt.changes if changes == 0 { changes = len(tt.zero) } if len(res.Changes) != changes { t.Errorf("got %d changes, want %d: %+v", len(res.Changes), changes, res.Changes) } checkNotes(t, res.Notes, tt.notes) }) } } func checkNotes(t *testing.T, got, want []string) { t.Helper() if len(got) != len(want) { t.Errorf("notes %q, want %q", got, want) return } for i := range want { if !strings.Contains(got[i], want[i]) { t.Errorf("note %q does not contain %q", got[i], want[i]) } } } func TestFixNotes(t *testing.T) { const ( ip4TCP = "45000000000140004006 0000 c0a80001c0a80002" tcpHdr = "c35000500000000100000000 5002ffff 0000 0000" ip4SCTP = "45000000000140004084 0000 c0a80001c0a80002" sctpHdr = "c35004d2 00000001 00000000" ) tests := []struct { name string pkt string hdrLen int notes []string }{ {"TCP header in payload field", ip4TCP + tcpHdr, 20, []string{"TCP header is in the payload field"}}, {"TCP header length not a multiple of 4", ip4TCP + tcpHdr + "0101", 42, []string{"must be 20-60"}}, {"IP header extends past layer 3", ip4TCP + tcpHdr, 12, []string{"extends past the layer 3 field", "TCP header is in the payload field"}}, {"IPv4 option overruns IHL", "46000000000140004006 0000 c0a80001c0a80002 01 44 08 00" + tcpHdr, 0, []string{"does not fit in the header length"}}, {"SCTP chunk header in payload field", ip4SCTP + sctpHdr + "00030000 00000001 00010000 00000000", 32, []string{"chunk header is not in the layer 3 field"}}, {"SCTP without chunks", ip4SCTP + sctpHdr, 32, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { res, err := Fix(mustHex(t, tt.pkt), tt.hdrLen) if err != nil { t.Fatal(err) } checkNotes(t, res.Notes, tt.notes) }) } } func TestFixIPv4Fragment(t *testing.T) { // More-fragments flag set: header fixed, UDP left alone. pkt := mustHex(t, "45000000000120004011 0000 c0a80001c0a80002 303904d200000000 aabb") res, err := Fix(pkt, 0) if err != nil { t.Fatal(err) } if len(res.Changes) != 2 || len(res.Notes) != 1 || !strings.Contains(res.Notes[0], "fragment") { t.Errorf("changes %+v notes %v", res.Changes, res.Notes) } if !bytes.Equal(pkt[24:28], []byte{0, 0, 0, 0}) { t.Errorf("UDP header modified: %x", pkt[20:28]) } } func TestFixTruncatedTransport(t *testing.T) { pkt := mustHex(t, "45000000000140004006 0000 c0a80001c0a80002 c350") res, err := Fix(pkt, 0) if err != nil { t.Fatal(err) } if len(res.Notes) != 1 || !strings.Contains(res.Notes[0], "TCP header truncated") { t.Errorf("notes %v", res.Notes) } } func TestFixErrors(t *testing.T) { for name, in := range map[string]string{ "empty": "", "not IP": "0011223344", "short IPv4": "4500001c", "IHL too small": "41000000000000004011000000000000000000000000", "IHL beyond end": "4f000000000000004011000000000000000000000000", "short IPv6": "6000000000001140", } { if _, err := Fix(mustHex(t, in), 0); err == nil { t.Errorf("%s: expected error", name) } } } func TestFindIP(t *testing.T) { tests := []struct { name string frame string linkType uint32 want int wantErr bool }{ {"ethernet", "001122334455 66778899aabb 0800 45", pcap.LinkTypeEthernet, 14, false}, {"ethernet IPv6", "001122334455 66778899aabb 86dd 60", pcap.LinkTypeEthernet, 14, false}, {"vlan", "001122334455 66778899aabb 8100 0064 0800 45", pcap.LinkTypeEthernet, 18, false}, {"qinq", "001122334455 66778899aabb 88a8 0001 8100 0064 86dd 60", pcap.LinkTypeEthernet, 22, false}, {"arp", "ffffffffffff 66778899aabb 0806 0001", pcap.LinkTypeEthernet, 0, true}, {"truncated", "001122334455", pcap.LinkTypeEthernet, 0, true}, {"raw", "45", pcap.LinkTypeRaw, 0, false}, {"sll", "0000 0001 0006 66778899aabb 0000 0800 45", pcap.LinkTypeLinuxSLL, 16, false}, {"sll2", "86dd 0000 00000002 0001 00 06 66778899aabb0000 60", pcap.LinkTypeLinuxSLL2, 20, false}, {"unsupported", "00", pcap.LinkTypeIEEE80211, 0, true}, } for _, tt := range tests { got, err := FindIP(mustHex(t, tt.frame), tt.linkType) if (err != nil) != tt.wantErr || got != tt.want { t.Errorf("%s: FindIP = %d, %v; want %d, err=%v", tt.name, got, err, tt.want, tt.wantErr) } } }