diff --git a/internal/deepprint/deepprint_test.go b/internal/deepprint/deepprint_test.go index c7e2031f2..d7198185c 100644 --- a/internal/deepprint/deepprint_test.go +++ b/internal/deepprint/deepprint_test.go @@ -6,6 +6,7 @@ package deepprint import ( "bytes" + "strings" "testing" "inet.af/netaddr" @@ -33,6 +34,8 @@ func TestDeepPrint(t *testing.T) { } } +var dummyHeyKey = strings.Repeat("0", wgcfg.HexKeyLen) + func getVal() []interface{} { return []interface{}{ &wgcfg.Config{ @@ -41,7 +44,7 @@ func getVal() []interface{} { ListenPort: 5, Peers: []wgcfg.Peer{ { - Endpoints: "foo:5", + Endpoints2: dummyHeyKey + "@foo:5", }, }, }, diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 7a437f9e8..58f6751a4 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -2745,7 +2745,6 @@ func (c *Conn) CreateBind(uint16) (conn.Bind, uint16, error) { // is running code that supports active discovery, so CreateEndpoint returns // a discoEndpoint. // - func (c *Conn) CreateEndpoint(pubKey [32]byte, addrs string) (conn.Endpoint, error) { c.mu.Lock() defer c.mu.Unlock() diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 199604614..37ffd89c4 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -639,11 +639,11 @@ func isTrimmablePeer(p *wgcfg.Peer, numPeers int) bool { if forceFullWireguardConfig(numPeers) { return false } - if !isSingleEndpoint(p.Endpoints) { + if !isSingleEndpoint(p.Endpoints2) { return false } - host, _, err := net.SplitHostPort(p.Endpoints) + host, _, err := splitEndpointHostPort(p.Endpoints2) if err != nil { return false } @@ -986,7 +986,13 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) // isSingleEndpoint reports whether endpoints contains exactly one host:port pair. func isSingleEndpoint(s string) bool { - return s != "" && !strings.Contains(s, ",") + return len(s) > wgcfg.HexKeyPrefixLen && !strings.Contains(s, ",") +} + +// splitEndpointHostPort returns net.SplitHostPort of the sole endpoint in s. +func splitEndpointHostPort(s string) (host, port string, err error) { + ep := s[wgcfg.HexKeyPrefixLen:] + return net.SplitHostPort(ep) } func (e *userspaceEngine) GetFilter() *filter.Filter { diff --git a/wgengine/wgcfg/config.go b/wgengine/wgcfg/config.go index 2928e47d2..73497f5e9 100644 --- a/wgengine/wgcfg/config.go +++ b/wgengine/wgcfg/config.go @@ -27,12 +27,21 @@ type Config struct { } type Peer struct { - PublicKey Key - AllowedIPs []netaddr.IPPrefix - Endpoints string // comma-separated host/port pairs: "1.2.3.4:56,[::]:80" + PublicKey Key + AllowedIPs []netaddr.IPPrefix + // Endpoints encodes information about the Peer. + // It has the form: "@1.2.3.4:56,[::]:80". + // The first 65 bytes are a hex-encoded public key, then '@'. + // The remainder is a comma-separated list of host/port pairs. + Endpoints2 string PersistentKeepalive uint16 } +const ( + HexKeyLength = KeySize * 2 // KeySize when encoded as hex bytes + HexKeyPrefixLen = HexKeyLength + len("@") +) + // Copy makes a deep copy of Config. // The result aliases no memory with the original. func (cfg Config) Copy() Config { diff --git a/wgengine/wgcfg/nmcfg/nmcfg.go b/wgengine/wgcfg/nmcfg/nmcfg.go index 57053ea78..1ea42c431 100644 --- a/wgengine/wgcfg/nmcfg/nmcfg.go +++ b/wgengine/wgcfg/nmcfg/nmcfg.go @@ -51,7 +51,7 @@ func cidrIsSubnet(node *tailcfg.Node, cidr netaddr.IPPrefix) bool { return true } -// WGCfg returns the NetworkMaps's Wireguard configuration. +// WGCfg returns the NetworkMaps's WireGuard configuration. func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags, exitNode tailcfg.StableNodeID) (*wgcfg.Config, error) { cfg := &wgcfg.Config{ Name: "tailscale", @@ -77,7 +77,7 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags, if err := appendEndpoint(cpeer, fmt.Sprintf("%x%s", peer.DiscoKey[:], wgcfg.EndpointDiscoSuffix)); err != nil { return nil, err } - cpeer.Endpoints = fmt.Sprintf("%x.disco.tailscale:12345", peer.DiscoKey[:]) + cpeer.Endpoints2 = fmt.Sprintf("%x@%x.disco.tailscale:12345", peer.Key, peer.DiscoKey[:]) } else { if err := appendEndpoint(cpeer, peer.DERP); err != nil { return nil, err @@ -122,9 +122,11 @@ func appendEndpoint(peer *wgcfg.Peer, epStr string) error { if err != nil { return fmt.Errorf("invalid port in endpoint %q for peer %v", epStr, peer.PublicKey.ShortString()) } - if peer.Endpoints != "" { - peer.Endpoints += "," + if peer.Endpoints2 == "" { + peer.Endpoints2 = peer.PublicKey.HexString() + "@" + } else { + peer.Endpoints2 += "," } - peer.Endpoints += epStr + peer.Endpoints2 += epStr return nil } diff --git a/wgengine/wgcfg/parser.go b/wgengine/wgcfg/parser.go index bf0b45835..6120ceb58 100644 --- a/wgengine/wgcfg/parser.go +++ b/wgengine/wgcfg/parser.go @@ -175,7 +175,7 @@ func (cfg *Config) handlePeerLine(peer *Peer, key, value string) error { if err != nil { return err } - peer.Endpoints = value + peer.Endpoints2 = peer.PublicKey.HexString() + "@" + value case "persistent_keepalive_interval": n, err := strconv.ParseUint(value, 10, 16) if err != nil { diff --git a/wgengine/wgcfg/writer.go b/wgengine/wgcfg/writer.go index 079c1eb5e..89c592f67 100644 --- a/wgengine/wgcfg/writer.go +++ b/wgengine/wgcfg/writer.go @@ -55,8 +55,8 @@ func (cfg *Config) ToUAPI(w io.Writer, prev *Config) error { setPeer(p) set("protocol_version", "1") - if !endpointsEqual(oldPeer.Endpoints, p.Endpoints) { - set("endpoint", p.Endpoints) + if !endpointsEqual(oldPeer.Endpoints2, p.Endpoints2) { + set("endpoint", p.Endpoints2) } // TODO: replace_allowed_ips is expensive. @@ -97,12 +97,18 @@ func endpointsEqual(x, y string) bool { if x == y { return true } + // Public keys must match. + if x[:HexKeyPrefixLen] != y[:HexKeyPrefixLen] { + return false + } + // See whether the addresses are the same, but out of order. + x = x[HexKeyPrefixLen:] + y = y[HexKeyPrefixLen:] xs := strings.Split(x, ",") ys := strings.Split(y, ",") if len(xs) != len(ys) { return false } - // Otherwise, see if they're the same, but out of order. sort.Strings(xs) sort.Strings(ys) x = strings.Join(xs, ",")