|
@@ -0,0 +1,175 @@
|
|
|
|
|
+package amneziawgnet
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "bytes"
|
|
|
|
|
+ "encoding/binary"
|
|
|
|
|
+ "net/netip"
|
|
|
|
|
+ "strings"
|
|
|
|
|
+ "testing"
|
|
|
|
|
+ "time"
|
|
|
|
|
+
|
|
|
|
|
+ "gvisor.dev/gvisor/pkg/tcpip"
|
|
|
|
|
+ "gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
|
|
|
+ "gvisor.dev/gvisor/pkg/tcpip/header"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+func TestWriteUDPReplyIPv6MTU(t *testing.T) {
|
|
|
|
|
+ for _, tc := range []struct {
|
|
|
|
|
+ name string
|
|
|
|
|
+ size int
|
|
|
|
|
+ fragmented bool
|
|
|
|
|
+ }{
|
|
|
|
|
+ {"small", 80, false}, {"boundary", 1232, false}, {"fragmented", 1452, true},
|
|
|
|
|
+ } {
|
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
+ tun, gstack, err := createNetTUNWithStack([]netip.Addr{netip.MustParseAddr("fd00::1")}, 1280)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer tun.Close()
|
|
|
|
|
+ enablePromiscuousRouting(gstack)
|
|
|
|
|
+ from := netip.MustParseAddrPort("[2001:db8::abcd]:5353")
|
|
|
|
|
+ to := netip.MustParseAddrPort("[fd00::2]:49152")
|
|
|
|
|
+ payload := make([]byte, tc.size)
|
|
|
|
|
+ for i := range payload {
|
|
|
|
|
+ payload[i] = byte(i*51 + 7)
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := WriteUDPReply(gstack, from, to, payload); err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ var data []byte
|
|
|
|
|
+ var id uint32
|
|
|
|
|
+ count := 0
|
|
|
|
|
+ for {
|
|
|
|
|
+ select {
|
|
|
|
|
+ case v := <-tun.(*stackTun).incomingPacket:
|
|
|
|
|
+ packet := append([]byte(nil), v.AsSlice()...)
|
|
|
|
|
+ v.Release()
|
|
|
|
|
+ if len(packet) > 1280 {
|
|
|
|
|
+ t.Fatalf("IPv6 TUN packet length %d exceeds MTU", len(packet))
|
|
|
|
|
+ }
|
|
|
|
|
+ ip := header.IPv6(packet)
|
|
|
|
|
+ if ip.SourceAddress().String() != from.Addr().String() || ip.DestinationAddress().String() != to.Addr().String() || int(ip.PayloadLength())+40 != len(packet) {
|
|
|
|
|
+ t.Fatalf("IPv6 addresses/length invalid: %s -> %s, %d", ip.SourceAddress(), ip.DestinationAddress(), ip.PayloadLength())
|
|
|
|
|
+ }
|
|
|
|
|
+ var offset int
|
|
|
|
|
+ more := false
|
|
|
|
|
+ part := packet[40:]
|
|
|
|
|
+ if tc.fragmented {
|
|
|
|
|
+ if ip.TransportProtocol() != header.IPv6FragmentHeader {
|
|
|
|
|
+ t.Fatalf("IPv6 NextHeader %d, want fragment", ip.TransportProtocol())
|
|
|
|
|
+ }
|
|
|
|
|
+ frag := header.IPv6Fragment(part)
|
|
|
|
|
+ if frag.NextHeader() != uint8(header.UDPProtocolNumber) {
|
|
|
|
|
+ t.Fatalf("fragment next header %d", frag.NextHeader())
|
|
|
|
|
+ }
|
|
|
|
|
+ offset = int(frag.FragmentOffset()) * 8
|
|
|
|
|
+ more = frag.More()
|
|
|
|
|
+ if count == 0 {
|
|
|
|
|
+ id = frag.ID()
|
|
|
|
|
+ } else if frag.ID() != id {
|
|
|
|
|
+ t.Fatalf("fragment ID %d != %d", frag.ID(), id)
|
|
|
|
|
+ }
|
|
|
|
|
+ part = part[8:]
|
|
|
|
|
+ } else if ip.TransportProtocol() != header.UDPProtocolNumber {
|
|
|
|
|
+ t.Fatalf("IPv6 NextHeader %d", ip.TransportProtocol())
|
|
|
|
|
+ }
|
|
|
|
|
+ if offset != len(data) {
|
|
|
|
|
+ t.Fatalf("fragment offset %d, want %d", offset, len(data))
|
|
|
|
|
+ }
|
|
|
|
|
+ data = append(data, part...)
|
|
|
|
|
+ count++
|
|
|
|
|
+ if !more {
|
|
|
|
|
+ goto assembled
|
|
|
|
|
+ }
|
|
|
|
|
+ case <-time.After(time.Second):
|
|
|
|
|
+ t.Fatal("IPv6 reply timed out")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ assembled:
|
|
|
|
|
+ if tc.fragmented && count < 2 {
|
|
|
|
|
+ t.Fatal("no IPv6 fragments")
|
|
|
|
|
+ }
|
|
|
|
|
+ if len(data) != len(payload)+8 || binary.BigEndian.Uint16(data[:2]) != from.Port() || binary.BigEndian.Uint16(data[2:4]) != to.Port() || binary.BigEndian.Uint16(data[4:6]) != uint16(len(data)) || !bytes.Equal(data[8:], payload) {
|
|
|
|
|
+ t.Fatal("reassembled IPv6 UDP bytes/ports/length differ")
|
|
|
|
|
+ }
|
|
|
|
|
+ if checksum.Checksum(data, header.PseudoHeaderChecksum(header.UDPProtocolNumber, tcpip.AddrFromSlice(from.Addr().AsSlice()), tcpip.AddrFromSlice(to.Addr().AsSlice()), uint16(len(data)))) != 0xffff {
|
|
|
|
|
+ t.Fatal("IPv6 UDP checksum invalid")
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestWriteUDPReplyRejectsInvalidDatagrams(t *testing.T) {
|
|
|
|
|
+ tun, gstack, err := createNetTUNWithStack([]netip.Addr{netip.MustParseAddr("10.77.0.1"), netip.MustParseAddr("fd00::1")}, 1280)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer tun.Close()
|
|
|
|
|
+ enablePromiscuousRouting(gstack)
|
|
|
|
|
+ for _, tc := range []struct {
|
|
|
|
|
+ name, from, to string
|
|
|
|
|
+ size int
|
|
|
|
|
+ want string
|
|
|
|
|
+ }{
|
|
|
|
|
+ {"mixed", "203.0.113.9:53", "[fd00::2]:4000", 1, "invalid UDP reply addresses"},
|
|
|
|
|
+ {"zone", "[fe80::1%eth0]:53", "[fd00::2]:4000", 1, "invalid UDP reply addresses"},
|
|
|
|
|
+ {"v4 length", "203.0.113.9:53", "10.77.0.2:4000", 65508, "IPv4 reply too large"},
|
|
|
|
|
+ {"v6 length", "[2001:db8::1]:53", "[fd00::2]:4000", 65528, "UDP reply too large"},
|
|
|
|
|
+ } {
|
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
+ err := WriteUDPReply(gstack, netip.MustParseAddrPort(tc.from), netip.MustParseAddrPort(tc.to), make([]byte, tc.size))
|
|
|
|
|
+ if err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
|
|
|
+ t.Fatalf("error = %v, want %q", err, tc.want)
|
|
|
|
|
+ }
|
|
|
|
|
+ select {
|
|
|
|
|
+ case v := <-tun.(*stackTun).incomingPacket:
|
|
|
|
|
+ v.Release()
|
|
|
|
|
+ t.Fatal("invalid reply emitted packet")
|
|
|
|
|
+ default:
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestWriteUDPReplyIPv6ZeroChecksumIsEncodedAsOnes(t *testing.T) {
|
|
|
|
|
+ tun, gstack, err := createNetTUNWithStack([]netip.Addr{netip.MustParseAddr("fd00::1")}, 1280)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer tun.Close()
|
|
|
|
|
+ enablePromiscuousRouting(gstack)
|
|
|
|
|
+ from := netip.MustParseAddrPort("[2001:db8::abcd]:5353")
|
|
|
|
|
+ to := netip.MustParseAddrPort("[fd00::2]:49152")
|
|
|
|
|
+ src := tcpip.AddrFromSlice(from.Addr().AsSlice())
|
|
|
|
|
+ dst := tcpip.AddrFromSlice(to.Addr().AsSlice())
|
|
|
|
|
+ udp := make([]byte, 10)
|
|
|
|
|
+ binary.BigEndian.PutUint16(udp[0:2], from.Port())
|
|
|
|
|
+ binary.BigEndian.PutUint16(udp[2:4], to.Port())
|
|
|
|
|
+ binary.BigEndian.PutUint16(udp[4:6], uint16(len(udp)))
|
|
|
|
|
+ pseudo := header.PseudoHeaderChecksum(header.UDPProtocolNumber, src, dst, uint16(len(udp)))
|
|
|
|
|
+ var found bool
|
|
|
|
|
+ for n := 0; n <= 65535; n++ {
|
|
|
|
|
+ binary.BigEndian.PutUint16(udp[8:], uint16(n))
|
|
|
|
|
+ if checksum.Checksum(udp, pseudo) == 0xffff {
|
|
|
|
|
+ found = true
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if !found {
|
|
|
|
|
+ t.Fatal("no zero-complement checksum payload found")
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := WriteUDPReply(gstack, from, to, udp[8:]); err != nil {
|
|
|
|
|
+ t.Fatal(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ select {
|
|
|
|
|
+ case v := <-tun.(*stackTun).incomingPacket:
|
|
|
|
|
+ packet := append([]byte(nil), v.AsSlice()...)
|
|
|
|
|
+ v.Release()
|
|
|
|
|
+ if got := binary.BigEndian.Uint16(packet[46:48]); got != 0xffff {
|
|
|
|
|
+ t.Fatalf("IPv6 UDP checksum = %#04x, want 0xffff (zero is invalid)", got)
|
|
|
|
|
+ }
|
|
|
|
|
+ case <-time.After(time.Second):
|
|
|
|
|
+ t.Fatal("reply timed out")
|
|
|
|
|
+ }
|
|
|
|
|
+}
|