From c573bef0aa088d519f6fd4cde10f2b42fa221c39 Mon Sep 17 00:00:00 2001 From: Charlotte Brandhorst-Satzkorn Date: Wed, 5 Apr 2023 17:28:28 -0700 Subject: [PATCH] tailcfg,wgengine: add initial support for WireGuard only peers A peer can have IsWireGuardOnly, which means it will not support DERP or Disco, and it must have Endpoints filled in order to be usable. In the present implementation only the first Endpoint will be used as the bestAddr. Updates tailscale/corp#10351 Co-authored-by: Charlotte Brandhorst-Satzkorn Co-authored-by: James Tucker Signed-off-by: James Tucker --- tailcfg/tailcfg.go | 12 +- tailcfg/tailcfg_clone.go | 1 + tailcfg/tailcfg_test.go | 1 + tailcfg/tailcfg_view.go | 2 + util/deephash/deephash_test.go | 4 +- wgengine/magicsock/magicsock.go | 92 +++++++++----- wgengine/magicsock/magicsock_test.go | 174 +++++++++++++++++++++++++++ wgengine/wgcfg/nmcfg/nmcfg.go | 2 +- 8 files changed, 253 insertions(+), 35 deletions(-) diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 6d206d682..adcf18c04 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -96,7 +96,8 @@ type CapabilityVersion int // - 57: 2023-01-25: Client understands CapabilityBindToInterfaceByRoute // - 58: 2023-03-10: Client retries lite map updates before restarting map poll. // - 59: 2023-03-16: Client understands Peers[].SelfNodeV4MasqAddrForThisPeer -const CurrentCapabilityVersion CapabilityVersion = 59 +// - 60: 2023-04-06: Client understands IsWireGuardOnly +const CurrentCapabilityVersion CapabilityVersion = 60 type StableID string @@ -289,6 +290,12 @@ type Node struct { // peer or any of its subnets. Traffic originating from subnet routes will // not be masqueraded (e.g. in case of --snat-subnet-routes). SelfNodeV4MasqAddrForThisPeer netip.Addr `json:",omitempty"` + + // IsWireGuardOnly indicates that this is a non-Tailscale WireGuard peer, it + // is not expected to speak Disco or DERP, and it must have Endpoints in + // order to be reachable. TODO(#7826): 2023-04-06: only the first parseable + // Endpoint is used, see #7826 for updates. + IsWireGuardOnly bool `json:",omitempty"` } // DisplayName returns the user-facing name for a node which should @@ -1715,7 +1722,8 @@ func (n *Node) Equal(n2 *Node) bool { n.ComputedNameWithHost == n2.ComputedNameWithHost && eqStrings(n.Tags, n2.Tags) && n.Expired == n2.Expired && - n.SelfNodeV4MasqAddrForThisPeer == n2.SelfNodeV4MasqAddrForThisPeer + n.SelfNodeV4MasqAddrForThisPeer == n2.SelfNodeV4MasqAddrForThisPeer && + n.IsWireGuardOnly == n2.IsWireGuardOnly } func eqBoolPtr(a, b *bool) bool { diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index 746cf95b7..ef8dac56e 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -99,6 +99,7 @@ var _NodeCloneNeedsRegeneration = Node(struct { DataPlaneAuditLogID string Expired bool SelfNodeV4MasqAddrForThisPeer netip.Addr + IsWireGuardOnly bool }{}) // Clone makes a deep copy of Hostinfo. diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index d5f5402aa..4beca887f 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -350,6 +350,7 @@ func TestNodeEqual(t *testing.T) { "UnsignedPeerAPIOnly", "ComputedName", "computedHostIfDifferent", "ComputedNameWithHost", "DataPlaneAuditLogID", "Expired", "SelfNodeV4MasqAddrForThisPeer", + "IsWireGuardOnly", } if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) { t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n", diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go index 2d038108a..5df1ea6a6 100644 --- a/tailcfg/tailcfg_view.go +++ b/tailcfg/tailcfg_view.go @@ -179,6 +179,7 @@ func (v NodeView) Expired() bool { return v.ж.Expired } func (v NodeView) SelfNodeV4MasqAddrForThisPeer() netip.Addr { return v.ж.SelfNodeV4MasqAddrForThisPeer } +func (v NodeView) IsWireGuardOnly() bool { return v.ж.IsWireGuardOnly } func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) } // A compilation failure here means this code must be regenerated, with the command at the top of this file. @@ -214,6 +215,7 @@ var _NodeViewNeedsRegeneration = Node(struct { DataPlaneAuditLogID string Expired bool SelfNodeV4MasqAddrForThisPeer netip.Addr + IsWireGuardOnly bool }{}) // View returns a readonly view of Hostinfo. diff --git a/util/deephash/deephash_test.go b/util/deephash/deephash_test.go index 6ad65e314..e712767f2 100644 --- a/util/deephash/deephash_test.go +++ b/util/deephash/deephash_test.go @@ -575,8 +575,8 @@ func TestGetTypeHasher(t *testing.T) { { name: "tailcfg.Node", val: &tailcfg.Node{}, - out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", - out32: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + out32: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", }, } for _, tt := range tests { diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 8f976a7da..45a8d17d6 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -208,6 +208,12 @@ func (m *peerMap) upsertEndpoint(ep *endpoint, oldDiscoKey key.DiscoPublic) { delete(m.nodesOfDisco[oldDiscoKey], ep.publicKey) } if epDisco == nil { + // If the peer does not support Disco, but it does have an endpoint address, + // attempt to use that (e.g. WireGuardOnly peers). + if ep.bestAddr.AddrPort.IsValid() { + m.setNodeKeyForIPPort(ep.bestAddr.AddrPort, ep.publicKey) + } + return } set := m.nodesOfDisco[epDisco.key] @@ -2732,8 +2738,14 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) { // handle full set updates. for _, n := range nm.Peers { if ep, ok := c.peerMap.endpointForNodeKey(n.Key); ok { - if n.DiscoKey.IsZero() { - // Discokey transitioned from non-zero to zero? Ignore. Server's confused. + if n.DiscoKey.IsZero() && !n.IsWireGuardOnly { + // Discokey transitioned from non-zero to zero? This should not + // happen in the wild, however it could mean: + // 1. A node was downgraded from post 0.100 to pre 0.100. + // 2. A Tailscale node key was extracted and used on a + // non-Tailscale node (should not enter here due to the + // IsWireGuardOnly check) + // 3. The server is misbehaving. c.peerMap.deleteEndpoint(ep) continue } @@ -2745,9 +2757,8 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) { c.peerMap.upsertEndpoint(ep, oldDiscoKey) // maybe update discokey mappings in peerMap continue } - if n.DiscoKey.IsZero() { - // Ancient pre-0.100 node. Ignore, so we can assume elsewhere in magicsock - // that all nodes have a DiscoKey. + if n.DiscoKey.IsZero() && !n.IsWireGuardOnly { + // Ancient pre-0.100 node, which does not have a disco key, and will only be reachable via DERP. continue } @@ -2763,35 +2774,40 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) { if len(n.Addresses) > 0 { ep.nodeAddr = n.Addresses[0].Addr() } - ep.disco.Store(&endpointDisco{ - key: n.DiscoKey, - short: n.DiscoKey.ShortString(), - }) ep.initFakeUDPAddr() - if debugDisco() { // rather than making a new knob - 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:" - if strings.HasPrefix(n.DERP, derpPrefix) { - ipp, _ := netip.ParseAddrPort(n.DERP) - regionID := int(ipp.Port()) - code := c.derpRegionCodeLocked(regionID) - if code != "" { - code = "(" + code + ")" + if n.DiscoKey.IsZero() { + ep.disco.Store(nil) + } else { + ep.disco.Store(&endpointDisco{ + key: n.DiscoKey, + short: n.DiscoKey.ShortString(), + }) + + if debugDisco() { // rather than making a new knob + 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:" + if strings.HasPrefix(n.DERP, derpPrefix) { + ipp, _ := netip.ParseAddrPort(n.DERP) + regionID := int(ipp.Port()) + code := c.derpRegionCodeLocked(regionID) + if code != "" { + code = "(" + code + ")" + } + fmt.Fprintf(w, "derp=%v%s ", regionID, code) } - fmt.Fprintf(w, "derp=%v%s ", regionID, code) - } - for _, a := range n.AllowedIPs { - if a.IsSingleIP() { - fmt.Fprintf(w, "aip=%v ", a.Addr()) - } else { - fmt.Fprintf(w, "aip=%v ", a) + for _, a := range n.AllowedIPs { + if a.IsSingleIP() { + fmt.Fprintf(w, "aip=%v ", a.Addr()) + } else { + fmt.Fprintf(w, "aip=%v ", a) + } } - } - for _, ep := range n.Endpoints { - fmt.Fprintf(w, "ep=%v ", ep) - } - })) + for _, ep := range n.Endpoints { + fmt.Fprintf(w, "ep=%v ", ep) + } + })) + } } ep.updateFromNode(n, heartbeatDisabled) c.peerMap.upsertEndpoint(ep, key.DiscoPublic{}) @@ -4627,6 +4643,22 @@ func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) { de.heartbeatDisabled = heartbeatDisabled de.expired = n.Expired + // TODO(#7826): add support for more than one endpoint for pure WireGuard + // peers, and/or support for probing "bestness" for endpoints. + if n.IsWireGuardOnly { + for _, ep := range n.Endpoints { + ipp, err := netip.ParseAddrPort(ep) + if err != nil { + de.c.logf("magicsock: invalid endpoint: %s %s", ep, err) + continue + } + de.bestAddr = addrLatency{ + AddrPort: ipp, + } + break + } + } + epDisco := de.disco.Load() var discoKey key.DiscoPublic if epDisco != nil { diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 3a553ddb7..20181528b 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -39,6 +39,7 @@ import ( "tailscale.com/ipn/ipnstate" "tailscale.com/net/connstats" "tailscale.com/net/netaddr" + "tailscale.com/net/packet" "tailscale.com/net/stun/stuntest" "tailscale.com/net/tstun" "tailscale.com/tailcfg" @@ -200,6 +201,7 @@ func newMagicStackWithKey(t testing.TB, logf logger.Logf, l nettype.PacketListen } func (s *magicStack) Reconfig(cfg *wgcfg.Config) error { + s.tsTun.SetWGConfig(cfg) s.wgLogger.SetPeers(cfg.Peers) return wgcfg.ReconfigDevice(s.dev, cfg, s.conn.logf) } @@ -2102,3 +2104,175 @@ func Test_batchingUDPConn_coalesceMessages(t *testing.T) { }) } } + +// newWireguard starts up a new wireguard-go device attached to a test tun, and +// returns the device, tun and netpoint address. To add peers call device.IpcSet +// with UAPI instructions. +func newWireguard(t *testing.T, uapi string, aips []netip.Prefix) (*device.Device, *tuntest.ChannelTUN, netip.AddrPort) { + wgtun := tuntest.NewChannelTUN() + wglogf := func(f string, args ...any) { + t.Logf("wg-go: "+f, args...) + } + wglog := device.Logger{ + Verbosef: func(string, ...any) {}, + Errorf: wglogf, + } + wgdev := wgcfg.NewDevice(wgtun.TUN(), wgconn.NewDefaultBind(), &wglog) + + if err := wgdev.IpcSet(uapi); err != nil { + t.Fatal(err) + } + + if err := wgdev.Up(); err != nil { + t.Fatal(err) + } + + var wgEp netip.AddrPort + + s, err := wgdev.IpcGet() + if err != nil { + t.Fatal(err) + } + for _, line := range strings.Split(s, "\n") { + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + k, v, _ := strings.Cut(line, "=") + if k == "listen_port" { + wgEp = netip.MustParseAddrPort("127.0.0.1:" + v) + break + } + } + + if !wgEp.IsValid() { + t.Fatalf("failed to get endpoint out of wg-go") + } + t.Logf("wg-go endpoint: %s", wgEp) + + return wgdev, wgtun, wgEp +} + +func TestIsWireGuardOnlyPeer(t *testing.T) { + derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1)) + defer cleanup() + + tskey := key.NewNode() + tsaip := netip.MustParsePrefix("100.111.222.111/32") + + wgkey := key.NewNode() + wgaip := netip.MustParsePrefix("100.222.111.222/32") + + uapi := fmt.Sprintf("private_key=%s\npublic_key=%s\nallowed_ip=%s\n\n", + wgkey.UntypedHexString(), tskey.Public().UntypedHexString(), tsaip.String()) + wgdev, wgtun, wgEp := newWireguard(t, uapi, []netip.Prefix{wgaip}) + defer wgdev.Close() + + m := newMagicStackWithKey(t, t.Logf, localhostListener{}, derpMap, tskey) + defer m.Close() + + nm := &netmap.NetworkMap{ + Name: "ts", + PrivateKey: m.privateKey, + NodeKey: m.privateKey.Public(), + Addresses: []netip.Prefix{tsaip}, + Peers: []*tailcfg.Node{ + { + Key: wgkey.Public(), + Endpoints: []string{wgEp.String()}, + IsWireGuardOnly: true, + Addresses: []netip.Prefix{wgaip}, + AllowedIPs: []netip.Prefix{wgaip}, + }, + }, + } + m.conn.SetNetworkMap(nm) + + cfg, err := nmcfg.WGCfg(nm, t.Logf, netmap.AllowSingleHosts|netmap.AllowSubnetRoutes, "") + if err != nil { + t.Fatal(err) + } + m.Reconfig(cfg) + + pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr()) + m.tun.Outbound <- pbuf + + select { + case p := <-wgtun.Inbound: + if !bytes.Equal(p, pbuf) { + t.Errorf("got unexpected packet: %x", p) + } + case <-time.After(time.Second): + t.Fatal("no packet after 1s") + } +} + +func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) { + derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1)) + defer cleanup() + + tskey := key.NewNode() + tsaip := netip.MustParsePrefix("100.111.222.111/32") + + wgkey := key.NewNode() + wgaip := netip.MustParsePrefix("10.64.0.1/32") + + // the ip that the wireguard peer has in allowed ips and expects as a masq source + masqip := netip.MustParsePrefix("10.64.0.2/32") + + uapi := fmt.Sprintf("private_key=%s\npublic_key=%s\nallowed_ip=%s\n\n", + wgkey.UntypedHexString(), tskey.Public().UntypedHexString(), masqip.String()) + wgdev, wgtun, wgEp := newWireguard(t, uapi, []netip.Prefix{wgaip}) + defer wgdev.Close() + + m := newMagicStackWithKey(t, t.Logf, localhostListener{}, derpMap, tskey) + defer m.Close() + + nm := &netmap.NetworkMap{ + Name: "ts", + PrivateKey: m.privateKey, + NodeKey: m.privateKey.Public(), + Addresses: []netip.Prefix{tsaip}, + Peers: []*tailcfg.Node{ + { + Key: wgkey.Public(), + Endpoints: []string{wgEp.String()}, + IsWireGuardOnly: true, + Addresses: []netip.Prefix{wgaip}, + AllowedIPs: []netip.Prefix{wgaip}, + SelfNodeV4MasqAddrForThisPeer: masqip.Addr(), + }, + }, + } + m.conn.SetNetworkMap(nm) + + cfg, err := nmcfg.WGCfg(nm, t.Logf, netmap.AllowSingleHosts|netmap.AllowSubnetRoutes, "") + if err != nil { + t.Fatal(err) + } + m.Reconfig(cfg) + + pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr()) + m.tun.Outbound <- pbuf + + select { + case p := <-wgtun.Inbound: + + // TODO(raggi): move to a bytes.Equal based test later, once + // tuntest.Ping produces correct checksums! + + var pkt packet.Parsed + pkt.Decode(p) + if pkt.ICMP4Header().Type != packet.ICMP4EchoRequest { + t.Fatalf("unexpected packet: %x", p) + } + if pkt.Src.Addr() != masqip.Addr() { + t.Fatalf("bad source IP, got %s, want %s", pkt.Src.Addr(), masqip.Addr()) + } + if pkt.Dst.Addr() != wgaip.Addr() { + t.Fatalf("bad source IP, got %s, want %s", pkt.Src.Addr(), masqip.Addr()) + } + case <-time.After(time.Second): + t.Fatal("no packet after 1s") + } +} diff --git a/wgengine/wgcfg/nmcfg/nmcfg.go b/wgengine/wgcfg/nmcfg/nmcfg.go index d07f7232b..f01b42cb1 100644 --- a/wgengine/wgcfg/nmcfg/nmcfg.go +++ b/wgengine/wgcfg/nmcfg/nmcfg.go @@ -85,7 +85,7 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags, skippedSubnets := new(bytes.Buffer) for _, peer := range nm.Peers { - if peer.DiscoKey.IsZero() && peer.DERP == "" { + if peer.DiscoKey.IsZero() && peer.DERP == "" && !peer.IsWireGuardOnly { // Peer predates both DERP and active discovery, we cannot // communicate with it. logf("[v1] wgcfg: skipped peer %s, doesn't offer DERP or disco", peer.Key.ShortString())