diff --git a/ipn/ipnlocal/dnsconfig_test.go b/ipn/ipnlocal/dnsconfig_test.go index 53060cc13..217b5457d 100644 --- a/ipn/ipnlocal/dnsconfig_test.go +++ b/ipn/ipnlocal/dnsconfig_test.go @@ -111,7 +111,8 @@ func TestDNSConfigForNetmap(t *testing.T) { }, prefs: &ipn.Prefs{}, want: &dns.Config{ - Routes: map[dnsname.FQDN][]dnstype.Resolver{}, + OnlyIPv6: true, + Routes: map[dnsname.FQDN][]dnstype.Resolver{}, Hosts: map[dnsname.FQDN][]netaddr.IP{ "b.net.": ips("fe75::2"), "myname.net.": ips("fe75::1"), diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 48d342998..d858c5982 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1951,6 +1951,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log // selfV6Only is whether we only have IPv6 addresses ourselves. selfV6Only := tsaddr.PrefixesContainsFunc(nm.Addresses, tsaddr.PrefixIs6) && !tsaddr.PrefixesContainsFunc(nm.Addresses, tsaddr.PrefixIs4) + dcfg.OnlyIPv6 = selfV6Only // Populate MagicDNS records. We do this unconditionally so that // quad-100 can always respond to MagicDNS queries, even if the OS @@ -2382,7 +2383,9 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router } } - rs.Routes = append(rs.Routes, netaddr.IPPrefixFrom(tsaddr.TailscaleServiceIP(), 32)) + if tsaddr.PrefixesContainsFunc(rs.LocalAddrs, tsaddr.PrefixIs4) { + rs.Routes = append(rs.Routes, netaddr.IPPrefixFrom(tsaddr.TailscaleServiceIP(), 32)) + } return rs } diff --git a/net/dns/config.go b/net/dns/config.go index 4df21cab6..27fa8395a 100644 --- a/net/dns/config.go +++ b/net/dns/config.go @@ -11,6 +11,7 @@ import ( "inet.af/netaddr" "tailscale.com/net/dns/resolver" + "tailscale.com/net/tsaddr" "tailscale.com/types/dnstype" "tailscale.com/util/dnsname" ) @@ -40,6 +41,16 @@ type Config struct { // it to resolve, you also need to add appropriate routes to // Routes. Hosts map[dnsname.FQDN][]netaddr.IP + // OnlyIPv6, if true, uses the IPv6 service IP (for MagicDNS) + // instead of the IPv4 version (100.100.100.100). + OnlyIPv6 bool +} + +func (c *Config) serviceIP() netaddr.IP { + if c.OnlyIPv6 { + return tsaddr.TailscaleServiceIPv6() + } + return tsaddr.TailscaleServiceIP() } // WriteToBufioWriter write a debug version of c for logs to w, omitting diff --git a/net/dns/manager.go b/net/dns/manager.go index db0fd6381..eb4b46592 100644 --- a/net/dns/manager.go +++ b/net/dns/manager.go @@ -12,7 +12,6 @@ import ( "inet.af/netaddr" "tailscale.com/health" "tailscale.com/net/dns/resolver" - "tailscale.com/net/tsaddr" "tailscale.com/net/tsdial" "tailscale.com/types/dnstype" "tailscale.com/types/logger" @@ -122,7 +121,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig // through quad-100. rcfg.Routes = routes rcfg.Routes["."] = cfg.DefaultResolvers - ocfg.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()} + ocfg.Nameservers = []netaddr.IP{cfg.serviceIP()} return rcfg, ocfg, nil } @@ -159,7 +158,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig // or routes + MagicDNS, or just MagicDNS, or on an OS that cannot // split-DNS. Install a split config pointing at quad-100. rcfg.Routes = routes - ocfg.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()} + ocfg.Nameservers = []netaddr.IP{cfg.serviceIP()} // If the OS can't do native split-dns, read out the underlying // resolver config and blend it into our config. diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index d9d6c736a..82f4ebe31 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -377,7 +377,7 @@ func (r *Resolver) HandleExitNodeDNSQuery(ctx context.Context, q []byte, from ne // TODO: more than 1 resolver from /etc/resolv.conf? var resolvers []resolverAndDelay - if nameserver == tsaddr.TailscaleServiceIP() { + if nameserver == tsaddr.TailscaleServiceIP() || nameserver == tsaddr.TailscaleServiceIPv6() { // If resolv.conf says 100.100.100.100, it's coming right back to us anyway // so avoid the loop through the kernel and just do what we // would've done anyway. By not passing any resolvers, the forwarder diff --git a/net/tsaddr/tsaddr.go b/net/tsaddr/tsaddr.go index 94d3422f5..b42cf586b 100644 --- a/net/tsaddr/tsaddr.go +++ b/net/tsaddr/tsaddr.go @@ -36,14 +36,26 @@ var ( tsUlaRange oncePrefix ula4To6Range oncePrefix ulaEph6Range oncePrefix + serviceIPv6 oncePrefix ) -// TailscaleServiceIP returns the listen address of services +// TailscaleServiceIP returns the IPv4 listen address of services // provided by Tailscale itself such as the MagicDNS proxy. +// +// For IPv6, use TailscaleServiceIPv6. func TailscaleServiceIP() netaddr.IP { return netaddr.IPv4(100, 100, 100, 100) // "100.100.100.100" for those grepping } +// TailscaleServiceIPv6 returns the IPv6 listen address of the services +// provided by Tailscale itself such as the MagicDNS proxy. +// +// For IPv4, use TailscaleServiceIP. +func TailscaleServiceIPv6() netaddr.IP { + serviceIPv6.Do(func() { mustPrefix(&serviceIPv6.v, "fd7a:115c:a1e0::53/128") }) + return serviceIPv6.v.IP() +} + // IsTailscaleIP reports whether ip is an IP address in a range that // Tailscale assigns from. func IsTailscaleIP(ip netaddr.IP) bool { diff --git a/net/tsaddr/tsaddr_test.go b/net/tsaddr/tsaddr_test.go index e072e0d6d..f49f44a46 100644 --- a/net/tsaddr/tsaddr_test.go +++ b/net/tsaddr/tsaddr_test.go @@ -31,6 +31,14 @@ func TestInCrostiniRange(t *testing.T) { } } +func TestTailscaleServiceIPv6(t *testing.T) { + got := TailscaleServiceIPv6().String() + want := "fd7a:115c:a1e0::53" + if got != want { + t.Errorf("got %q; want %q", got, want) + } +} + func TestChromeOSVMRange(t *testing.T) { if got, want := ChromeOSVMRange().String(), "100.115.92.0/23"; got != want { t.Errorf("got %q; want %q", got, want) diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index 79dd9ae6b..a1bbc51c6 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -22,6 +22,7 @@ import ( "inet.af/netaddr" "tailscale.com/disco" "tailscale.com/net/packet" + "tailscale.com/net/tsaddr" "tailscale.com/tstime/mono" "tailscale.com/types/ipproto" "tailscale.com/types/key" @@ -407,17 +408,30 @@ func (t *Wrapper) sendOutbound(r tunReadResult) { t.outbound <- r } -var magicDNSIPPort = netaddr.MustParseIPPort("100.100.100.100:0") +var ( + magicDNSIPPort = netaddr.IPPortFrom(tsaddr.TailscaleServiceIP(), 0) // 100.100.100.100:0 + magicDNSIPPortv6 = netaddr.IPPortFrom(tsaddr.TailscaleServiceIPv6(), 0) +) func (t *Wrapper) filterOut(p *packet.Parsed) filter.Response { // Fake ICMP echo responses to MagicDNS (100.100.100.100). - if p.IsEchoRequest() && p.Dst == magicDNSIPPort { - header := p.ICMP4Header() - header.ToResponse() - outp := packet.Generate(&header, p.Payload()) - t.InjectInboundCopy(outp) - return filter.DropSilently // don't pass on to OS; already handled + if p.IsEchoRequest() { + switch p.Dst { + case magicDNSIPPort: + header := p.ICMP4Header() + header.ToResponse() + outp := packet.Generate(&header, p.Payload()) + t.InjectInboundCopy(outp) + return filter.DropSilently // don't pass on to OS; already handled + case magicDNSIPPortv6: + header := p.ICMP6Header() + header.ToResponse() + outp := packet.Generate(&header, p.Payload()) + t.InjectInboundCopy(outp) + return filter.DropSilently // don't pass on to OS; already handled + } } + // TODO(bradfitz): support pinging TailscaleServiceIPv6 too. // Issue 1526 workaround: if we sent disco packets over // Tailscale from ourselves, then drop them, as that shouldn't diff --git a/wgengine/userspace.go b/wgengine/userspace.go index c09e49d5d..6e7f5286a 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -56,7 +56,10 @@ import ( const magicDNSPort = 53 -var magicDNSIP = netaddr.IPv4(100, 100, 100, 100) +var ( + magicDNSIP = tsaddr.TailscaleServiceIP() + magicDNSIPv6 = tsaddr.TailscaleServiceIPv6() +) // Lazy wireguard-go configuration parameters. const ( @@ -486,12 +489,15 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) // handleDNS is an outbound pre-filter resolving Tailscale domains. func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.Wrapper) filter.Response { - if p.Dst.IP() == magicDNSIP && p.Dst.Port() == magicDNSPort && p.IPProto == ipproto.UDP { - err := e.dns.EnqueueRequest(append([]byte(nil), p.Payload()...), p.Src) - if err != nil { - e.logf("dns: enqueue: %v", err) + if p.Dst.Port() == magicDNSPort && p.IPProto == ipproto.UDP { + switch p.Dst.IP() { + case magicDNSIP, magicDNSIPv6: + err := e.dns.EnqueueRequest(append([]byte(nil), p.Payload()...), p.Src) + if err != nil { + e.logf("dns: enqueue: %v", err) + } + return filter.Drop } - return filter.Drop } return filter.Accept } @@ -508,22 +514,38 @@ func (e *userspaceEngine) pollResolver() { continue } - h := packet.UDP4Header{ - IP4Header: packet.IP4Header{ - Src: magicDNSIP, - Dst: to.IP(), - }, - SrcPort: magicDNSPort, - DstPort: to.Port(), - } - hlen := h.Len() - - // TODO(dmytro): avoid this allocation without importing tstun quirks into dns. + var buf []byte const offset = tstun.PacketStartOffset - buf := make([]byte, offset+hlen+len(bs)) - copy(buf[offset+hlen:], bs) - h.Marshal(buf[offset:]) - + switch { + case to.IP().Is4(): + h := packet.UDP4Header{ + IP4Header: packet.IP4Header{ + Src: magicDNSIP, + Dst: to.IP(), + }, + SrcPort: magicDNSPort, + DstPort: to.Port(), + } + hlen := h.Len() + // TODO(dmytro): avoid this allocation without importing tstun quirks into dns. + buf = make([]byte, offset+hlen+len(bs)) + copy(buf[offset+hlen:], bs) + h.Marshal(buf[offset:]) + case to.IP().Is6(): + h := packet.UDP6Header{ + IP6Header: packet.IP6Header{ + Src: magicDNSIPv6, + Dst: to.IP(), + }, + SrcPort: magicDNSPort, + DstPort: to.Port(), + } + hlen := h.Len() + // TODO(dmytro): avoid this allocation without importing tstun quirks into dns. + buf = make([]byte, offset+hlen+len(bs)) + copy(buf[offset+hlen:], bs) + h.Marshal(buf[offset:]) + } e.tundev.InjectInboundDirect(buf, offset) } }