wgengine/magicsock: actually use AF_PACKET socket for raw disco
Previously, despite what the commit said, we were using a raw IP socket that was *not* an AF_PACKET socket, and thus was subject to the host firewall rules. Switch to using a real AF_PACKET socket to actually get the functionality we want. Updates #13140 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: If657daeeda9ab8d967e75a4f049c66e2bca54b78
This commit is contained in:
148
wgengine/magicsock/magicsock_linux_test.go
Normal file
148
wgengine/magicsock/magicsock_linux_test.go
Normal file
@ -0,0 +1,148 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user