wgengine/wgcfg: use just the hexlified node key as the WireGuard endpoint.

The node key is all magicsock needs to find the endpoint that WireGuard
needs.

Updates #2752

Signed-off-by: David Anderson <danderson@tailscale.com>
pull/2773/head
David Anderson 3 years ago committed by Dave Anderson
parent d00341360f
commit bb10443edf

@ -19,6 +19,7 @@ import (
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/dnstype" "tailscale.com/types/dnstype"
"tailscale.com/types/ipproto" "tailscale.com/types/ipproto"
"tailscale.com/types/wgkey"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
@ -137,7 +138,7 @@ func getVal() []interface{} {
Addresses: []netaddr.IPPrefix{netaddr.IPPrefixFrom(netaddr.IPFrom16([16]byte{3: 3}), 5)}, Addresses: []netaddr.IPPrefix{netaddr.IPPrefixFrom(netaddr.IPFrom16([16]byte{3: 3}), 5)},
Peers: []wgcfg.Peer{ Peers: []wgcfg.Peer{
{ {
Endpoints: wgcfg.Endpoints{}, PublicKey: wgkey.Key{},
}, },
}, },
}, },

@ -102,9 +102,6 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netadd
for _, ep := range st.LocalAddrs { for _, ep := range st.LocalAddrs {
eps = append(eps, ep.Addr.String()) eps = append(eps, ep.Addr.String())
} }
endpoint := wgcfg.Endpoints{
PublicKey: c1.PrivateKey.Public(),
}
n := tailcfg.Node{ n := tailcfg.Node{
ID: tailcfg.NodeID(0), ID: tailcfg.NodeID(0),
@ -122,7 +119,6 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netadd
p := wgcfg.Peer{ p := wgcfg.Peer{
PublicKey: c1.PrivateKey.Public(), PublicKey: c1.PrivateKey.Public(),
AllowedIPs: []netaddr.IPPrefix{a1}, AllowedIPs: []netaddr.IPPrefix{a1},
Endpoints: endpoint,
} }
c2.Peers = []wgcfg.Peer{p} c2.Peers = []wgcfg.Peer{p}
e2.Reconfig(&c2, &router.Config{}, new(dns.Config), nil) e2.Reconfig(&c2, &router.Config{}, new(dns.Config), nil)
@ -143,9 +139,6 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netadd
for _, ep := range st.LocalAddrs { for _, ep := range st.LocalAddrs {
eps = append(eps, ep.Addr.String()) eps = append(eps, ep.Addr.String())
} }
endpoint := wgcfg.Endpoints{
PublicKey: c2.PrivateKey.Public(),
}
n := tailcfg.Node{ n := tailcfg.Node{
ID: tailcfg.NodeID(0), ID: tailcfg.NodeID(0),
@ -163,7 +156,6 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netadd
p := wgcfg.Peer{ p := wgcfg.Peer{
PublicKey: c2.PrivateKey.Public(), PublicKey: c2.PrivateKey.Public(),
AllowedIPs: []netaddr.IPPrefix{a2}, AllowedIPs: []netaddr.IPPrefix{a2},
Endpoints: endpoint,
} }
c1.Peers = []wgcfg.Peer{p} c1.Peers = []wgcfg.Peer{p}
e1.Reconfig(&c1, &router.Config{}, new(dns.Config), nil) e1.Reconfig(&c1, &router.Config{}, new(dns.Config), nil)

@ -11,7 +11,6 @@ import (
"context" "context"
crand "crypto/rand" crand "crypto/rand"
"encoding/binary" "encoding/binary"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"hash/fnv" "hash/fnv"
@ -55,7 +54,6 @@ import (
"tailscale.com/util/uniq" "tailscale.com/util/uniq"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/wgengine/monitor" "tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/wgcfg"
) )
// useDerpRoute reports whether magicsock should enable the DERP // useDerpRoute reports whether magicsock should enable the DERP
@ -2101,20 +2099,7 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
ep.discoKey = n.DiscoKey ep.discoKey = n.DiscoKey
ep.discoShort = n.DiscoKey.ShortString() ep.discoShort = n.DiscoKey.ShortString()
} }
epDef := wgcfg.Endpoints{ ep.wgEndpoint = (wgkey.Key(n.Key)).HexString()
PublicKey: wgkey.Key(n.Key),
DiscoKey: n.DiscoKey,
}
// We have to make the endpoint string we return to
// WireGuard be the right kind of json that wgcfg expects
// to get back out of uapi, so we have to do this somewhat
// unnecessary json encoding here.
// TODO(danderson): remove this in the wgcfg.Endpoints refactor.
epBytes, err := json.Marshal(epDef)
if err != nil {
c.logf("[unexpected] magicsock: creating endpoint: failed to marshal endpoints json %w", err)
}
ep.wgEndpoint = string(epBytes)
ep.initFakeUDPAddr() ep.initFakeUDPAddr()
c.logf("magicsock: created endpoint key=%s: disco=%s; %v", n.Key.ShortString(), n.DiscoKey.ShortString(), logger.ArgWriter(func(w *bufio.Writer) { c.logf("magicsock: created endpoint key=%s: disco=%s; %v", n.Key.ShortString(), n.DiscoKey.ShortString(), logger.ArgWriter(func(w *bufio.Writer) {
const derpPrefix = "127.3.3.40:" const derpPrefix = "127.3.3.40:"
@ -2636,14 +2621,12 @@ func packIPPort(ua netaddr.IPPort) []byte {
} }
// ParseEndpoint is called by WireGuard to connect to an endpoint. // ParseEndpoint is called by WireGuard to connect to an endpoint.
// endpointStr is a json-serialized wgcfg.Endpoints struct. func (c *Conn) ParseEndpoint(nodeKeyStr string) (conn.Endpoint, error) {
func (c *Conn) ParseEndpoint(endpointStr string) (conn.Endpoint, error) { k, err := wgkey.ParseHex(nodeKeyStr)
var endpoints wgcfg.Endpoints
err := json.Unmarshal([]byte(endpointStr), &endpoints)
if err != nil { if err != nil {
return nil, fmt.Errorf("magicsock: ParseEndpoint: json.Unmarshal failed on %q: %w", endpointStr, err) return nil, fmt.Errorf("magicsock: ParseEndpoint: parse failed on %q: %w", nodeKeyStr, err)
} }
pk := key.Public(endpoints.PublicKey) pk := tailcfg.NodeKey(k)
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()

@ -9,7 +9,6 @@ import (
"context" "context"
crand "crypto/rand" crand "crypto/rand"
"crypto/tls" "crypto/tls"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -942,11 +941,8 @@ func testTwoDevicePing(t *testing.T, d *devices) {
Peers: []wgcfg.Peer{ Peers: []wgcfg.Peer{
wgcfg.Peer{ wgcfg.Peer{
PublicKey: m2.privateKey.Public(), PublicKey: m2.privateKey.Public(),
DiscoKey: m2.conn.DiscoPublicKey(),
AllowedIPs: []netaddr.IPPrefix{netaddr.MustParseIPPrefix("1.0.0.2/32")}, AllowedIPs: []netaddr.IPPrefix{netaddr.MustParseIPPrefix("1.0.0.2/32")},
Endpoints: wgcfg.Endpoints{
PublicKey: m2.privateKey.Public(),
DiscoKey: m2.conn.DiscoPublicKey(),
},
}, },
}, },
} }
@ -957,11 +953,8 @@ func testTwoDevicePing(t *testing.T, d *devices) {
Peers: []wgcfg.Peer{ Peers: []wgcfg.Peer{
wgcfg.Peer{ wgcfg.Peer{
PublicKey: m1.privateKey.Public(), PublicKey: m1.privateKey.Public(),
DiscoKey: m1.conn.DiscoPublicKey(),
AllowedIPs: []netaddr.IPPrefix{netaddr.MustParseIPPrefix("1.0.0.1/32")}, AllowedIPs: []netaddr.IPPrefix{netaddr.MustParseIPPrefix("1.0.0.1/32")},
Endpoints: wgcfg.Endpoints{
PublicKey: m1.privateKey.Public(),
DiscoKey: m1.conn.DiscoPublicKey(),
},
}, },
}, },
} }
@ -1158,19 +1151,6 @@ func newTestConn(t testing.TB) *Conn {
return conn return conn
} }
func makeEndpoint(tb testing.TB, public tailcfg.NodeKey, disco tailcfg.DiscoKey) string {
tb.Helper()
ep := wgcfg.Endpoints{
PublicKey: wgkey.Key(public),
DiscoKey: disco,
}
buf, err := json.Marshal(ep)
if err != nil {
tb.Fatal(err)
}
return string(buf)
}
// addTestEndpoint sets conn's network map to a single peer expected // addTestEndpoint sets conn's network map to a single peer expected
// to receive packets from sendConn (or DERP), and returns that peer's // to receive packets from sendConn (or DERP), and returns that peer's
// nodekey and discokey. // nodekey and discokey.
@ -1190,7 +1170,7 @@ func addTestEndpoint(tb testing.TB, conn *Conn, sendConn net.PacketConn) (tailcf
}, },
}) })
conn.SetPrivateKey(wgkey.Private{0: 1}) conn.SetPrivateKey(wgkey.Private{0: 1})
_, err := conn.ParseEndpoint(makeEndpoint(tb, nodeKey, discoKey)) _, err := conn.ParseEndpoint(wgkey.Key(nodeKey).HexString())
if err != nil { if err != nil {
tb.Fatal(err) tb.Fatal(err)
} }
@ -1374,7 +1354,7 @@ func TestSetNetworkMapChangingNodeKey(t *testing.T) {
}, },
}, },
}) })
_, err := conn.ParseEndpoint(makeEndpoint(t, nodeKey1, discoKey)) _, err := conn.ParseEndpoint(wgkey.Key(nodeKey1).HexString())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -843,19 +843,19 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
{ {
prevEP := make(map[tailcfg.NodeKey]tailcfg.DiscoKey) prevEP := make(map[tailcfg.NodeKey]tailcfg.DiscoKey)
for i := range e.lastCfgFull.Peers { for i := range e.lastCfgFull.Peers {
if p := &e.lastCfgFull.Peers[i]; !p.Endpoints.DiscoKey.IsZero() { if p := &e.lastCfgFull.Peers[i]; !p.DiscoKey.IsZero() {
prevEP[tailcfg.NodeKey(p.PublicKey)] = p.Endpoints.DiscoKey prevEP[tailcfg.NodeKey(p.PublicKey)] = p.DiscoKey
} }
} }
for i := range cfg.Peers { for i := range cfg.Peers {
p := &cfg.Peers[i] p := &cfg.Peers[i]
if p.Endpoints.DiscoKey.IsZero() { if p.DiscoKey.IsZero() {
continue continue
} }
pub := tailcfg.NodeKey(p.PublicKey) pub := tailcfg.NodeKey(p.PublicKey)
if old, ok := prevEP[pub]; ok && old != p.Endpoints.DiscoKey { if old, ok := prevEP[pub]; ok && old != p.DiscoKey {
discoChanged[pub] = true discoChanged[pub] = true
e.logf("wgengine: Reconfig: %s changed from %q to %q", pub.ShortString(), old, p.Endpoints) e.logf("wgengine: Reconfig: %s changed from %q to %q", pub.ShortString(), old, p.DiscoKey)
} }
} }
} }

@ -114,7 +114,6 @@ func TestUserspaceEngineReconfig(t *testing.T) {
AllowedIPs: []netaddr.IPPrefix{ AllowedIPs: []netaddr.IPPrefix{
netaddr.IPPrefixFrom(netaddr.IPv4(100, 100, 99, 1), 32), netaddr.IPPrefixFrom(netaddr.IPv4(100, 100, 99, 1), 32),
}, },
Endpoints: wgcfg.Endpoints{PublicKey: wgkey.Key(nkFromHex(nodeHex))},
}, },
}, },
} }
@ -168,10 +167,10 @@ func TestUserspaceEnginePortReconfig(t *testing.T) {
cfg := &wgcfg.Config{ cfg := &wgcfg.Config{
Peers: []wgcfg.Peer{ Peers: []wgcfg.Peer{
{ {
PublicKey: wgkey.Key(nodeKey),
AllowedIPs: []netaddr.IPPrefix{ AllowedIPs: []netaddr.IPPrefix{
netaddr.IPPrefixFrom(netaddr.IPv4(100, 100, 99, 1), 32), netaddr.IPPrefixFrom(netaddr.IPv4(100, 100, 99, 1), 32),
}, },
Endpoints: wgcfg.Endpoints{PublicKey: wgkey.Key(nodeKey)},
}, },
}, },
} }

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Code generated by tailscale.com/cmd/cloner -type Config,Peer,Endpoints; DO NOT EDIT. // Code generated by tailscale.com/cmd/cloner -type Config,Peer; DO NOT EDIT.
package wgcfg package wgcfg
@ -30,7 +30,7 @@ func (src *Config) Clone() *Config {
} }
// A compilation failure here means this code must be regenerated, with command: // A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type Config,Peer,Endpoints // tailscale.com/cmd/cloner -type Config,Peer
var _ConfigNeedsRegeneration = Config(struct { var _ConfigNeedsRegeneration = Config(struct {
Name string Name string
PrivateKey wgkey.Private PrivateKey wgkey.Private
@ -53,28 +53,10 @@ func (src *Peer) Clone() *Peer {
} }
// A compilation failure here means this code must be regenerated, with command: // A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type Config,Peer,Endpoints // tailscale.com/cmd/cloner -type Config,Peer
var _PeerNeedsRegeneration = Peer(struct { var _PeerNeedsRegeneration = Peer(struct {
PublicKey wgkey.Key PublicKey wgkey.Key
DiscoKey tailcfg.DiscoKey
AllowedIPs []netaddr.IPPrefix AllowedIPs []netaddr.IPPrefix
Endpoints Endpoints
PersistentKeepalive uint16 PersistentKeepalive uint16
}{}) }{})
// Clone makes a deep copy of Endpoints.
// The result aliases no memory with the original.
func (src *Endpoints) Clone() *Endpoints {
if src == nil {
return nil
}
dst := new(Endpoints)
*dst = *src
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type Config,Peer,Endpoints
var _EndpointsNeedsRegeneration = Endpoints(struct {
PublicKey wgkey.Key
DiscoKey tailcfg.DiscoKey
}{})

@ -11,7 +11,7 @@ import (
"tailscale.com/types/wgkey" "tailscale.com/types/wgkey"
) )
//go:generate go run tailscale.com/cmd/cloner -type=Config,Peer,Endpoints -output=clone.go //go:generate go run tailscale.com/cmd/cloner -type=Config,Peer -output=clone.go
// Config is a WireGuard configuration. // Config is a WireGuard configuration.
// It only supports the set of things Tailscale uses. // It only supports the set of things Tailscale uses.
@ -26,22 +26,11 @@ type Config struct {
type Peer struct { type Peer struct {
PublicKey wgkey.Key PublicKey wgkey.Key
DiscoKey tailcfg.DiscoKey // present only so we can handle restarts within wgengine, not passed to WireGuard
AllowedIPs []netaddr.IPPrefix AllowedIPs []netaddr.IPPrefix
Endpoints Endpoints
PersistentKeepalive uint16 PersistentKeepalive uint16
} }
// Endpoints represents the routes to reach a remote node.
// It is serialized and provided to wireguard-go as a conn.Endpoint.
//
// TODO: change name, it's now just a pair of keys representing a peer.
type Endpoints struct {
// PublicKey is the public key for the remote node.
PublicKey wgkey.Key `json:"pk"`
// DiscoKey is the disco key associated with the remote node.
DiscoKey tailcfg.DiscoKey `json:"dk,omitempty"`
}
// PeerWithKey returns the Peer with key k and reports whether it was found. // PeerWithKey returns the Peer with key k and reports whether it was found.
func (config Config) PeerWithKey(k wgkey.Key) (Peer, bool) { func (config Config) PeerWithKey(k wgkey.Key) (Peer, bool) {
for _, p := range config.Peers { for _, p := range config.Peers {

@ -10,7 +10,6 @@ import (
"io" "io"
"net" "net"
"os" "os"
"reflect"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -129,7 +128,7 @@ func TestDeviceConfig(t *testing.T) {
}) })
t.Run("device1 modify peer", func(t *testing.T) { t.Run("device1 modify peer", func(t *testing.T) {
cfg1.Peers[0].Endpoints.DiscoKey = tailcfg.DiscoKey{1} cfg1.Peers[0].DiscoKey = tailcfg.DiscoKey{1}
if err := ReconfigDevice(device1, cfg1, t.Logf); err != nil { if err := ReconfigDevice(device1, cfg1, t.Logf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -137,7 +136,7 @@ func TestDeviceConfig(t *testing.T) {
}) })
t.Run("device1 replace endpoint", func(t *testing.T) { t.Run("device1 replace endpoint", func(t *testing.T) {
cfg1.Peers[0].Endpoints.DiscoKey = tailcfg.DiscoKey{2} cfg1.Peers[0].DiscoKey = tailcfg.DiscoKey{2}
if err := ReconfigDevice(device1, cfg1, t.Logf); err != nil { if err := ReconfigDevice(device1, cfg1, t.Logf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -177,8 +176,7 @@ func TestDeviceConfig(t *testing.T) {
return p return p
} }
peersEqual := func(p, q Peer) bool { peersEqual := func(p, q Peer) bool {
return p.PublicKey == q.PublicKey && p.PersistentKeepalive == q.PersistentKeepalive && return p.PublicKey == q.PublicKey && p.DiscoKey == q.DiscoKey && p.PersistentKeepalive == q.PersistentKeepalive && cidrsEqual(p.AllowedIPs, q.AllowedIPs)
reflect.DeepEqual(p.Endpoints, q.Endpoints) && cidrsEqual(p.AllowedIPs, q.AllowedIPs)
} }
if !peersEqual(peer0(origCfg), peer0(newCfg)) { if !peersEqual(peer0(origCfg), peer0(newCfg)) {
t.Error("reconfig modified old peer") t.Error("reconfig modified old peer")

@ -71,13 +71,13 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags,
} }
cfg.Peers = append(cfg.Peers, wgcfg.Peer{ cfg.Peers = append(cfg.Peers, wgcfg.Peer{
PublicKey: wgkey.Key(peer.Key), PublicKey: wgkey.Key(peer.Key),
DiscoKey: peer.DiscoKey,
}) })
cpeer := &cfg.Peers[len(cfg.Peers)-1] cpeer := &cfg.Peers[len(cfg.Peers)-1]
if peer.KeepAlive { if peer.KeepAlive {
cpeer.PersistentKeepalive = 25 // seconds cpeer.PersistentKeepalive = 25 // seconds
} }
cpeer.Endpoints = wgcfg.Endpoints{PublicKey: wgkey.Key(peer.Key), DiscoKey: peer.DiscoKey}
didExitNodeWarn := false didExitNodeWarn := false
for _, allowedIP := range peer.AllowedIPs { for _, allowedIP := range peer.AllowedIPs {
if allowedIP.Bits() == 0 && peer.StableID != exitNode { if allowedIP.Bits() == 0 && peer.StableID != exitNode {

@ -7,7 +7,6 @@ package wgcfg
import ( import (
"bufio" "bufio"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -156,8 +155,17 @@ func (cfg *Config) handlePublicKeyLine(valueBytes []byte) (*Peer, error) {
func (cfg *Config) handlePeerLine(peer *Peer, key, value mem.RO, valueBytes []byte) error { func (cfg *Config) handlePeerLine(peer *Peer, key, value mem.RO, valueBytes []byte) error {
switch { switch {
case key.EqualString("endpoint"): case key.EqualString("endpoint"):
if err := json.Unmarshal(valueBytes, &peer.Endpoints); err != nil { // TODO: our key types are all over the place, and this
return err // particular one can't parse a mem.RO or a []byte without
// allocating. We don't reconfigure wireguard often though, so
// this is okay.
s := value.StringCopy()
k, err := wgkey.ParseHex(s)
if err != nil {
return fmt.Errorf("invalid endpoint %q for peer %q, expected a hex public key", s, peer.PublicKey.ShortString())
}
if k != peer.PublicKey {
return fmt.Errorf("unexpected endpoint %q for peer %q, expected the peer's public key", s, peer.PublicKey.ShortString())
} }
case key.EqualString("persistent_keepalive_interval"): case key.EqualString("persistent_keepalive_interval"):
n, err := mem.ParseUint(value, 10, 16) n, err := mem.ParseUint(value, 10, 16)

@ -75,7 +75,6 @@ func BenchmarkFromUAPI(b *testing.B) {
peer := Peer{ peer := Peer{
PublicKey: k1, PublicKey: k1,
AllowedIPs: []netaddr.IPPrefix{ip1}, AllowedIPs: []netaddr.IPPrefix{ip1},
Endpoints: Endpoints{PublicKey: k1},
} }
cfg1 := &Config{ cfg1 := &Config{
PrivateKey: wgkey.Private(pk1), PrivateKey: wgkey.Private(pk1),

@ -5,7 +5,6 @@
package wgcfg package wgcfg
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
@ -48,16 +47,15 @@ func (cfg *Config) ToUAPI(w io.Writer, prev *Config) error {
// Add/configure all new peers. // Add/configure all new peers.
for _, p := range cfg.Peers { for _, p := range cfg.Peers {
oldPeer := old[p.PublicKey] oldPeer, wasPresent := old[p.PublicKey]
setPeer(p) setPeer(p)
set("protocol_version", "1") set("protocol_version", "1")
if oldPeer.Endpoints != p.Endpoints { // Avoid setting endpoints if the correct one is already known
buf, err := json.Marshal(p.Endpoints) // to WireGuard, because doing so generates a bit more work in
if err != nil { // calling magicsock's ParseEndpoint for effectively a no-op.
return err if !wasPresent {
} set("endpoint", p.PublicKey.HexString())
set("endpoint", string(buf))
} }
// TODO: replace_allowed_ips is expensive. // TODO: replace_allowed_ips is expensive.

Loading…
Cancel
Save