mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +01:00 
			
		
		
		
	This commit introduces a userspace program for managing an experimental eBPF XDP STUN server program. derp/xdp contains the eBPF pseudo-C along with a Go pkg for loading it and exporting its metrics. cmd/xdpderper is a package main user of derp/xdp. Updates tailscale/corp#20689 Signed-off-by: Jordan Whited <jordan@tailscale.com>
		
			
				
	
	
		
			302 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| package stun_test
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/hex"
 | |
| 	"fmt"
 | |
| 	"net/netip"
 | |
| 	"testing"
 | |
| 
 | |
| 	"tailscale.com/net/stun"
 | |
| 	"tailscale.com/util/must"
 | |
| )
 | |
| 
 | |
| // TODO(bradfitz): fuzz this.
 | |
| 
 | |
| func ExampleRequest() {
 | |
| 	txID := stun.NewTxID()
 | |
| 	req := stun.Request(txID)
 | |
| 	fmt.Printf("%x\n", req)
 | |
| }
 | |
| 
 | |
| var responseTests = []struct {
 | |
| 	name     string
 | |
| 	data     []byte
 | |
| 	wantTID  []byte
 | |
| 	wantAddr netip.Addr
 | |
| 	wantPort uint16
 | |
| }{
 | |
| 	{
 | |
| 		name: "google-1",
 | |
| 		data: []byte{
 | |
| 			0x01, 0x01, 0x00, 0x0c, 0x21, 0x12, 0xa4, 0x42,
 | |
| 			0x23, 0x60, 0xb1, 0x1e, 0x3e, 0xc6, 0x8f, 0xfa,
 | |
| 			0x93, 0xe0, 0x80, 0x07, 0x00, 0x20, 0x00, 0x08,
 | |
| 			0x00, 0x01, 0xc7, 0x86, 0x69, 0x57, 0x85, 0x6f,
 | |
| 		},
 | |
| 		wantTID: []byte{
 | |
| 			0x23, 0x60, 0xb1, 0x1e, 0x3e, 0xc6, 0x8f, 0xfa,
 | |
| 			0x93, 0xe0, 0x80, 0x07,
 | |
| 		},
 | |
| 		wantAddr: netip.AddrFrom4([4]byte{72, 69, 33, 45}),
 | |
| 		wantPort: 59028,
 | |
| 	},
 | |
| 	{
 | |
| 		name: "google-2",
 | |
| 		data: []byte{
 | |
| 			0x01, 0x01, 0x00, 0x0c, 0x21, 0x12, 0xa4, 0x42,
 | |
| 			0xf9, 0xf1, 0x21, 0xcb, 0xde, 0x7d, 0x7c, 0x75,
 | |
| 			0x92, 0x3c, 0xe2, 0x71, 0x00, 0x20, 0x00, 0x08,
 | |
| 			0x00, 0x01, 0xc7, 0x87, 0x69, 0x57, 0x85, 0x6f,
 | |
| 		},
 | |
| 		wantTID: []byte{
 | |
| 			0xf9, 0xf1, 0x21, 0xcb, 0xde, 0x7d, 0x7c, 0x75,
 | |
| 			0x92, 0x3c, 0xe2, 0x71,
 | |
| 		},
 | |
| 		wantAddr: netip.AddrFrom4([4]byte{72, 69, 33, 45}),
 | |
| 		wantPort: 59029,
 | |
| 	},
 | |
| 	{
 | |
| 		name: "stun.sipgate.net:10000",
 | |
| 		data: []byte{
 | |
| 			0x01, 0x01, 0x00, 0x44, 0x21, 0x12, 0xa4, 0x42,
 | |
| 			0x48, 0x2e, 0xb6, 0x47, 0x15, 0xe8, 0xb2, 0x8e,
 | |
| 			0xae, 0xad, 0x64, 0x44, 0x00, 0x01, 0x00, 0x08,
 | |
| 			0x00, 0x01, 0xe4, 0xab, 0x48, 0x45, 0x21, 0x2d,
 | |
| 			0x00, 0x04, 0x00, 0x08, 0x00, 0x01, 0x27, 0x10,
 | |
| 			0xd9, 0x0a, 0x44, 0x98, 0x00, 0x05, 0x00, 0x08,
 | |
| 			0x00, 0x01, 0x27, 0x11, 0xd9, 0x74, 0x7a, 0x8a,
 | |
| 			0x80, 0x20, 0x00, 0x08, 0x00, 0x01, 0xc5, 0xb9,
 | |
| 			0x69, 0x57, 0x85, 0x6f, 0x80, 0x22, 0x00, 0x10,
 | |
| 			0x56, 0x6f, 0x76, 0x69, 0x64, 0x61, 0x2e, 0x6f,
 | |
| 			0x72, 0x67, 0x20, 0x30, 0x2e, 0x39, 0x36, 0x00,
 | |
| 		},
 | |
| 		wantTID: []byte{
 | |
| 			0x48, 0x2e, 0xb6, 0x47, 0x15, 0xe8, 0xb2, 0x8e,
 | |
| 			0xae, 0xad, 0x64, 0x44,
 | |
| 		},
 | |
| 		wantAddr: netip.AddrFrom4([4]byte{72, 69, 33, 45}),
 | |
| 		wantPort: 58539,
 | |
| 	},
 | |
| 	{
 | |
| 		name: "stun.powervoip.com:3478",
 | |
| 		data: []byte{
 | |
| 			0x01, 0x01, 0x00, 0x24, 0x21, 0x12, 0xa4, 0x42,
 | |
| 			0x7e, 0x57, 0x96, 0x68, 0x29, 0xf4, 0x44, 0x60,
 | |
| 			0x9d, 0x1d, 0xea, 0xa6, 0x00, 0x01, 0x00, 0x08,
 | |
| 			0x00, 0x01, 0xe9, 0xd3, 0x48, 0x45, 0x21, 0x2d,
 | |
| 			0x00, 0x04, 0x00, 0x08, 0x00, 0x01, 0x0d, 0x96,
 | |
| 			0x4d, 0x48, 0xa9, 0xd4, 0x00, 0x05, 0x00, 0x08,
 | |
| 			0x00, 0x01, 0x0d, 0x97, 0x4d, 0x48, 0xa9, 0xd5,
 | |
| 		},
 | |
| 		wantTID: []byte{
 | |
| 			0x7e, 0x57, 0x96, 0x68, 0x29, 0xf4, 0x44, 0x60,
 | |
| 			0x9d, 0x1d, 0xea, 0xa6,
 | |
| 		},
 | |
| 		wantAddr: netip.AddrFrom4([4]byte{72, 69, 33, 45}),
 | |
| 		wantPort: 59859,
 | |
| 	},
 | |
| 	{
 | |
| 		name: "in-process pion server",
 | |
| 		data: []byte{
 | |
| 			0x01, 0x01, 0x00, 0x24, 0x21, 0x12, 0xa4, 0x42,
 | |
| 			0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
 | |
| 			0x4f, 0x3e, 0x30, 0x8e, 0x80, 0x22, 0x00, 0x0a,
 | |
| 			0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
 | |
| 			0x65, 0x72, 0x00, 0x00, 0x00, 0x20, 0x00, 0x08,
 | |
| 			0x00, 0x01, 0xce, 0x66, 0x5e, 0x12, 0xa4, 0x43,
 | |
| 			0x80, 0x28, 0x00, 0x04, 0xb6, 0x99, 0xbb, 0x02,
 | |
| 			0x01, 0x01, 0x00, 0x24, 0x21, 0x12, 0xa4, 0x42,
 | |
| 		},
 | |
| 		wantTID: []byte{
 | |
| 			0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
 | |
| 			0x4f, 0x3e, 0x30, 0x8e,
 | |
| 		},
 | |
| 		wantAddr: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
 | |
| 		wantPort: 61300,
 | |
| 	},
 | |
| 	{
 | |
| 		name: "stuntman-server ipv6",
 | |
| 		data: []byte{
 | |
| 			0x01, 0x01, 0x00, 0x48, 0x21, 0x12, 0xa4, 0x42,
 | |
| 			0x06, 0xf5, 0x66, 0x85, 0xd2, 0x8a, 0xf3, 0xe6,
 | |
| 			0x9c, 0xe3, 0x41, 0xe2, 0x00, 0x01, 0x00, 0x14,
 | |
| 			0x00, 0x02, 0x90, 0xce, 0x26, 0x02, 0x00, 0xd1,
 | |
| 			0xb4, 0xcf, 0xc1, 0x00, 0x38, 0xb2, 0x31, 0xff,
 | |
| 			0xfe, 0xef, 0x96, 0xf6, 0x80, 0x2b, 0x00, 0x14,
 | |
| 			0x00, 0x02, 0x0d, 0x96, 0x26, 0x04, 0xa8, 0x80,
 | |
| 			0x00, 0x02, 0x00, 0xd1, 0x00, 0x00, 0x00, 0x00,
 | |
| 			0x00, 0xc5, 0x70, 0x01, 0x00, 0x20, 0x00, 0x14,
 | |
| 			0x00, 0x02, 0xb1, 0xdc, 0x07, 0x10, 0xa4, 0x93,
 | |
| 			0xb2, 0x3a, 0xa7, 0x85, 0xea, 0x38, 0xc2, 0x19,
 | |
| 			0x62, 0x0c, 0xd7, 0x14,
 | |
| 		},
 | |
| 		wantTID: []byte{
 | |
| 			6, 245, 102, 133, 210, 138, 243, 230, 156, 227,
 | |
| 			65, 226,
 | |
| 		},
 | |
| 		wantAddr: netip.MustParseAddr("2602:d1:b4cf:c100:38b2:31ff:feef:96f6"),
 | |
| 		wantPort: 37070,
 | |
| 	},
 | |
| 
 | |
| 	// Testing STUN attribute padding rules using STUN software attribute
 | |
| 	// with values of 1 & 3 length respectively before the XorMappedAddress attribute
 | |
| 	{
 | |
| 		name: "software-a",
 | |
| 		data: []byte{
 | |
| 			0x01, 0x01, 0x00, 0x14, 0x21, 0x12, 0xa4, 0x42,
 | |
| 			0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
 | |
| 			0x4f, 0x3e, 0x30, 0x8e, 0x80, 0x22, 0x00, 0x01,
 | |
| 			0x61, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x08,
 | |
| 			0x00, 0x01, 0xce, 0x66, 0x5e, 0x12, 0xa4, 0x43,
 | |
| 		},
 | |
| 		wantTID: []byte{
 | |
| 			0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
 | |
| 			0x4f, 0x3e, 0x30, 0x8e,
 | |
| 		},
 | |
| 		wantAddr: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
 | |
| 		wantPort: 61300,
 | |
| 	},
 | |
| 	{
 | |
| 		name: "software-abc",
 | |
| 		data: []byte{
 | |
| 			0x01, 0x01, 0x00, 0x14, 0x21, 0x12, 0xa4, 0x42,
 | |
| 			0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
 | |
| 			0x4f, 0x3e, 0x30, 0x8e, 0x80, 0x22, 0x00, 0x03,
 | |
| 			0x61, 0x62, 0x63, 0x00, 0x00, 0x20, 0x00, 0x08,
 | |
| 			0x00, 0x01, 0xce, 0x66, 0x5e, 0x12, 0xa4, 0x43,
 | |
| 		},
 | |
| 		wantTID: []byte{
 | |
| 			0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
 | |
| 			0x4f, 0x3e, 0x30, 0x8e,
 | |
| 		},
 | |
| 		wantAddr: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
 | |
| 		wantPort: 61300,
 | |
| 	},
 | |
| 	{
 | |
| 		name:     "no-4in6",
 | |
| 		data:     must.Get(hex.DecodeString("010100182112a4424fd5d202dcb37d31fc773306002000140002cd3d2112a4424fd5d202dcb382ce2dc3fcc7")),
 | |
| 		wantTID:  []byte{79, 213, 210, 2, 220, 179, 125, 49, 252, 119, 51, 6},
 | |
| 		wantAddr: netip.AddrFrom4([4]byte{209, 180, 207, 193}),
 | |
| 		wantPort: 60463,
 | |
| 	},
 | |
| }
 | |
| 
 | |
| func TestParseResponse(t *testing.T) {
 | |
| 	subtest := func(t *testing.T, i int) {
 | |
| 		test := responseTests[i]
 | |
| 		tID, addrPort, err := stun.ParseResponse(test.data)
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 
 | |
| 		if !bytes.Equal(tID[:], test.wantTID) {
 | |
| 			t.Errorf("tid=%v, want %v", tID[:], test.wantTID)
 | |
| 		}
 | |
| 		if addrPort.Addr().Compare(test.wantAddr) != 0 {
 | |
| 			t.Errorf("addr=%v, want %v", addrPort.Addr(), test.wantAddr)
 | |
| 		}
 | |
| 		if addrPort.Port() != test.wantPort {
 | |
| 			t.Errorf("port=%d, want %d", addrPort.Port(), test.wantPort)
 | |
| 		}
 | |
| 	}
 | |
| 	for i, test := range responseTests {
 | |
| 		t.Run(test.name, func(t *testing.T) {
 | |
| 			subtest(t, i)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestIs(t *testing.T) {
 | |
| 	const magicCookie = "\x21\x12\xa4\x42"
 | |
| 	tests := []struct {
 | |
| 		in   string
 | |
| 		want bool
 | |
| 	}{
 | |
| 		{"", false},
 | |
| 		{"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", false},
 | |
| 		{"\x00\x00\x00\x00" + magicCookie + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", false},
 | |
| 		{"\x00\x00\x00\x00" + magicCookie + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", true},
 | |
| 		{"\x00\x00\x00\x00" + magicCookie + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00foo", true},
 | |
| 		// high bits set:
 | |
| 		{"\xf0\x00\x00\x00" + magicCookie + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", false},
 | |
| 		{"\x40\x00\x00\x00" + magicCookie + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", false},
 | |
| 		// first byte non-zero, but not high bits:
 | |
| 		{"\x20\x00\x00\x00" + magicCookie + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", true},
 | |
| 	}
 | |
| 	for i, tt := range tests {
 | |
| 		pkt := []byte(tt.in)
 | |
| 		got := stun.Is(pkt)
 | |
| 		if got != tt.want {
 | |
| 			t.Errorf("%d. In(%q (%v)) = %v; want %v", i, pkt, pkt, got, tt.want)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestParseBindingRequest(t *testing.T) {
 | |
| 	tx := stun.NewTxID()
 | |
| 	req := stun.Request(tx)
 | |
| 	gotTx, err := stun.ParseBindingRequest(req)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	if gotTx != tx {
 | |
| 		t.Errorf("original txID %q != got txID %q", tx, gotTx)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestResponse(t *testing.T) {
 | |
| 	txN := func(n int) (x stun.TxID) {
 | |
| 		for i := range x {
 | |
| 			x[i] = byte(n)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	tests := []struct {
 | |
| 		tx   stun.TxID
 | |
| 		addr netip.Addr
 | |
| 		port uint16
 | |
| 	}{
 | |
| 		{tx: txN(1), addr: netip.MustParseAddr("1.2.3.4"), port: 254},
 | |
| 		{tx: txN(2), addr: netip.MustParseAddr("1.2.3.4"), port: 257},
 | |
| 		{tx: txN(3), addr: netip.MustParseAddr("1::4"), port: 254},
 | |
| 		{tx: txN(4), addr: netip.MustParseAddr("1::4"), port: 257},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		res := stun.Response(tt.tx, netip.AddrPortFrom(tt.addr, tt.port))
 | |
| 		tx2, addr2, err := stun.ParseResponse(res)
 | |
| 		if err != nil {
 | |
| 			t.Errorf("TX %x: error: %v", tt.tx, err)
 | |
| 			continue
 | |
| 		}
 | |
| 		if tt.tx != tx2 {
 | |
| 			t.Errorf("TX %x: got TxID = %v", tt.tx, tx2)
 | |
| 		}
 | |
| 		if tt.addr.Compare(addr2.Addr()) != 0 {
 | |
| 			t.Errorf("TX %x: addr = %v; want %v", tt.tx, addr2.Addr(), tt.addr)
 | |
| 		}
 | |
| 		if tt.port != addr2.Port() {
 | |
| 			t.Errorf("TX %x: port = %v; want %v", tt.tx, addr2.Port(), tt.port)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestAttrOrderForXdpDERP(t *testing.T) {
 | |
| 	// package derp/xdp assumes attribute order. This test ensures we don't
 | |
| 	// drift and break that assumption.
 | |
| 	txID := stun.NewTxID()
 | |
| 	req := stun.Request(txID)
 | |
| 	if len(req) < 20+12 {
 | |
| 		t.Fatal("too short")
 | |
| 	}
 | |
| 	if !bytes.Equal(req[20:22], []byte{0x80, 0x22}) {
 | |
| 		t.Fatal("the first attr is not of type software")
 | |
| 	}
 | |
| 	if !bytes.Equal(req[24:32], []byte("tailnode")) {
 | |
| 		t.Fatal("unexpected software attr value")
 | |
| 	}
 | |
| }
 |