mirror of https://github.com/tailscale/tailscale/
Merge 0698ac8362 into ece6e27f39
commit
e532d37686
@ -1,261 +0,0 @@
|
|||||||
// 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, offset int) 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{} }
|
|
||||||
@ -1,154 +0,0 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
package wgcfg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"tailscale.com/types/key"
|
|
||||||
"tailscale.com/types/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ToUAPI writes cfg in UAPI format to w.
|
|
||||||
// Prev is the previous device Config.
|
|
||||||
//
|
|
||||||
// Prev is required so that we can remove now-defunct peers without having to
|
|
||||||
// remove and re-add all peers, and so that we can avoid writing information
|
|
||||||
// about peers that have not changed since the previous time we wrote our
|
|
||||||
// Config.
|
|
||||||
func (cfg *Config) ToUAPI(logf logger.Logf, w io.Writer, prev *Config) error {
|
|
||||||
var stickyErr error
|
|
||||||
set := func(key, value string) {
|
|
||||||
if stickyErr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err := fmt.Fprintf(w, "%s=%s\n", key, value)
|
|
||||||
if err != nil {
|
|
||||||
stickyErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setUint16 := func(key string, value uint16) {
|
|
||||||
set(key, strconv.FormatUint(uint64(value), 10))
|
|
||||||
}
|
|
||||||
setPeer := func(peer Peer) {
|
|
||||||
set("public_key", peer.PublicKey.UntypedHexString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device config.
|
|
||||||
if !prev.PrivateKey.Equal(cfg.PrivateKey) {
|
|
||||||
set("private_key", cfg.PrivateKey.UntypedHexString())
|
|
||||||
}
|
|
||||||
|
|
||||||
old := make(map[key.NodePublic]Peer)
|
|
||||||
for _, p := range prev.Peers {
|
|
||||||
old[p.PublicKey] = p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add/configure all new peers.
|
|
||||||
for _, p := range cfg.Peers {
|
|
||||||
oldPeer, wasPresent := old[p.PublicKey]
|
|
||||||
|
|
||||||
// We only want to write the peer header/version if we're about
|
|
||||||
// to change something about that peer, or if it's a new peer.
|
|
||||||
// Figure out up-front whether we'll need to do anything for
|
|
||||||
// this peer, and skip doing anything if not.
|
|
||||||
//
|
|
||||||
// If the peer was not present in the previous config, this
|
|
||||||
// implies that this is a new peer; set all of these to 'true'
|
|
||||||
// to ensure that we're writing the full peer configuration.
|
|
||||||
willSetEndpoint := oldPeer.WGEndpoint != p.PublicKey || !wasPresent
|
|
||||||
willChangeIPs := !cidrsEqual(oldPeer.AllowedIPs, p.AllowedIPs) || !wasPresent
|
|
||||||
willChangeKeepalive := oldPeer.PersistentKeepalive != p.PersistentKeepalive // if not wasPresent, no need to redundantly set zero (default)
|
|
||||||
|
|
||||||
if !willSetEndpoint && !willChangeIPs && !willChangeKeepalive {
|
|
||||||
// It's safe to skip doing anything here; wireguard-go
|
|
||||||
// will not remove a peer if it's unspecified unless we
|
|
||||||
// tell it to (which we do below if necessary).
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
setPeer(p)
|
|
||||||
set("protocol_version", "1")
|
|
||||||
|
|
||||||
// Avoid setting endpoints if the correct one is already known
|
|
||||||
// to WireGuard, because doing so generates a bit more work in
|
|
||||||
// calling magicsock's ParseEndpoint for effectively a no-op.
|
|
||||||
if willSetEndpoint {
|
|
||||||
if wasPresent {
|
|
||||||
// We had an endpoint, and it was wrong.
|
|
||||||
// By construction, this should not happen.
|
|
||||||
// If it does, keep going so that we can recover from it,
|
|
||||||
// but log so that we know about it,
|
|
||||||
// because it is an indicator of other failed invariants.
|
|
||||||
// See corp issue 3016.
|
|
||||||
logf("[unexpected] endpoint changed from %s to %s", oldPeer.WGEndpoint, p.PublicKey)
|
|
||||||
}
|
|
||||||
set("endpoint", p.PublicKey.UntypedHexString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace_allowed_ips is expensive.
|
|
||||||
// If p.AllowedIPs is a strict superset of oldPeer.AllowedIPs,
|
|
||||||
// then skip replace_allowed_ips and instead add only
|
|
||||||
// the new ipps with allowed_ip.
|
|
||||||
if willChangeIPs {
|
|
||||||
set("replace_allowed_ips", "true")
|
|
||||||
for _, ipp := range p.AllowedIPs {
|
|
||||||
set("allowed_ip", ipp.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set PersistentKeepalive after the peer is otherwise configured,
|
|
||||||
// because it can trigger handshake packets.
|
|
||||||
if willChangeKeepalive {
|
|
||||||
setUint16("persistent_keepalive_interval", p.PersistentKeepalive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove peers that were present but should no longer be.
|
|
||||||
for _, p := range cfg.Peers {
|
|
||||||
delete(old, p.PublicKey)
|
|
||||||
}
|
|
||||||
for _, p := range old {
|
|
||||||
setPeer(p)
|
|
||||||
set("remove", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if stickyErr != nil {
|
|
||||||
stickyErr = fmt.Errorf("ToUAPI: %w", stickyErr)
|
|
||||||
}
|
|
||||||
return stickyErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func cidrsEqual(x, y []netip.Prefix) bool {
|
|
||||||
// TODO: re-implement using netaddr.IPSet.Equal.
|
|
||||||
if len(x) != len(y) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// First see if they're equal in order, without allocating.
|
|
||||||
exact := true
|
|
||||||
for i := range x {
|
|
||||||
if x[i] != y[i] {
|
|
||||||
exact = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if exact {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, see if they're the same, but out of order.
|
|
||||||
m := make(map[netip.Prefix]bool)
|
|
||||||
for _, v := range x {
|
|
||||||
m[v] = true
|
|
||||||
}
|
|
||||||
for _, v := range y {
|
|
||||||
if !m[v] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue