mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-06 04:36:15 +02:00
ipn/ipnlocal, tailcfg: return AAAA addresses for peers with IPv6 support
This adds the peer capability PeerCapabilityOSIPv6, which indicates that a given peer supports IPv6 at the OS level (as sent by the control server) and can thus be communicated with over IPv6. For peers with this capability, we can thus return AAAA addresses from MagicDNS. Updates #1152 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: I0d1374c6e47592807f886749fb509a01e1ceb855
This commit is contained in:
parent
b4816e19b6
commit
1ecc2f2d23
@ -14,10 +14,13 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/util/cloudenv"
|
||||
"tailscale.com/util/cmpx"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/wgengine"
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
func ipps(ippStrs ...string) (ipps []netip.Prefix) {
|
||||
@ -327,12 +330,70 @@ func TestDNSConfigForNetmap(t *testing.T) {
|
||||
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ipv6_os_support",
|
||||
nm: &netmap.NetworkMap{
|
||||
Name: "myname.net",
|
||||
SelfNode: (&tailcfg.Node{
|
||||
Addresses: ipps("100.101.101.101"),
|
||||
}).View(),
|
||||
PacketFilter: []filter.Match{{
|
||||
// TODO(andrew): this looks backwards?
|
||||
Srcs: []netip.Prefix{netip.MustParsePrefix("100.102.0.1/32")},
|
||||
Caps: []filter.CapMatch{
|
||||
{
|
||||
Dst: netip.MustParsePrefix("100.101.101.101/32"),
|
||||
Cap: tailcfg.PeerCapabilityOSIPv6,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
peers: nodeViews([]*tailcfg.Node{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "peera.net",
|
||||
Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::1001", "fe75::1002"),
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Name: "b.net",
|
||||
Addresses: ipps("100.102.0.3", "100.102.0.4", "fe75::2"),
|
||||
},
|
||||
}),
|
||||
prefs: &ipn.Prefs{},
|
||||
want: &dns.Config{
|
||||
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
|
||||
Hosts: map[dnsname.FQDN][]netip.Addr{
|
||||
"b.net.": ips("100.102.0.3", "100.102.0.4"),
|
||||
"myname.net.": ips("100.101.101.101"),
|
||||
"peera.net.": ips("100.102.0.1", "100.102.0.2", "fe75::1001", "fe75::1002"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
verOS := cmpx.Or(tt.os, "linux")
|
||||
|
||||
eng, _ := wgengine.NewFakeUserspaceEngine(logger.Discard, 0)
|
||||
|
||||
var log tstest.MemLogger
|
||||
got := dnsConfigForNetmap(tt.nm, peersMap(tt.peers), tt.prefs.View(), log.Logf, verOS)
|
||||
b := &LocalBackend{
|
||||
e: eng,
|
||||
netMap: tt.nm,
|
||||
logf: log.Logf,
|
||||
peers: peersMap(tt.peers),
|
||||
}
|
||||
b.mu.Lock()
|
||||
b.updateFilterLocked(b.netMap, tt.prefs.View())
|
||||
|
||||
// the updateFilterLocked function logs something; clear it
|
||||
log.Lock()
|
||||
log.Reset()
|
||||
log.Unlock()
|
||||
|
||||
got := b.dnsConfigForNetmapLocked(tt.prefs.View(), verOS)
|
||||
b.mu.Unlock()
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
gotj, _ := json.MarshalIndent(got, "", "\t")
|
||||
wantj, _ := json.MarshalIndent(tt.want, "", "\t")
|
||||
|
||||
@ -3143,7 +3143,7 @@ func (b *LocalBackend) authReconfig() {
|
||||
hasPAC := b.prevIfState.HasPAC()
|
||||
disableSubnetsIfPAC := hasCapability(nm, tailcfg.NodeAttrDisableSubnetsIfPAC)
|
||||
dohURL, dohURLOK := exitNodeCanProxyDNS(nm, b.peers, prefs.ExitNodeID())
|
||||
dcfg := dnsConfigForNetmap(nm, b.peers, prefs, b.logf, version.OS())
|
||||
dcfg := b.dnsConfigForNetmapLocked(prefs, version.OS())
|
||||
b.mu.Unlock()
|
||||
|
||||
if blocked {
|
||||
@ -3233,12 +3233,15 @@ func shouldUseOneCGNATRoute(logf logger.Logf, controlKnobs *controlknobs.Knobs,
|
||||
return false
|
||||
}
|
||||
|
||||
// dnsConfigForNetmap returns a *dns.Config for the given netmap,
|
||||
// dnsConfigForNetmapLocked returns a *dns.Config for the given netmap,
|
||||
// prefs, client OS version, and cloud hosting environment.
|
||||
//
|
||||
// The versionOS is a Tailscale-style version ("iOS", "macOS") and not
|
||||
// a runtime.GOOS.
|
||||
func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.NodeView, prefs ipn.PrefsView, logf logger.Logf, versionOS string) *dns.Config {
|
||||
//
|
||||
// b.mu must be held
|
||||
func (b *LocalBackend) dnsConfigForNetmapLocked(prefs ipn.PrefsView, versionOS string) *dns.Config {
|
||||
nm := b.netMap
|
||||
if nm == nil {
|
||||
return nil
|
||||
}
|
||||
@ -3265,10 +3268,21 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
if err != nil {
|
||||
return // TODO: propagate error?
|
||||
}
|
||||
var have4 bool
|
||||
var have4, osHasV6 bool
|
||||
for i := range addrs.LenIter() {
|
||||
if addrs.At(i).Addr().Is4() {
|
||||
addr := addrs.At(i).Addr()
|
||||
if addr.Is4() {
|
||||
have4 = true
|
||||
}
|
||||
|
||||
// TODO(andrew): PeerCaps depend on the IPProto, so we
|
||||
// need to do this for every address; can we make this
|
||||
// less expensive?
|
||||
if b.peerHasCapLocked(addr, tailcfg.PeerCapabilityOSIPv6) {
|
||||
osHasV6 = true
|
||||
}
|
||||
|
||||
if have4 && osHasV6 {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -3281,23 +3295,24 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
}
|
||||
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.
|
||||
|
||||
// If this node has an IPv4 address and doesn't have
|
||||
// host-level IPv6 support, then we don't return IPv6
|
||||
// addresses to stop this node from trying to
|
||||
// communicate with the peer over IPv6.
|
||||
//
|
||||
// https://github.com/tailscale/tailscale/issues/1152
|
||||
// tracks adding the right capability reporting to
|
||||
// enable AAAA in MagicDNS.
|
||||
if addr.Addr().Is6() && have4 {
|
||||
if addr.Addr().Is6() && have4 && !osHasV6 {
|
||||
continue
|
||||
}
|
||||
ips = append(ips, addr.Addr())
|
||||
}
|
||||
dcfg.Hosts[fqdn] = ips
|
||||
}
|
||||
set(nm.Name, nm.GetAddresses())
|
||||
for _, peer := range peers {
|
||||
set(nm.Name, nm.GetAddresses()) // TODO(andrew): set osHasV6 properly here?
|
||||
for _, peer := range b.peers {
|
||||
set(peer.Name(), peer.Addresses())
|
||||
}
|
||||
for _, rec := range nm.DNS.ExtraRecords {
|
||||
@ -3327,7 +3342,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
for _, dom := range nm.DNS.Domains {
|
||||
fqdn, err := dnsname.ToFQDN(dom)
|
||||
if err != nil {
|
||||
logf("[unexpected] non-FQDN search domain %q", dom)
|
||||
b.logf("[unexpected] non-FQDN search domain %q", dom)
|
||||
}
|
||||
dcfg.SearchDomains = append(dcfg.SearchDomains, fqdn)
|
||||
}
|
||||
@ -3343,7 +3358,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
|
||||
// If we're using an exit node and that exit node is new enough (1.19.x+)
|
||||
// to run a DoH DNS proxy, then send all our DNS traffic through it.
|
||||
if dohURL, ok := exitNodeCanProxyDNS(nm, peers, prefs.ExitNodeID()); ok {
|
||||
if dohURL, ok := exitNodeCanProxyDNS(nm, b.peers, prefs.ExitNodeID()); ok {
|
||||
addDefault([]*dnstype.Resolver{{Addr: dohURL}})
|
||||
return dcfg
|
||||
}
|
||||
@ -3354,7 +3369,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
if len(nm.DNS.Resolvers) > 0 {
|
||||
addDefault(nm.DNS.Resolvers)
|
||||
} else {
|
||||
if resolvers, ok := wireguardExitNodeDNSResolvers(nm, peers, prefs.ExitNodeID()); ok {
|
||||
if resolvers, ok := wireguardExitNodeDNSResolvers(nm, b.peers, prefs.ExitNodeID()); ok {
|
||||
addDefault(resolvers)
|
||||
}
|
||||
}
|
||||
@ -3362,7 +3377,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
for suffix, resolvers := range nm.DNS.Routes {
|
||||
fqdn, err := dnsname.ToFQDN(suffix)
|
||||
if err != nil {
|
||||
logf("[unexpected] non-FQDN route suffix %q", suffix)
|
||||
b.logf("[unexpected] non-FQDN route suffix %q", suffix)
|
||||
}
|
||||
|
||||
// Create map entry even if len(resolvers) == 0; Issue 2706.
|
||||
|
||||
@ -1112,7 +1112,14 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) {
|
||||
}
|
||||
|
||||
prefs := &ipn.Prefs{ExitNodeID: tc.exitNode, CorpDNS: true}
|
||||
got := dnsConfigForNetmap(nm, peersMap(tc.peers), prefs.View(), t.Logf, "")
|
||||
b := &LocalBackend{
|
||||
netMap: nm,
|
||||
logf: t.Logf,
|
||||
peers: peersMap(tc.peers),
|
||||
}
|
||||
b.mu.Lock()
|
||||
got := b.dnsConfigForNetmapLocked(prefs.View(), "")
|
||||
b.mu.Unlock()
|
||||
if !resolversEqual(t, got.DefaultResolvers, tc.wantDefaultResolvers) {
|
||||
t.Errorf("DefaultResolvers: got %#v, want %#v", got.DefaultResolvers, tc.wantDefaultResolvers)
|
||||
}
|
||||
|
||||
@ -1322,6 +1322,10 @@ const (
|
||||
PeerCapabilityWakeOnLAN PeerCapability = "https://tailscale.com/cap/wake-on-lan"
|
||||
// PeerCapabilityIngress grants the ability for a peer to send ingress traffic.
|
||||
PeerCapabilityIngress PeerCapability = "https://tailscale.com/cap/ingress"
|
||||
// PeerCapabilityOSIPv6 grants the ability for the current node to send
|
||||
// traffic to the peer over IPv6; this indicates that the node has IPv6
|
||||
// support at the OS level.
|
||||
PeerCapabilityOSIPv6 PeerCapability = "https://tailscale.com/cap/os-ipv6"
|
||||
)
|
||||
|
||||
// NodeCapMap is a map of capabilities to their optional values. It is valid for
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user