diff --git a/ipn/ipnlocal/dnsconfig_test.go b/ipn/ipnlocal/dnsconfig_test.go index 9778c37f4..a4ec6060c 100644 --- a/ipn/ipnlocal/dnsconfig_test.go +++ b/ipn/ipnlocal/dnsconfig_test.go @@ -86,6 +86,40 @@ func TestDNSConfigForNetmap(t *testing.T) { }, }, }, + { + // An ephemeral node with only an IPv6 address + // should get IPv6 records for all its peers, + // even if they have IPv4. + name: "v6_only_self", + nm: &netmap.NetworkMap{ + Name: "myname.net", + Addresses: ipps("fe75::1"), + Peers: []*tailcfg.Node{ + { + Name: "peera.net", + Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::1001"), + }, + { + Name: "b.net", + Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::2"), + }, + { + Name: "v6-only.net", + Addresses: ipps("fe75::3"), // no IPv4, so we don't ignore IPv6 + }, + }, + }, + prefs: &ipn.Prefs{}, + want: &dns.Config{ + Routes: map[dnsname.FQDN][]dnstype.Resolver{}, + Hosts: map[dnsname.FQDN][]netaddr.IP{ + "b.net.": ips("fe75::2"), + "myname.net.": ips("fe75::1"), + "peera.net.": ips("fe75::1001"), + "v6-only.net.": ips("fe75::3"), + }, + }, + }, { name: "extra_records", nm: &netmap.NetworkMap{ diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index e9f7969cb..439a54928 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1817,6 +1817,10 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log Hosts: map[dnsname.FQDN][]netaddr.IP{}, } + // selfV6Only is whether we only have IPv6 addresses ourselves. + selfV6Only := tsaddr.PrefixesContainsFunc(nm.Addresses, tsaddr.PrefixIs6) && + !tsaddr.PrefixesContainsFunc(nm.Addresses, tsaddr.PrefixIs4) + // Populate MagicDNS records. We do this unconditionally so that // quad-100 can always respond to MagicDNS queries, even if the OS // isn't configured to make MagicDNS resolution truly @@ -1830,12 +1834,19 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log if err != nil { return // TODO: propagate error? } - have4 := tsaddr.PrefixesContainsFunc(addrs, func(p netaddr.IPPrefix) bool { return p.IP().Is4() }) + have4 := tsaddr.PrefixesContainsFunc(addrs, tsaddr.PrefixIs4) var ips []netaddr.IP for _, addr := range addrs { - // Remove IPv6 addresses for now, as we don't - // guarantee that the peer node actually can speak - // IPv6 correctly. + if selfV6Only { + if addr.IP().Is6() { + ips = append(ips, addr.IP()) + } + continue + } + // If this node has an IPv4 address, then + // remove peers' IPv6 addresses for now, as we + // don't guarantee that the peer node actually + // can speak IPv6 correctly. // // https://github.com/tailscale/tailscale/issues/1152 // tracks adding the right capability reporting to diff --git a/net/tsaddr/tsaddr.go b/net/tsaddr/tsaddr.go index 46496dd54..a18757671 100644 --- a/net/tsaddr/tsaddr.go +++ b/net/tsaddr/tsaddr.go @@ -185,3 +185,9 @@ func IPsContainsFunc(ips []netaddr.IP, f func(netaddr.IP) bool) bool { } return false } + +// PrefixIs4 reports whether p is an IPv4 prefix. +func PrefixIs4(p netaddr.IPPrefix) bool { return p.IP().Is4() } + +// PrefixIs6 reports whether p is an IPv6 prefix. +func PrefixIs6(p netaddr.IPPrefix) bool { return p.IP().Is6() }