mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-11 02:34:43 +00:00
71029cea2d
This updates all source files to use a new standard header for copyright and license declaration. Notably, copyright no longer includes a date, and we now use the standard SPDX-License-Identifier header. This commit was done almost entirely mechanically with perl, and then some minimal manual fixes. Updates #6865 Signed-off-by: Will Norris <will@tailscale.com>
262 lines
6.4 KiB
Go
262 lines
6.4 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package wgcfg
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"io"
|
|
"net/netip"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/tailscale/wireguard-go/conn"
|
|
"github.com/tailscale/wireguard-go/device"
|
|
"github.com/tailscale/wireguard-go/tun"
|
|
"go4.org/mem"
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
func TestDeviceConfig(t *testing.T) {
|
|
newK := func() (key.NodePublic, key.NodePrivate) {
|
|
t.Helper()
|
|
k := key.NewNode()
|
|
return k.Public(), k
|
|
}
|
|
k1, pk1 := newK()
|
|
ip1 := netip.MustParsePrefix("10.0.0.1/32")
|
|
|
|
k2, pk2 := newK()
|
|
ip2 := netip.MustParsePrefix("10.0.0.2/32")
|
|
|
|
k3, _ := newK()
|
|
ip3 := netip.MustParsePrefix("10.0.0.3/32")
|
|
|
|
cfg1 := &Config{
|
|
PrivateKey: pk1,
|
|
Peers: []Peer{{
|
|
PublicKey: k2,
|
|
AllowedIPs: []netip.Prefix{ip2},
|
|
}},
|
|
}
|
|
|
|
cfg2 := &Config{
|
|
PrivateKey: pk2,
|
|
Peers: []Peer{{
|
|
PublicKey: k1,
|
|
AllowedIPs: []netip.Prefix{ip1},
|
|
PersistentKeepalive: 5,
|
|
}},
|
|
}
|
|
|
|
device1 := NewDevice(newNilTun(), new(noopBind), device.NewLogger(device.LogLevelError, "device1"))
|
|
device2 := NewDevice(newNilTun(), new(noopBind), device.NewLogger(device.LogLevelError, "device2"))
|
|
defer device1.Close()
|
|
defer device2.Close()
|
|
|
|
cmp := func(t *testing.T, d *device.Device, want *Config) {
|
|
t.Helper()
|
|
got, err := DeviceConfig(d)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prev := new(Config)
|
|
gotbuf := new(strings.Builder)
|
|
err = got.ToUAPI(t.Logf, gotbuf, prev)
|
|
gotStr := gotbuf.String()
|
|
if err != nil {
|
|
t.Errorf("got.ToUAPI(): error: %v", err)
|
|
return
|
|
}
|
|
wantbuf := new(strings.Builder)
|
|
err = want.ToUAPI(t.Logf, wantbuf, prev)
|
|
wantStr := wantbuf.String()
|
|
if err != nil {
|
|
t.Errorf("want.ToUAPI(): error: %v", err)
|
|
return
|
|
}
|
|
if gotStr != wantStr {
|
|
buf := new(bytes.Buffer)
|
|
w := bufio.NewWriter(buf)
|
|
if err := d.IpcGetOperation(w); err != nil {
|
|
t.Errorf("on error, could not IpcGetOperation: %v", err)
|
|
}
|
|
w.Flush()
|
|
t.Errorf("config mismatch:\n---- got:\n%s\n---- want:\n%s\n---- uapi:\n%s", gotStr, wantStr, buf.String())
|
|
}
|
|
}
|
|
|
|
t.Run("device1 config", func(t *testing.T) {
|
|
if err := ReconfigDevice(device1, cfg1, t.Logf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmp(t, device1, cfg1)
|
|
})
|
|
|
|
t.Run("device2 config", func(t *testing.T) {
|
|
if err := ReconfigDevice(device2, cfg2, t.Logf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmp(t, device2, cfg2)
|
|
})
|
|
|
|
// This is only to test that Config and Reconfig are properly synchronized.
|
|
t.Run("device2 config/reconfig", func(t *testing.T) {
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
|
|
go func() {
|
|
ReconfigDevice(device2, cfg2, t.Logf)
|
|
wg.Done()
|
|
}()
|
|
|
|
go func() {
|
|
DeviceConfig(device2)
|
|
wg.Done()
|
|
}()
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("device1 modify peer", func(t *testing.T) {
|
|
cfg1.Peers[0].DiscoKey = key.DiscoPublicFromRaw32(mem.B([]byte{0: 1, 31: 0}))
|
|
if err := ReconfigDevice(device1, cfg1, t.Logf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmp(t, device1, cfg1)
|
|
})
|
|
|
|
t.Run("device1 replace endpoint", func(t *testing.T) {
|
|
cfg1.Peers[0].DiscoKey = key.DiscoPublicFromRaw32(mem.B([]byte{0: 2, 31: 0}))
|
|
if err := ReconfigDevice(device1, cfg1, t.Logf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmp(t, device1, cfg1)
|
|
})
|
|
|
|
t.Run("device1 add new peer", func(t *testing.T) {
|
|
cfg1.Peers = append(cfg1.Peers, Peer{
|
|
PublicKey: k3,
|
|
AllowedIPs: []netip.Prefix{ip3},
|
|
})
|
|
sort.Slice(cfg1.Peers, func(i, j int) bool {
|
|
return cfg1.Peers[i].PublicKey.Less(cfg1.Peers[j].PublicKey)
|
|
})
|
|
|
|
origCfg, err := DeviceConfig(device1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := ReconfigDevice(device1, cfg1, t.Logf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmp(t, device1, cfg1)
|
|
|
|
newCfg, err := DeviceConfig(device1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
peer0 := func(cfg *Config) Peer {
|
|
p, ok := cfg.PeerWithKey(k2)
|
|
if !ok {
|
|
t.Helper()
|
|
t.Fatal("failed to look up peer 2")
|
|
}
|
|
return p
|
|
}
|
|
peersEqual := func(p, q Peer) bool {
|
|
return p.PublicKey == q.PublicKey && p.DiscoKey == q.DiscoKey && p.PersistentKeepalive == q.PersistentKeepalive && cidrsEqual(p.AllowedIPs, q.AllowedIPs)
|
|
}
|
|
if !peersEqual(peer0(origCfg), peer0(newCfg)) {
|
|
t.Error("reconfig modified old peer")
|
|
}
|
|
})
|
|
|
|
t.Run("device1 remove peer", func(t *testing.T) {
|
|
removeKey := cfg1.Peers[len(cfg1.Peers)-1].PublicKey
|
|
cfg1.Peers = cfg1.Peers[:len(cfg1.Peers)-1]
|
|
|
|
if err := ReconfigDevice(device1, cfg1, t.Logf); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmp(t, device1, cfg1)
|
|
|
|
newCfg, err := DeviceConfig(device1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, ok := newCfg.PeerWithKey(removeKey)
|
|
if ok {
|
|
t.Error("reconfig failed to remove peer")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TODO: replace with a loopback tunnel
|
|
type nilTun struct {
|
|
events chan tun.Event
|
|
closed chan struct{}
|
|
}
|
|
|
|
func newNilTun() tun.Device {
|
|
return &nilTun{
|
|
events: make(chan tun.Event),
|
|
closed: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (t *nilTun) File() *os.File { return nil }
|
|
func (t *nilTun) Flush() error { return nil }
|
|
func (t *nilTun) MTU() (int, error) { return 1420, nil }
|
|
func (t *nilTun) Name() (string, error) { return "niltun", nil }
|
|
func (t *nilTun) Events() <-chan tun.Event { return t.events }
|
|
|
|
func (t *nilTun) Read(data [][]byte, sizes []int, offset int) (int, error) {
|
|
<-t.closed
|
|
return 0, io.EOF
|
|
}
|
|
|
|
func (t *nilTun) Write(data [][]byte, offset int) (int, error) {
|
|
<-t.closed
|
|
return 0, io.EOF
|
|
}
|
|
|
|
func (t *nilTun) Close() error {
|
|
close(t.events)
|
|
close(t.closed)
|
|
return nil
|
|
}
|
|
|
|
func (t *nilTun) BatchSize() int { return 1 }
|
|
|
|
// A noopBind is a conn.Bind that does no actual binding work.
|
|
type noopBind struct{}
|
|
|
|
func (noopBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
|
return nil, 1, nil
|
|
}
|
|
func (noopBind) Close() error { return nil }
|
|
func (noopBind) SetMark(mark uint32) error { return nil }
|
|
func (noopBind) Send(b [][]byte, ep conn.Endpoint) error { return nil }
|
|
func (noopBind) ParseEndpoint(s string) (conn.Endpoint, error) {
|
|
return dummyEndpoint(s), nil
|
|
}
|
|
func (noopBind) BatchSize() int { return 1 }
|
|
|
|
// A dummyEndpoint is a string holding the endpoint destination.
|
|
type dummyEndpoint string
|
|
|
|
func (e dummyEndpoint) ClearSrc() {}
|
|
func (e dummyEndpoint) SrcToString() string { return "" }
|
|
func (e dummyEndpoint) DstToString() string { return string(e) }
|
|
func (e dummyEndpoint) DstToBytes() []byte { return nil }
|
|
func (e dummyEndpoint) DstIP() netip.Addr { return netip.Addr{} }
|
|
func (dummyEndpoint) SrcIP() netip.Addr { return netip.Addr{} }
|