Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net/dns/{publicdns,resolver}: add NextDNS DoH support #5556

Merged
merged 1 commit into from Sep 8, 2022

Conversation

bradfitz
Copy link
Member

@bradfitz bradfitz commented Sep 6, 2022

NextDNS is unique in that users create accounts and then get
user-specific DNS IPs & DoH URLs.

For DoH, the customer ID is in the URL path.

For IPv6, the IP address includes the customer ID in the lower bits.

For IPv4, there's a fragile "IP linking" mechanism to associate your
public IPv4 with an assigned NextDNS IPv4 and that tuple maps to your
customer ID.

We don't use the IP linking mechanism.

Instead, NextDNS is DoH-only. Which means using NextDNS necessarily
shunts all DNS traffic through 100.100.100.100 (programming the OS to
use 100.100.100.100 as the global resolver) because operating systems
can't usually do DoH themselves.

Once it's in Tailscale's DoH client, we then connect out to the known
NextDNS IPv4/IPv6 anycast addresses.

If the control plane sends the client a NextDNS IPv6 address, we then
map it to the corresponding NextDNS DoH with the same client ID, and
we dial that DoH server using the combination of v4/v6 anycast IPs.

Updates #2452

@bradfitz bradfitz force-pushed the bradfitz/nextdns branch 3 times, most recently from dee6825 to b6d6cbd Compare September 7, 2022 03:52
@bradfitz bradfitz marked this pull request as ready for review September 7, 2022 03:55
Copy link
Member

@crawshaw crawshaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure our code is going to do the right thing if your only DNS server is NextDNS, so there is no IPv4 address. It should work, because we have an address to give the OS, but I got lost trying to read through the code.

@@ -78,13 +79,14 @@ func (c Config) hasRoutes() bool {
}

// hasDefaultIPResolversOnly reports whether the only resolvers in c are
// DefaultResolvers, and that those resolvers are simple IP addresses.
// DefaultResolvers, and that those resolvers are simple IP addresses
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have spent a minute trying to decide if that should be an and or an or. I don't know. Maybe this function needs a different name? tailscaleOwnsDNS?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the code, it seems to be 'and'.

net/dns/publicdns/publicdns.go Show resolved Hide resolved
tailcfg/tailcfg.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@maisem maisem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you've tested that this works?

@bradfitz
Copy link
Member Author

bradfitz commented Sep 7, 2022

I've tested it but will do more after latest control changes. I also have some questions for NextDNS on how to test it better.

@bradfitz
Copy link
Member Author

bradfitz commented Sep 8, 2022

And added more tests.

@bradfitz bradfitz force-pushed the bradfitz/nextdns branch 2 times, most recently from 41aa508 to e49c102 Compare September 8, 2022 05:04
Copy link
Member

@danderson danderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable. My main request is to try and clarify in the publicdns package the difference between IPs that can be used for UDP querying, and those which are just static pre-resolutions of DoH resolver names. It took me several confused reads through the forwarder code to figure out that there were two non-equivalent sets of IPs (IPs indicating you should DoH to NextDNS, and IPs to which you should send those DoH queries) being kicked around when configuring for NextDNS resolution.

@@ -78,13 +79,14 @@ func (c Config) hasRoutes() bool {
}

// hasDefaultIPResolversOnly reports whether the only resolvers in c are
// DefaultResolvers, and that those resolvers are simple IP addresses.
// DefaultResolvers, and that those resolvers are simple IP addresses
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the code, it seems to be 'and'.

net/dns/publicdns/publicdns.go Show resolved Hide resolved
net/dns/publicdns/publicdns.go Show resolved Hide resolved
}

// DoHIPsOfBase returns a DoH base URL's IP addresses.
// It is basically the inverse of DoHEndpointFromIP.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it basically the inverse, or exactly the inverse? I think you can drop "basically".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, it's not exactly the inverse, because this one also returns IPv4s.

Can you make the docs of this function clarify that it's not returning IPs which are the equivalent of the DoH URL, it's returning a static resolution for the URL's hostname? It took me many re-reads to figure out that the returned IPs from this function cannot be used for UDP queries to NextDNS, only as a static resolution of the URL's hostname.

And drop the mention of being the inverse of DoHEndpointFromIP, that to me suggested that, just like DohEndpointFromIP simply lets you uplift a UDP DNS query into DoH, this one lets you go the other way - which would be true except for the IPv4 results which lose information.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated comment.

if b, ok := m[ip]; ok {
return b, false, true
}
if nextDNSv6RangeA.Contains(ip) || nextDNSv6RangeB.Contains(ip) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the IPv4 NextDNS servers? Constants are declared for them, but they don't seem to be in KnownDOH or handled specially here. Shouldn't we at least do "naked" DoH without the client ID in that case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only do DoH to NextDNS if we know the profile ID. The IPv4 addresses are only used for dialing DoH, not for regular DNS.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated comment.

{
base: "https://dns.nextdns.io/c3a884",
want: ips(
"45.90.28.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will this work? If I translate the DoH name to IPs, the IPv4s lack a client ID, so presumably are not going to behave the same as the v6 endpoints? Should this function only return v6 addrs, so that you don't lose fidelity roundtripping from URL > IP > URL ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the IPs for dialing DoH.

On the wire from control to clients we'll only ever send the NextDNS v6 IP containing the profile ID in the lower bytes. We'll eat any NextDNS v4 address server side as if the user didn't specify it at all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the comment on the func

@@ -22,11 +29,63 @@ func KnownDoH() map[netip.Addr]string {
return knownDoH
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't comment any higher, but KnownDOH is now broken, it doesn't return all known DoH. How does this affect callers?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed it. It was only used by tests.

NextDNS is unique in that users create accounts and then get
user-specific DNS IPs & DoH URLs.

For DoH, the customer ID is in the URL path.

For IPv6, the IP address includes the customer ID in the lower bits.

For IPv4, there's a fragile "IP linking" mechanism to associate your
public IPv4 with an assigned NextDNS IPv4 and that tuple maps to your
customer ID.

We don't use the IP linking mechanism.

Instead, NextDNS is DoH-only. Which means using NextDNS necessarily
shunts all DNS traffic through 100.100.100.100 (programming the OS to
use 100.100.100.100 as the global resolver) because operating systems
can't usually do DoH themselves.

Once it's in Tailscale's DoH client, we then connect out to the known
NextDNS IPv4/IPv6 anycast addresses.

If the control plane sends the client a NextDNS IPv6 address, we then
map it to the corresponding NextDNS DoH with the same client ID, and
we dial that DoH server using the combination of v4/v6 anycast IPs.

Updates #2452

Change-Id: I3439d798d21d5fc9df5a2701839910f5bef85463
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants