mirror of https://github.com/tailscale/tailscale/
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
736 lines
20 KiB
Go
736 lines
20 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package packet
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"net/netip"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"unicode"
|
|
|
|
"tailscale.com/tstest"
|
|
"tailscale.com/types/ipproto"
|
|
"tailscale.com/util/must"
|
|
)
|
|
|
|
const (
|
|
Unknown = ipproto.Unknown
|
|
TCP = ipproto.TCP
|
|
UDP = ipproto.UDP
|
|
SCTP = ipproto.SCTP
|
|
IGMP = ipproto.IGMP
|
|
ICMPv4 = ipproto.ICMPv4
|
|
ICMPv6 = ipproto.ICMPv6
|
|
TSMP = ipproto.TSMP
|
|
Fragment = ipproto.Fragment
|
|
)
|
|
|
|
func fullHeaderChecksumV4(b []byte) uint16 {
|
|
s := uint32(0)
|
|
for i := 0; i < len(b); i += 2 {
|
|
if i == 10 {
|
|
// Skip checksum field.
|
|
continue
|
|
}
|
|
s += uint32(binary.BigEndian.Uint16(b[i : i+2]))
|
|
}
|
|
for s>>16 > 0 {
|
|
s = s&0xFFFF + s>>16
|
|
}
|
|
return ^uint16(s)
|
|
}
|
|
|
|
func TestHeaderChecksums(t *testing.T) {
|
|
// This is not a good enough test, because it doesn't
|
|
// check the various packet types or the many edge cases
|
|
// of the checksum algorithm. But it's a start.
|
|
|
|
tests := []struct {
|
|
name string
|
|
packet []byte
|
|
}{
|
|
{
|
|
name: "ICMPv4",
|
|
packet: []byte{
|
|
0x45, 0x00, 0x00, 0x54, 0xb7, 0x96, 0x40, 0x00, 0x40, 0x01, 0x7a, 0x06, 0x64, 0x7f, 0x3f, 0x4c, 0x64, 0x40, 0x01, 0x01, 0x08, 0x00, 0x47, 0x1a, 0x00, 0x11, 0x01, 0xac, 0xcc, 0xf5, 0x95, 0x63, 0x00, 0x00, 0x00, 0x00, 0x8d, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
|
|
},
|
|
},
|
|
{
|
|
name: "TLS",
|
|
packet: []byte{
|
|
0x45, 0x00, 0x00, 0x3c, 0x54, 0x29, 0x40, 0x00, 0x40, 0x06, 0xb1, 0xac, 0x64, 0x42, 0xd4, 0x33, 0x64, 0x61, 0x98, 0x0f, 0xb1, 0x94, 0x01, 0xbb, 0x0a, 0x51, 0xce, 0x7c, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, 0xfb, 0xe0, 0x38, 0xf6, 0x00, 0x00, 0x02, 0x04, 0x04, 0xd8, 0x04, 0x02, 0x08, 0x0a, 0x86, 0x2b, 0xcc, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x07,
|
|
},
|
|
},
|
|
{
|
|
name: "DNS",
|
|
packet: []byte{
|
|
0x45, 0x00, 0x00, 0x74, 0xe2, 0x85, 0x00, 0x00, 0x40, 0x11, 0x96, 0xb5, 0x64, 0x64, 0x64, 0x64, 0x64, 0x42, 0xd4, 0x33, 0x00, 0x35, 0xec, 0x55, 0x00, 0x60, 0xd9, 0x19, 0xed, 0xfd, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x34, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x01, 0x1e, 0x00, 0x0c, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x01, 0x6c, 0xc0, 0x15, 0xc0, 0x31, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x1e, 0x00, 0x04, 0x8e, 0xfa, 0xbd, 0xce, 0x00, 0x00, 0x29, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
},
|
|
{
|
|
name: "DCCP",
|
|
packet: []byte{
|
|
0x45, 0x00, 0x00, 0x28, 0x15, 0x06, 0x40, 0x00, 0x40, 0x21, 0x5f, 0x2f, 0xc0, 0xa8, 0x01, 0x1f, 0xc9, 0x0b, 0x3b, 0xad, 0x80, 0x04, 0x13, 0x89, 0x05, 0x00, 0x08, 0xdb, 0x01, 0x00, 0x00, 0x04, 0x29, 0x01, 0x6d, 0xdc, 0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
},
|
|
{
|
|
name: "SCTP",
|
|
packet: []byte{
|
|
0x45, 0x00, 0x00, 0x30, 0x09, 0xd9, 0x40, 0x00, 0xff, 0x84, 0x50, 0xe2, 0x0a, 0x1c, 0x06, 0x2c, 0x0a, 0x1c, 0x06, 0x2b, 0x0b, 0x80, 0x40, 0x00, 0x21, 0x44, 0x15, 0x23, 0x2b, 0xf2, 0x02, 0x4e, 0x03, 0x00, 0x00, 0x10, 0x28, 0x02, 0x43, 0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
},
|
|
},
|
|
// TODO(maisem): add test for GRE.
|
|
}
|
|
var p Parsed
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p.Decode(tt.packet)
|
|
t.Log(p.String())
|
|
p.UpdateSrcAddr(netip.MustParseAddr("100.64.0.1"))
|
|
|
|
got := binary.BigEndian.Uint16(tt.packet[10:12])
|
|
want := fullHeaderChecksumV4(tt.packet[:20])
|
|
if got != want {
|
|
t.Fatalf("got %x want %x", got, want)
|
|
}
|
|
|
|
p.UpdateDstAddr(netip.MustParseAddr("100.64.0.2"))
|
|
got = binary.BigEndian.Uint16(tt.packet[10:12])
|
|
want = fullHeaderChecksumV4(tt.packet[:20])
|
|
if got != want {
|
|
t.Fatalf("got %x want %x", got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func mustIPPort(s string) netip.AddrPort {
|
|
ipp, err := netip.ParseAddrPort(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return ipp
|
|
}
|
|
|
|
var icmp4RequestBuffer = []byte{
|
|
// IP header up to checksum
|
|
0x45, 0x00, 0x00, 0x27, 0xde, 0xad, 0x00, 0x00, 0x40, 0x01, 0x8c, 0x15,
|
|
// source IP
|
|
0x01, 0x02, 0x03, 0x04,
|
|
// destination IP
|
|
0x05, 0x06, 0x07, 0x08,
|
|
// ICMP header
|
|
0x08, 0x00, 0x7d, 0x22,
|
|
// "request_payload"
|
|
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
|
}
|
|
|
|
var icmp4RequestDecode = Parsed{
|
|
b: icmp4RequestBuffer,
|
|
subofs: 20,
|
|
dataofs: 24,
|
|
length: len(icmp4RequestBuffer),
|
|
|
|
IPVersion: 4,
|
|
IPProto: ICMPv4,
|
|
Src: mustIPPort("1.2.3.4:0"),
|
|
Dst: mustIPPort("5.6.7.8:0"),
|
|
}
|
|
|
|
var icmp4ReplyBuffer = []byte{
|
|
0x45, 0x00, 0x00, 0x25, 0x21, 0x52, 0x00, 0x00, 0x40, 0x01, 0x49, 0x73,
|
|
// source IP
|
|
0x05, 0x06, 0x07, 0x08,
|
|
// destination IP
|
|
0x01, 0x02, 0x03, 0x04,
|
|
// ICMP header
|
|
0x00, 0x00, 0xe6, 0x9e,
|
|
// "reply_payload"
|
|
0x72, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
|
}
|
|
|
|
var icmp4ReplyDecode = Parsed{
|
|
b: icmp4ReplyBuffer,
|
|
subofs: 20,
|
|
dataofs: 24,
|
|
length: len(icmp4ReplyBuffer),
|
|
|
|
IPVersion: 4,
|
|
IPProto: ICMPv4,
|
|
Src: mustIPPort("1.2.3.4:0"),
|
|
Dst: mustIPPort("5.6.7.8:0"),
|
|
}
|
|
|
|
// ICMPv6 Router Solicitation
|
|
var icmp6PacketBuffer = []byte{
|
|
0x60, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3a, 0xff,
|
|
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0xfb, 0x57, 0x1d, 0xea, 0x9c, 0x39, 0x8f, 0xb7,
|
|
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
|
0x85, 0x00, 0x38, 0x04, 0x00, 0x00, 0x00, 0x00,
|
|
}
|
|
|
|
var icmp6PacketDecode = Parsed{
|
|
b: icmp6PacketBuffer,
|
|
subofs: 40,
|
|
dataofs: 44,
|
|
length: len(icmp6PacketBuffer),
|
|
IPVersion: 6,
|
|
IPProto: ICMPv6,
|
|
Src: mustIPPort("[fe80::fb57:1dea:9c39:8fb7]:0"),
|
|
Dst: mustIPPort("[ff02::2]:0"),
|
|
}
|
|
|
|
// This is a malformed IPv4 packet.
|
|
// Namely, the string "tcp_payload" follows the first byte of the IPv4 header.
|
|
var unknownPacketBuffer = []byte{
|
|
0x45, 0x74, 0x63, 0x70, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
|
}
|
|
|
|
var unknownPacketDecode = Parsed{
|
|
b: unknownPacketBuffer,
|
|
IPVersion: 0,
|
|
IPProto: Unknown,
|
|
}
|
|
|
|
var tcp4PacketBuffer = []byte{
|
|
// IP header up to checksum
|
|
0x45, 0x00, 0x00, 0x37, 0xde, 0xad, 0x00, 0x00, 0x40, 0x06, 0x49, 0x5f,
|
|
// source IP
|
|
0x01, 0x02, 0x03, 0x04,
|
|
// destination IP
|
|
0x05, 0x06, 0x07, 0x08,
|
|
// TCP header with SYN, ACK set
|
|
0x00, 0x7b, 0x02, 0x37, 0x00, 0x00, 0x12, 0x34, 0x00, 0x00, 0x00, 0x00,
|
|
0x50, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// "request_payload"
|
|
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
|
}
|
|
|
|
var tcp4PacketDecode = Parsed{
|
|
b: tcp4PacketBuffer,
|
|
subofs: 20,
|
|
dataofs: 40,
|
|
length: len(tcp4PacketBuffer),
|
|
|
|
IPVersion: 4,
|
|
IPProto: TCP,
|
|
Src: mustIPPort("1.2.3.4:123"),
|
|
Dst: mustIPPort("5.6.7.8:567"),
|
|
TCPFlags: TCPSynAck,
|
|
}
|
|
|
|
var tcp6RequestBuffer = []byte{
|
|
// IPv6 header up to hop limit
|
|
0x60, 0x06, 0xef, 0xcc, 0x00, 0x28, 0x06, 0x40,
|
|
// Src addr
|
|
0x20, 0x01, 0x05, 0x59, 0xbc, 0x13, 0x54, 0x00, 0x17, 0x49, 0x46, 0x28, 0x39, 0x34, 0x0e, 0x1b,
|
|
// Dst addr
|
|
0x26, 0x07, 0xf8, 0xb0, 0x40, 0x0a, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e,
|
|
// TCP SYN segment, no payload
|
|
0xa4, 0x60, 0x00, 0x50, 0xf3, 0x82, 0xa1, 0x25, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, 0xfd, 0x20,
|
|
0xb1, 0xc6, 0x00, 0x00, 0x02, 0x04, 0x05, 0xa0, 0x04, 0x02, 0x08, 0x0a, 0xca, 0x76, 0xa6, 0x8e,
|
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x07,
|
|
}
|
|
|
|
var tcp6RequestDecode = Parsed{
|
|
b: tcp6RequestBuffer,
|
|
subofs: 40,
|
|
dataofs: len(tcp6RequestBuffer),
|
|
length: len(tcp6RequestBuffer),
|
|
|
|
IPVersion: 6,
|
|
IPProto: TCP,
|
|
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:42080"),
|
|
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:80"),
|
|
TCPFlags: TCPSyn,
|
|
}
|
|
|
|
var udp4RequestBuffer = []byte{
|
|
// IP header up to checksum
|
|
0x45, 0x00, 0x00, 0x2b, 0xde, 0xad, 0x00, 0x00, 0x40, 0x11, 0x8c, 0x01,
|
|
// source IP
|
|
0x01, 0x02, 0x03, 0x04,
|
|
// destination IP
|
|
0x05, 0x06, 0x07, 0x08,
|
|
// UDP header
|
|
0x00, 0x7b, 0x02, 0x37, 0x00, 0x17, 0x72, 0x1d,
|
|
// "request_payload"
|
|
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
|
}
|
|
|
|
var udp4RequestDecode = Parsed{
|
|
b: udp4RequestBuffer,
|
|
subofs: 20,
|
|
dataofs: 28,
|
|
length: len(udp4RequestBuffer),
|
|
|
|
IPVersion: 4,
|
|
IPProto: UDP,
|
|
Src: mustIPPort("1.2.3.4:123"),
|
|
Dst: mustIPPort("5.6.7.8:567"),
|
|
}
|
|
|
|
var invalid4RequestBuffer = []byte{
|
|
// IP header up to checksum. IHL field points beyond end of packet.
|
|
0x4a, 0x00, 0x00, 0x14, 0xde, 0xad, 0x00, 0x00, 0x40, 0x11, 0x8c, 0x01,
|
|
// source IP
|
|
0x01, 0x02, 0x03, 0x04,
|
|
// destination IP
|
|
0x05, 0x06, 0x07, 0x08,
|
|
}
|
|
|
|
// Regression check for the IHL field pointing beyond the end of the
|
|
// packet.
|
|
var invalid4RequestDecode = Parsed{
|
|
b: invalid4RequestBuffer,
|
|
subofs: 40,
|
|
length: len(invalid4RequestBuffer),
|
|
|
|
IPVersion: 4,
|
|
IPProto: Unknown,
|
|
Src: mustIPPort("1.2.3.4:0"),
|
|
Dst: mustIPPort("5.6.7.8:0"),
|
|
}
|
|
|
|
var udp6RequestBuffer = []byte{
|
|
// IPv6 header up to hop limit
|
|
0x60, 0x0e, 0xc9, 0x67, 0x00, 0x29, 0x11, 0x40,
|
|
// Src addr
|
|
0x20, 0x01, 0x05, 0x59, 0xbc, 0x13, 0x54, 0x00, 0x17, 0x49, 0x46, 0x28, 0x39, 0x34, 0x0e, 0x1b,
|
|
// Dst addr
|
|
0x26, 0x07, 0xf8, 0xb0, 0x40, 0x0a, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0e,
|
|
// UDP header
|
|
0xd4, 0x04, 0x01, 0xbb, 0x00, 0x29, 0x96, 0x84,
|
|
// Payload
|
|
0x5c, 0x06, 0xae, 0x85, 0x02, 0xf5, 0xdb, 0x90, 0xe0, 0xe0, 0x93, 0xed, 0x9a, 0xd9, 0x92, 0x69, 0xbe, 0x36, 0x8a, 0x7d, 0xd7, 0xce, 0xd0, 0x8a, 0xf2, 0x51, 0x95, 0xff, 0xb6, 0x92, 0x70, 0x10, 0xd7,
|
|
}
|
|
|
|
var udp6RequestDecode = Parsed{
|
|
b: udp6RequestBuffer,
|
|
subofs: 40,
|
|
dataofs: 48,
|
|
length: len(udp6RequestBuffer),
|
|
|
|
IPVersion: 6,
|
|
IPProto: UDP,
|
|
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:54276"),
|
|
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:443"),
|
|
}
|
|
|
|
var udp4ReplyBuffer = []byte{
|
|
// IP header up to checksum
|
|
0x45, 0x00, 0x00, 0x29, 0x21, 0x52, 0x00, 0x00, 0x40, 0x11, 0x49, 0x5f,
|
|
// source IP
|
|
0x05, 0x06, 0x07, 0x08,
|
|
// destination IP
|
|
0x01, 0x02, 0x03, 0x04,
|
|
// UDP header
|
|
0x02, 0x37, 0x00, 0x7b, 0x00, 0x15, 0xd3, 0x9d,
|
|
// "reply_payload"
|
|
0x72, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
|
}
|
|
|
|
var udp4ReplyDecode = Parsed{
|
|
b: udp4ReplyBuffer,
|
|
subofs: 20,
|
|
dataofs: 28,
|
|
length: len(udp4ReplyBuffer),
|
|
|
|
IPProto: UDP,
|
|
Src: mustIPPort("1.2.3.4:567"),
|
|
Dst: mustIPPort("5.6.7.8:123"),
|
|
}
|
|
|
|
// First TCP fragment of a packet with leading 24 bytes of 'a's
|
|
var tcp4MediumFragmentBuffer = []byte{
|
|
// IP header up to checksum
|
|
0x45, 0x20, 0x00, 0x4c, 0x2c, 0x62, 0x20, 0x00, 0x22, 0x06, 0x3a, 0x0f,
|
|
// source IP
|
|
0x01, 0x02, 0x03, 0x04,
|
|
// destination IP
|
|
0x05, 0x06, 0x07, 0x08,
|
|
// TCP header
|
|
0x00, 0x50, 0xf3, 0x8c, 0x58, 0xad, 0x60, 0x94, 0x25, 0xe4, 0x23, 0xa8, 0x80,
|
|
0x10, 0x01, 0xfd, 0xc6, 0x6e, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0xff, 0x60,
|
|
0xfb, 0xfe, 0xba, 0x31, 0x78, 0x6a,
|
|
// data
|
|
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
|
|
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
|
|
}
|
|
|
|
var tcp4MediumFragmentDecode = Parsed{
|
|
b: tcp4MediumFragmentBuffer,
|
|
subofs: 20,
|
|
dataofs: 52,
|
|
length: len(tcp4MediumFragmentBuffer),
|
|
|
|
IPVersion: 4,
|
|
IPProto: TCP,
|
|
Src: mustIPPort("1.2.3.4:80"),
|
|
Dst: mustIPPort("5.6.7.8:62348"),
|
|
TCPFlags: 0x10,
|
|
}
|
|
|
|
var tcp4ShortFragmentBuffer = []byte{
|
|
// IP header up to checksum
|
|
0x45, 0x20, 0x00, 0x1e, 0x2c, 0x62, 0x20, 0x00, 0x22, 0x06, 0x3c, 0x4f,
|
|
// source IP
|
|
0x01, 0x02, 0x03, 0x04,
|
|
// destination IP
|
|
0x05, 0x06, 0x07, 0x08,
|
|
// partial TCP header
|
|
0x00, 0x50, 0xf3, 0x8c, 0x58, 0xad, 0x60, 0x94, 0x00, 0x00,
|
|
}
|
|
|
|
var tcp4ShortFragmentDecode = Parsed{
|
|
b: tcp4ShortFragmentBuffer,
|
|
subofs: 20,
|
|
dataofs: 0,
|
|
length: len(tcp4ShortFragmentBuffer),
|
|
// short fragments are rejected (marked unknown) to avoid header attacks as described in RFC 1858
|
|
IPProto: ipproto.Unknown,
|
|
IPVersion: 4,
|
|
Src: mustIPPort("1.2.3.4:0"),
|
|
Dst: mustIPPort("5.6.7.8:0"),
|
|
}
|
|
|
|
var igmpPacketBuffer = []byte{
|
|
// IP header up to checksum
|
|
0x46, 0xc0, 0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x01, 0x02, 0x41, 0x22,
|
|
// source IP
|
|
0xc0, 0xa8, 0x01, 0x52,
|
|
// destination IP
|
|
0xe0, 0x00, 0x00, 0xfb,
|
|
// IGMP Membership Report
|
|
0x94, 0x04, 0x00, 0x00, 0x16, 0x00, 0x09, 0x04, 0xe0, 0x00, 0x00, 0xfb,
|
|
//0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}
|
|
|
|
var igmpPacketDecode = Parsed{
|
|
b: igmpPacketBuffer,
|
|
subofs: 24,
|
|
length: len(igmpPacketBuffer),
|
|
|
|
IPVersion: 4,
|
|
IPProto: IGMP,
|
|
Src: mustIPPort("192.168.1.82:0"),
|
|
Dst: mustIPPort("224.0.0.251:0"),
|
|
}
|
|
|
|
var ipv4TSMPBuffer = []byte{
|
|
// IPv4 header:
|
|
0x45, 0x00,
|
|
0x00, 0x1b, // 20 + 7 bytes total
|
|
0x00, 0x00, // ID
|
|
0x00, 0x00, // Fragment
|
|
0x40, // TTL
|
|
byte(TSMP),
|
|
0x5f, 0xc3, // header checksum (wrong here)
|
|
// source IP:
|
|
0x64, 0x5e, 0x0c, 0x0e,
|
|
// dest IP:
|
|
0x64, 0x4a, 0x46, 0x03,
|
|
byte(TSMPTypeRejectedConn),
|
|
byte(TCP),
|
|
byte(RejectedDueToACLs),
|
|
0x00, 123, // src port
|
|
0x00, 80, // dst port
|
|
}
|
|
|
|
var ipv4TSMPDecode = Parsed{
|
|
b: ipv4TSMPBuffer,
|
|
subofs: 20,
|
|
dataofs: 20,
|
|
length: 27,
|
|
IPVersion: 4,
|
|
IPProto: TSMP,
|
|
Src: mustIPPort("100.94.12.14:0"),
|
|
Dst: mustIPPort("100.74.70.3:0"),
|
|
}
|
|
|
|
// IPv4 SCTP
|
|
var sctpBuffer = []byte{
|
|
// IPv4 header:
|
|
0x45, 0x00,
|
|
0x00, 0x20, // 20 + 12 bytes total
|
|
0x00, 0x00, // ID
|
|
0x00, 0x00, // Fragment
|
|
0x40, // TTL
|
|
byte(SCTP),
|
|
// Checksum, unchecked:
|
|
1, 2,
|
|
// source IP:
|
|
0x64, 0x5e, 0x0c, 0x0e,
|
|
// dest IP:
|
|
0x64, 0x4a, 0x46, 0x03,
|
|
// Src Port, Dest Port:
|
|
0x00, 0x7b, 0x01, 0xc8,
|
|
// Verification tag:
|
|
1, 2, 3, 4,
|
|
// Checksum: (unchecked)
|
|
5, 6, 7, 8,
|
|
}
|
|
|
|
var sctpDecode = Parsed{
|
|
b: sctpBuffer,
|
|
subofs: 20,
|
|
length: 20 + 12,
|
|
IPVersion: 4,
|
|
IPProto: SCTP,
|
|
Src: mustIPPort("100.94.12.14:123"),
|
|
Dst: mustIPPort("100.74.70.3:456"),
|
|
}
|
|
|
|
func TestParsedString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
qdecode Parsed
|
|
want string
|
|
}{
|
|
{"tcp4", tcp4PacketDecode, "TCP{1.2.3.4:123 > 5.6.7.8:567}"},
|
|
{"tcp6", tcp6RequestDecode, "TCP{[2001:559:bc13:5400:1749:4628:3934:e1b]:42080 > [2607:f8b0:400a:809::200e]:80}"},
|
|
{"udp4", udp4RequestDecode, "UDP{1.2.3.4:123 > 5.6.7.8:567}"},
|
|
{"udp6", udp6RequestDecode, "UDP{[2001:559:bc13:5400:1749:4628:3934:e1b]:54276 > [2607:f8b0:400a:809::200e]:443}"},
|
|
{"icmp4", icmp4RequestDecode, "ICMPv4{1.2.3.4:0 > 5.6.7.8:0}"},
|
|
{"icmp6", icmp6PacketDecode, "ICMPv6{[fe80::fb57:1dea:9c39:8fb7]:0 > [ff02::2]:0}"},
|
|
{"igmp", igmpPacketDecode, "IGMP{192.168.1.82:0 > 224.0.0.251:0}"},
|
|
{"unknown", unknownPacketDecode, "Unknown{???}"},
|
|
{"ipv4_tsmp", ipv4TSMPDecode, "TSMP{100.94.12.14:0 > 100.74.70.3:0}"},
|
|
{"sctp", sctpDecode, "SCTP{100.94.12.14:123 > 100.74.70.3:456}"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.qdecode.String()
|
|
if got != tt.want {
|
|
t.Errorf("got %q; want %q", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
err := tstest.MinAllocsPerRun(t, 1, func() {
|
|
sinkString = tests[0].qdecode.String()
|
|
})
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
// mustHexDecode is like hex.DecodeString, but panics on error
|
|
// and ignores whitespace in s.
|
|
func mustHexDecode(s string) []byte {
|
|
return must.Get(hex.DecodeString(strings.Map(func(r rune) rune {
|
|
if unicode.IsSpace(r) {
|
|
return -1
|
|
}
|
|
return r
|
|
}, s)))
|
|
}
|
|
|
|
func TestDecode(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
buf []byte
|
|
want Parsed
|
|
}{
|
|
{"icmp4", icmp4RequestBuffer, icmp4RequestDecode},
|
|
{"icmp6", icmp6PacketBuffer, icmp6PacketDecode},
|
|
{"tcp4", tcp4PacketBuffer, tcp4PacketDecode},
|
|
{"tcp6", tcp6RequestBuffer, tcp6RequestDecode},
|
|
{"udp4", udp4RequestBuffer, udp4RequestDecode},
|
|
{"udp6", udp6RequestBuffer, udp6RequestDecode},
|
|
{"igmp", igmpPacketBuffer, igmpPacketDecode},
|
|
{"unknown", unknownPacketBuffer, unknownPacketDecode},
|
|
{"invalid4", invalid4RequestBuffer, invalid4RequestDecode},
|
|
{"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode},
|
|
{"ipv4_sctp", sctpBuffer, sctpDecode},
|
|
{"ipv4_frag", tcp4MediumFragmentBuffer, tcp4MediumFragmentDecode},
|
|
{"ipv4_fragtooshort", tcp4ShortFragmentBuffer, tcp4ShortFragmentDecode},
|
|
|
|
{"ip97", mustHexDecode("4500 0019 d186 4000 4061 751d 644a 4603 6449 e549 6865 6c6c 6f"), Parsed{
|
|
IPVersion: 4,
|
|
IPProto: 97,
|
|
Src: netip.MustParseAddrPort("100.74.70.3:0"),
|
|
Dst: netip.MustParseAddrPort("100.73.229.73:0"),
|
|
b: mustHexDecode("4500 0019 d186 4000 4061 751d 644a 4603 6449 e549 6865 6c6c 6f"),
|
|
length: 25,
|
|
subofs: 20,
|
|
}},
|
|
|
|
// This packet purports to use protocol 0xFF, which is verboten and
|
|
// used internally as a sentinel value for fragments. So test that
|
|
// we map packets using 0xFF to Unknown (0) instead.
|
|
{"bogus_proto_ff", mustHexDecode("4500 0019 d186 4000 40" + "FF" /* bogus FF */ + " 751d 644a 4603 6449 e549 6865 6c6c 6f"), Parsed{
|
|
IPVersion: 4,
|
|
IPProto: ipproto.Unknown, // 0, not bogus 0xFF
|
|
Src: netip.MustParseAddrPort("100.74.70.3:0"),
|
|
Dst: netip.MustParseAddrPort("100.73.229.73:0"),
|
|
b: mustHexDecode("4500 0019 d186 4000 40" + "FF" /* bogus FF */ + " 751d 644a 4603 6449 e549 6865 6c6c 6f"),
|
|
length: 25,
|
|
subofs: 20,
|
|
}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var got Parsed
|
|
got.Decode(tt.buf)
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("mismatch\n got: %s %#v\nwant: %s %#v", got.String(), got, tt.want.String(), tt.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
err := tstest.MinAllocsPerRun(t, 0, func() {
|
|
var got Parsed
|
|
got.Decode(tests[0].buf)
|
|
})
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func BenchmarkDecode(b *testing.B) {
|
|
benches := []struct {
|
|
name string
|
|
buf []byte
|
|
}{
|
|
{"tcp4", tcp4PacketBuffer},
|
|
{"tcp6", tcp6RequestBuffer},
|
|
{"udp4", udp4RequestBuffer},
|
|
{"udp6", udp6RequestBuffer},
|
|
{"icmp4", icmp4RequestBuffer},
|
|
{"icmp6", icmp6PacketBuffer},
|
|
{"igmp", igmpPacketBuffer},
|
|
{"unknown", unknownPacketBuffer},
|
|
}
|
|
|
|
for _, bench := range benches {
|
|
b.Run(bench.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
var p Parsed
|
|
p.Decode(bench.buf)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMarshalRequest(t *testing.T) {
|
|
// Too small to hold our packets, but only barely.
|
|
var small [20]byte
|
|
var large [64]byte
|
|
|
|
icmpHeader := icmp4RequestDecode.ICMP4Header()
|
|
udpHeader := udp4RequestDecode.UDP4Header()
|
|
tests := []struct {
|
|
name string
|
|
header Header
|
|
want []byte
|
|
}{
|
|
{"icmp", &icmpHeader, icmp4RequestBuffer},
|
|
{"udp", &udpHeader, udp4RequestBuffer},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.header.Marshal(small[:])
|
|
if err != errSmallBuffer {
|
|
t.Errorf("got err: nil; want: %s", errSmallBuffer)
|
|
}
|
|
|
|
dataOffset := tt.header.Len()
|
|
dataLength := copy(large[dataOffset:], []byte("request_payload"))
|
|
end := dataOffset + dataLength
|
|
err = tt.header.Marshal(large[:end])
|
|
|
|
if err != nil {
|
|
t.Errorf("got err: %s; want nil", err)
|
|
}
|
|
|
|
if !bytes.Equal(large[:end], tt.want) {
|
|
t.Errorf("got %x; want %x", large[:end], tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMarshalResponse(t *testing.T) {
|
|
var buf [64]byte
|
|
|
|
icmpHeader := icmp4RequestDecode.ICMP4Header()
|
|
udpHeader := udp4RequestDecode.UDP4Header()
|
|
|
|
type HeaderToResponser interface {
|
|
Header
|
|
// ToResponse transforms the header into one for a response packet.
|
|
// For instance, this swaps the source and destination IPs.
|
|
ToResponse()
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
header HeaderToResponser
|
|
want []byte
|
|
}{
|
|
{"icmp", &icmpHeader, icmp4ReplyBuffer},
|
|
{"udp", &udpHeader, udp4ReplyBuffer},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.header.ToResponse()
|
|
|
|
dataOffset := tt.header.Len()
|
|
dataLength := copy(buf[dataOffset:], []byte("reply_payload"))
|
|
end := dataOffset + dataLength
|
|
err := tt.header.Marshal(buf[:end])
|
|
|
|
if err != nil {
|
|
t.Errorf("got err: %s; want nil", err)
|
|
}
|
|
|
|
if !bytes.Equal(buf[:end], tt.want) {
|
|
t.Errorf("got %x; want %x", buf[:end], tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var sinkString string
|
|
|
|
func BenchmarkString(b *testing.B) {
|
|
benches := []struct {
|
|
name string
|
|
buf []byte
|
|
}{
|
|
{"tcp4", tcp4PacketBuffer},
|
|
{"tcp6", tcp6RequestBuffer},
|
|
{"udp4", udp4RequestBuffer},
|
|
{"udp6", udp6RequestBuffer},
|
|
{"icmp4", icmp4RequestBuffer},
|
|
{"icmp6", icmp6PacketBuffer},
|
|
{"igmp", igmpPacketBuffer},
|
|
{"unknown", unknownPacketBuffer},
|
|
}
|
|
|
|
for _, bench := range benches {
|
|
b.Run(bench.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
var p Parsed
|
|
p.Decode(bench.buf)
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
sinkString = p.String()
|
|
}
|
|
})
|
|
}
|
|
}
|