From 722859b47628cf7cc7ceb7c989919ececd10188f Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 28 Jun 2021 15:16:37 -0700 Subject: [PATCH] wgengine/netstack: make SOCKS5 resolve names to IPv6 if self node when no IPv4 For instance, ephemeral nodes with only IPv6 addresses can now SOCKS5-dial out to names like "foo" and resolve foo's IPv6 address rather than foo's IPv4 address and get a "no route" (*tcpip.ErrNoRoute) error from netstack's dialer. Per https://github.com/tailscale/tailscale/issues/2268#issuecomment-870027626 which is only part of the isuse. Updates #2268 Signed-off-by: Brad Fitzpatrick --- wgengine/netstack/netstack.go | 18 ++++- wgengine/netstack/netstack_test.go | 112 +++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 wgengine/netstack/netstack_test.go diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 47103b9ce..4bd00f041 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -177,21 +177,33 @@ func (ns *Impl) Start() error { func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap { ret := make(DNSMap) suffix := nm.MagicDNSSuffix() - + have4 := false if nm.Name != "" && len(nm.Addresses) > 0 { ip := nm.Addresses[0].IP() ret[strings.TrimRight(nm.Name, ".")] = ip if dnsname.HasSuffix(nm.Name, suffix) { ret[dnsname.TrimSuffix(nm.Name, suffix)] = ip } + for _, a := range nm.Addresses { + if a.IP().Is4() { + have4 = true + } + } } for _, p := range nm.Peers { - if p.Name != "" && len(p.Addresses) > 0 { - ip := p.Addresses[0].IP() + if p.Name == "" { + continue + } + for _, a := range p.Addresses { + ip := a.IP() + if ip.Is4() && !have4 { + continue + } ret[strings.TrimRight(p.Name, ".")] = ip if dnsname.HasSuffix(p.Name, suffix) { ret[dnsname.TrimSuffix(p.Name, suffix)] = ip } + break } } return ret diff --git a/wgengine/netstack/netstack_test.go b/wgengine/netstack/netstack_test.go new file mode 100644 index 000000000..9b4792190 --- /dev/null +++ b/wgengine/netstack/netstack_test.go @@ -0,0 +1,112 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package netstack + +import ( + "reflect" + "testing" + + "inet.af/netaddr" + "tailscale.com/tailcfg" + "tailscale.com/types/netmap" +) + +func TestDNSMapFromNetworkMap(t *testing.T) { + pfx := netaddr.MustParseIPPrefix + ip := netaddr.MustParseIP + tests := []struct { + name string + nm *netmap.NetworkMap + want DNSMap + }{ + { + name: "self", + nm: &netmap.NetworkMap{ + Name: "foo.tailnet", + Addresses: []netaddr.IPPrefix{ + pfx("100.102.103.104/32"), + pfx("100::123/128"), + }, + }, + want: DNSMap{ + "foo": ip("100.102.103.104"), + "foo.tailnet": ip("100.102.103.104"), + }, + }, + { + name: "self_and_peers", + nm: &netmap.NetworkMap{ + Name: "foo.tailnet", + Addresses: []netaddr.IPPrefix{ + pfx("100.102.103.104/32"), + pfx("100::123/128"), + }, + Peers: []*tailcfg.Node{ + { + Name: "a.tailnet", + Addresses: []netaddr.IPPrefix{ + pfx("100.0.0.201/32"), + pfx("100::201/128"), + }, + }, + { + Name: "b.tailnet", + Addresses: []netaddr.IPPrefix{ + pfx("100::202/128"), + }, + }, + }, + }, + want: DNSMap{ + "foo": ip("100.102.103.104"), + "foo.tailnet": ip("100.102.103.104"), + "a": ip("100.0.0.201"), + "a.tailnet": ip("100.0.0.201"), + "b": ip("100::202"), + "b.tailnet": ip("100::202"), + }, + }, + { + name: "self_has_v6_only", + nm: &netmap.NetworkMap{ + Name: "foo.tailnet", + Addresses: []netaddr.IPPrefix{ + pfx("100::123/128"), + }, + Peers: []*tailcfg.Node{ + { + Name: "a.tailnet", + Addresses: []netaddr.IPPrefix{ + pfx("100.0.0.201/32"), + pfx("100::201/128"), + }, + }, + { + Name: "b.tailnet", + Addresses: []netaddr.IPPrefix{ + pfx("100::202/128"), + }, + }, + }, + }, + want: DNSMap{ + "foo": ip("100::123"), + "foo.tailnet": ip("100::123"), + "a": ip("100::201"), + "a.tailnet": ip("100::201"), + "b": ip("100::202"), + "b.tailnet": ip("100::202"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := DNSMapFromNetworkMap(tt.nm) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("mismatch:\n got %v\nwant %v\n", got, tt.want) + } + }) + } +}