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.
240 lines
6.1 KiB
Go
240 lines
6.1 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package prober
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"tailscale.com/derp"
|
|
"tailscale.com/derp/derphttp"
|
|
"tailscale.com/net/netmon"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
func TestDerpProber(t *testing.T) {
|
|
dm := &tailcfg.DERPMap{
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
0: {
|
|
RegionID: 0,
|
|
RegionCode: "zero",
|
|
Nodes: []*tailcfg.DERPNode{
|
|
{
|
|
Name: "n1",
|
|
RegionID: 0,
|
|
HostName: "derpn1.tailscale.test",
|
|
IPv4: "1.1.1.1",
|
|
IPv6: "::1",
|
|
},
|
|
{
|
|
Name: "n2",
|
|
RegionID: 0,
|
|
HostName: "derpn2.tailscale.test",
|
|
IPv4: "1.1.1.1",
|
|
IPv6: "::1",
|
|
},
|
|
},
|
|
},
|
|
1: {
|
|
RegionID: 1,
|
|
RegionCode: "one",
|
|
Nodes: []*tailcfg.DERPNode{
|
|
{
|
|
Name: "n3",
|
|
RegionID: 0,
|
|
HostName: "derpn3.tailscale.test",
|
|
IPv4: "1.1.1.1",
|
|
IPv6: "::1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
resp, err := json.Marshal(dm)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w.Write(resp)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
clk := newFakeTime()
|
|
p := newForTest(clk.Now, clk.NewTicker)
|
|
dp := &derpProber{
|
|
p: p,
|
|
derpMapURL: srv.URL,
|
|
tlsInterval: time.Second,
|
|
tlsProbeFn: func(_ string) ProbeClass { return FuncProbe(func(context.Context) error { return nil }) },
|
|
udpInterval: time.Second,
|
|
udpProbeFn: func(_ string, _ int) ProbeClass { return FuncProbe(func(context.Context) error { return nil }) },
|
|
meshInterval: time.Second,
|
|
meshProbeFn: func(_, _ string) ProbeClass { return FuncProbe(func(context.Context) error { return nil }) },
|
|
nodes: make(map[string]*tailcfg.DERPNode),
|
|
probes: make(map[string]*Probe),
|
|
regionCode: "zero",
|
|
}
|
|
if err := dp.probeMapFn(context.Background()); err != nil {
|
|
t.Errorf("unexpected probeMapFn() error: %s", err)
|
|
}
|
|
if len(dp.nodes) != 2 || dp.nodes["n1"] == nil || dp.nodes["n2"] == nil {
|
|
t.Errorf("unexpected nodes: %+v", dp.nodes)
|
|
}
|
|
// Probes expected for two nodes:
|
|
// - 3 regular probes per node (TLS, UDPv4, UDPv6)
|
|
// - 4 mesh probes (N1->N2, N1->N1, N2->N1, N2->N2)
|
|
if len(dp.probes) != 10 {
|
|
t.Errorf("unexpected probes: %+v", dp.probes)
|
|
}
|
|
|
|
// Add one more node and check that probes got created.
|
|
dm.Regions[0].Nodes = append(dm.Regions[0].Nodes, &tailcfg.DERPNode{
|
|
Name: "n4",
|
|
RegionID: 0,
|
|
HostName: "derpn4.tailscale.test",
|
|
IPv4: "1.1.1.1",
|
|
IPv6: "::1",
|
|
})
|
|
if err := dp.probeMapFn(context.Background()); err != nil {
|
|
t.Errorf("unexpected probeMapFn() error: %s", err)
|
|
}
|
|
if len(dp.nodes) != 3 {
|
|
t.Errorf("unexpected nodes: %+v", dp.nodes)
|
|
}
|
|
// 9 regular probes + 9 mesh probes
|
|
if len(dp.probes) != 18 {
|
|
t.Errorf("unexpected probes: %+v", dp.probes)
|
|
}
|
|
|
|
// Remove 2 nodes and check that probes have been destroyed.
|
|
dm.Regions[0].Nodes = dm.Regions[0].Nodes[:1]
|
|
if err := dp.probeMapFn(context.Background()); err != nil {
|
|
t.Errorf("unexpected probeMapFn() error: %s", err)
|
|
}
|
|
if len(dp.nodes) != 1 {
|
|
t.Errorf("unexpected nodes: %+v", dp.nodes)
|
|
}
|
|
// 3 regular probes + 1 mesh probe
|
|
if len(dp.probes) != 4 {
|
|
t.Errorf("unexpected probes: %+v", dp.probes)
|
|
}
|
|
|
|
// Stop filtering regions.
|
|
dp.regionCode = ""
|
|
if err := dp.probeMapFn(context.Background()); err != nil {
|
|
t.Errorf("unexpected probeMapFn() error: %s", err)
|
|
}
|
|
if len(dp.nodes) != 2 {
|
|
t.Errorf("unexpected nodes: %+v", dp.nodes)
|
|
}
|
|
// 6 regular probes + 2 mesh probe
|
|
if len(dp.probes) != 8 {
|
|
t.Errorf("unexpected probes: %+v", dp.probes)
|
|
}
|
|
}
|
|
|
|
func TestRunDerpProbeNodePair(t *testing.T) {
|
|
// os.Setenv("DERP_DEBUG_LOGS", "true")
|
|
serverPrivateKey := key.NewNode()
|
|
s := derp.NewServer(serverPrivateKey, t.Logf)
|
|
defer s.Close()
|
|
|
|
httpsrv := &http.Server{
|
|
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
|
Handler: derphttp.Handler(s),
|
|
}
|
|
ln, err := net.Listen("tcp4", "localhost:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
serverURL := "http://" + ln.Addr().String()
|
|
t.Logf("server URL: %s", serverURL)
|
|
|
|
go func() {
|
|
if err := httpsrv.Serve(ln); err != nil {
|
|
if err == http.ErrServerClosed {
|
|
return
|
|
}
|
|
panic(err)
|
|
}
|
|
}()
|
|
newClient := func() *derphttp.Client {
|
|
c, err := derphttp.NewClient(key.NewNode(), serverURL, t.Logf, netmon.NewStatic())
|
|
if err != nil {
|
|
t.Fatalf("NewClient: %v", err)
|
|
}
|
|
m, err := c.Recv()
|
|
if err != nil {
|
|
t.Fatalf("Recv: %v", err)
|
|
}
|
|
switch m.(type) {
|
|
case derp.ServerInfoMessage:
|
|
default:
|
|
t.Fatalf("unexpected first message type %T", m)
|
|
}
|
|
return c
|
|
}
|
|
|
|
c1 := newClient()
|
|
defer c1.Close()
|
|
c2 := newClient()
|
|
defer c2.Close()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
err = runDerpProbeNodePair(ctx, &tailcfg.DERPNode{Name: "c1"}, &tailcfg.DERPNode{Name: "c2"}, c1, c2, 100_000_000)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func Test_packetsForSize(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
size int
|
|
wantPackets int
|
|
wantUnique bool
|
|
}{
|
|
{"small_unqiue", 8, 1, true},
|
|
{"8k_unique", 8192, 1, true},
|
|
{"full_size_packet", derp.MaxPacketSize, 1, true},
|
|
{"larger_than_one", derp.MaxPacketSize + 1, 2, false},
|
|
{"large", 500000, 8, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hashes := make(map[string]int)
|
|
for range 5 {
|
|
pkts := packetsForSize(int64(tt.size))
|
|
if len(pkts) != tt.wantPackets {
|
|
t.Errorf("packetsForSize(%d) got %d packets, want %d", tt.size, len(pkts), tt.wantPackets)
|
|
}
|
|
var total int
|
|
hash := sha256.New()
|
|
for _, p := range pkts {
|
|
hash.Write(p)
|
|
total += len(p)
|
|
}
|
|
hashes[string(hash.Sum(nil))]++
|
|
if total != tt.size {
|
|
t.Errorf("packetsForSize(%d) returned %d bytes total", tt.size, total)
|
|
}
|
|
}
|
|
unique := len(hashes) > 1
|
|
if unique != tt.wantUnique {
|
|
t.Errorf("packetsForSize(%d) is unique=%v (returned %d different answers); want unique=%v", tt.size, unique, len(hashes), unique)
|
|
}
|
|
})
|
|
}
|
|
}
|