149 lines
4.3 KiB
Go
149 lines
4.3 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package magicsock
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"golang.org/x/sys/cpu"
|
|
"golang.org/x/sys/unix"
|
|
"tailscale.com/disco"
|
|
)
|
|
|
|
func TestParseUDPPacket(t *testing.T) {
|
|
src4 := netip.MustParseAddrPort("127.0.0.1:12345")
|
|
dst4 := netip.MustParseAddrPort("127.0.0.2:54321")
|
|
|
|
src6 := netip.MustParseAddrPort("[::1]:12345")
|
|
dst6 := netip.MustParseAddrPort("[::2]:54321")
|
|
|
|
udp4Packet := []byte{
|
|
// IPv4 header
|
|
0x45, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00,
|
|
0x40, 0x11, 0x00, 0x00,
|
|
0x7f, 0x00, 0x00, 0x01, // source ip
|
|
0x7f, 0x00, 0x00, 0x02, // dest ip
|
|
|
|
// UDP header
|
|
0x30, 0x39, // src port
|
|
0xd4, 0x31, // dest port
|
|
0x00, 0x12, // length; 8 bytes header + 10 bytes payload = 18 bytes
|
|
0x00, 0x00, // checksum; unused
|
|
|
|
// Payload: disco magic plus 4 bytes
|
|
0x54, 0x53, 0xf0, 0x9f, 0x92, 0xac, 0x00, 0x01, 0x02, 0x03,
|
|
}
|
|
udp6Packet := []byte{
|
|
// IPv6 header
|
|
0x60, 0x00, 0x00, 0x00,
|
|
0x00, 0x12, // payload length
|
|
0x11, // next header: UDP
|
|
0x00, // hop limit; unused
|
|
|
|
// Source IP
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
// Dest IP
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
|
|
|
// UDP header
|
|
0x30, 0x39, // src port
|
|
0xd4, 0x31, // dest port
|
|
0x00, 0x12, // length; 8 bytes header + 10 bytes payload = 18 bytes
|
|
0x00, 0x00, // checksum; unused
|
|
|
|
// Payload: disco magic plus 4 bytes
|
|
0x54, 0x53, 0xf0, 0x9f, 0x92, 0xac, 0x00, 0x01, 0x02, 0x03,
|
|
}
|
|
|
|
// Verify that parsing the UDP packet works correctly.
|
|
t.Run("IPv4", func(t *testing.T) {
|
|
src, dst, payload := parseUDPPacket(udp4Packet, false)
|
|
if src != src4 {
|
|
t.Errorf("src = %v; want %v", src, src4)
|
|
}
|
|
if dst != dst4 {
|
|
t.Errorf("dst = %v; want %v", dst, dst4)
|
|
}
|
|
if !bytes.HasPrefix(payload, []byte(disco.Magic)) {
|
|
t.Errorf("payload = %x; must start with %x", payload, disco.Magic)
|
|
}
|
|
})
|
|
t.Run("IPv6", func(t *testing.T) {
|
|
src, dst, payload := parseUDPPacket(udp6Packet, true)
|
|
if src != src6 {
|
|
t.Errorf("src = %v; want %v", src, src6)
|
|
}
|
|
if dst != dst6 {
|
|
t.Errorf("dst = %v; want %v", dst, dst6)
|
|
}
|
|
if !bytes.HasPrefix(payload, []byte(disco.Magic)) {
|
|
t.Errorf("payload = %x; must start with %x", payload, disco.Magic)
|
|
}
|
|
})
|
|
t.Run("Truncated", func(t *testing.T) {
|
|
truncateBy := func(b []byte, n int) []byte {
|
|
if n >= len(b) {
|
|
return nil
|
|
}
|
|
return b[:len(b)-n]
|
|
}
|
|
|
|
src, dst, payload := parseUDPPacket(truncateBy(udp4Packet, 11), false)
|
|
if payload != nil {
|
|
t.Errorf("payload = %x; want nil", payload)
|
|
}
|
|
if src.IsValid() || dst.IsValid() {
|
|
t.Errorf("src = %v, dst = %v; want invalid", src, dst)
|
|
}
|
|
|
|
src, dst, payload = parseUDPPacket(truncateBy(udp6Packet, 11), true)
|
|
if payload != nil {
|
|
t.Errorf("payload = %x; want nil", payload)
|
|
}
|
|
if src.IsValid() || dst.IsValid() {
|
|
t.Errorf("src = %v, dst = %v; want invalid", src, dst)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestEthernetProto(t *testing.T) {
|
|
htons := func(x uint16) int {
|
|
// Network byte order is big-endian; write the value as
|
|
// big-endian to a byte slice and read it back in the native
|
|
// endian-ness. This is a no-op on a big-endian platform and a
|
|
// byte swap on a little-endian platform.
|
|
var b [2]byte
|
|
binary.BigEndian.PutUint16(b[:], x)
|
|
return int(binary.NativeEndian.Uint16(b[:]))
|
|
}
|
|
|
|
if v4 := ethernetProtoIPv4(); v4 != htons(unix.ETH_P_IP) {
|
|
t.Errorf("ethernetProtoIPv4 = 0x%04x; want 0x%04x", v4, htons(unix.ETH_P_IP))
|
|
}
|
|
if v6 := ethernetProtoIPv6(); v6 != htons(unix.ETH_P_IPV6) {
|
|
t.Errorf("ethernetProtoIPv6 = 0x%04x; want 0x%04x", v6, htons(unix.ETH_P_IPV6))
|
|
}
|
|
|
|
// As a way to verify that the htons function is working correctly,
|
|
// assert that the ETH_P_IP value returned from our function matches
|
|
// the value defined in the unix package based on whether the host is
|
|
// big-endian (network byte order) or little-endian.
|
|
if cpu.IsBigEndian {
|
|
if v4 := ethernetProtoIPv4(); v4 != unix.ETH_P_IP {
|
|
t.Errorf("ethernetProtoIPv4 = 0x%04x; want 0x%04x", v4, unix.ETH_P_IP)
|
|
}
|
|
} else {
|
|
if v4 := ethernetProtoIPv4(); v4 == unix.ETH_P_IP {
|
|
t.Errorf("ethernetProtoIPv4 = 0x%04x; want 0x%04x", v4, htons(unix.ETH_P_IP))
|
|
} else {
|
|
t.Logf("ethernetProtoIPv4 = 0x%04x, correctly different from 0x%04x", v4, unix.ETH_P_IP)
|
|
}
|
|
}
|
|
}
|