-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
tun: replace the DNS client in netstack with net.Resolver #43
base: master
Are you sure you want to change the base?
Conversation
Thanks for the patches. First, some house keeping -- this repo uses With regards to the DNS change, I'm a bit lost in the diff, but my current understand is:
Is this more or less what's happening? If so, I'm wondering what happens when there are no system resolvers specified in /etc/resolv.conf. Will net.Resolver still pass an IP to be hijacked to the Dial function, or will it just fail? What is the behavior when a tnet has 1 DNS server, but the system specifies multiple DNS servers? Will tnet's 1 server be queried multiple times? |
Thanks, I added the signoffs, I also noticed some wonkiness with I realized I forgot to document some of the behavioral changes this PR introduces. It's not easy to describe without an excruciatingly long explainer on how DNS works in Go apps, so here goes. The net package has two ways of looking up a hostname: native OS functions/syscalls or pure Go resolver. Since the host is unaware of the netstack networking layer, if lookups are handled by the OS they will not be sent over the WireGuard connection, and internal names won't be resolved. So for any new connections over the WireGuard tunnel, it makes sense to also tunnel DNS queries. Using a net.Resolver with PreferGo set and Dial set to tnet's will force this behavior, but unfortunately that's not the end of it. Even with the pure Go resolver configured, the local DNS config (e.g. resolv.conf, nsswitch.conf) is still used to determine the DNS server to contact. (If no config is present, I believe it default to "localhost:53".) This can cause the pure Go resolver to lookup a hostname over the WireGuard tunnel, but attempt to contact the server specified in the host config. On my machine, it was attempting to send DNS queries to 192.168.1.1:53, which was not part of the allowed ips address range. So the dialDNS method attempts to correct this by swapping out the DNS server IP supplied by the system config for the DNS server IP(s) specified in the CreateNetTUN parameter. Unfortunately, the net package does not expose the DNS server(s) it discovers via reading system configs, so this dialDNS method instead replaces any connection attempts with a random IP in dnsServers, unless the address is already in dnsServers. (The range over a map makes the order random.) The previous version of Dial was resolving all IPs for the address with the custom resolver implemented in LookupContextHost. That version would query each DNS server in dnsServers until it received a valid response. This version uses the net.Resolver to do the lookup, but it overrides the IP address that net.Resolver attempts to dial, via the dialDNS method. This causes a subtle change in behavior: LookupContextHost returns the first valid record from a DNS server, dialDNS returns the first valid connection to a DNS server. If the record cannot be resolved by this server, it won't attempt to query another server. That behavior could be preserved by adding a wrapper around the call to tnet.resolver.LookupHost, if it is important to keep. Let me know if you would like any of these details documented in the commit message. |
5861be1
to
2f788f7
Compare
Signed-off-by: Jason A. Donenfeld <[email protected]>
gVisor has a separate branch for use with real Go, since they have some forked Go. I fixed things up in slightly different ways for both the pkgsite license and the api bump. Thanks for bringing that all to my attention.
Okay, so your explanation matches my three bullet points. I think we're on the same page. I worry, though, isn't there a problem with:
I asked about a corner case of this in my prior message when I wrote:
It looks like in that case, lookups will fail. But lookups shouldn't fail for that reason. So as much as I'd like to stop open coding DNS resolver logic in this module and use something in the library, it doesn't look like Go's resolver is flexible enough to make that work. I briefly looked into what it'd take to hijack Go's internal resolver logic using unsafe, but it looks like there's a lot of build tags and such in there complicating it. I wonder if this would be a good proposal for Go 1.17? |
Signed-off-by: Jason A. Donenfeld <[email protected]>
It should be enough to check for the trailing zero name. Signed-off-by: Jason A. Donenfeld <[email protected]>
Signed-off-by: Jason A. Donenfeld <[email protected]>
Signed-off-by: Jason A. Donenfeld <[email protected]>
I think hijacking the internal resolver would be overstepping if it forces all DNS lookups for the process to go over the tunnel. And you can already do this (minus loading the resolv.conf config) by modifying |
Use the net.Resolver DNS client to send domain lookups to the server specified as a CreateNetTUN parameter. The net package's DNS client handles DNS request and response parsing when PreferGo is true. Like the previous DNS client it replaces, the net.Resolver instances also sends DNS queries over the WireGuard connection. Tested on and with support from Fly.io. Signed-off-by: Ben Burkert <[email protected]>
Oh, great! That's very promising. So we always get some request. Two questions:
Yea, I wasn't thinking hijacking DefaultResolver, but just an instance of Resolver. But it looks like resolverConfig might be global anyway, which is a bummer. |
I don't think it would, because AFAIK none of them support the
Yes, but if subsequent lookup fails with a "temporary" DNS error (like a timeout), the resolver will redial then resend the query, until it runs out of servers. But dialDNS could pick the same server on a redial.
The max is None of this seems ideal, so perhaps a better approach is to keep the DNS lookups handled by |
After thinking a bit more about this a bit more, I'm no longer in favor of using a different DNS client. What I really want is a way to override the What do you think about extracting the DNS client here into an exported type with just a |
6b5293b
to
6005c57
Compare
58beb0f
to
54dbe24
Compare
1ae3898
to
4e9e5da
Compare
eba36c5
to
ffb742d
Compare
89a9432
to
b9669b7
Compare
Hi, Not sure if it's right place to chime in. I also want to config the netstack to use DoH/DoT. Something that has changed over the time. How about adding a resolver interface, like |
Hello,
I used the new netstack package to setup an in-process tunnel for
tunneling SSH connections over WireGuard. It worked quite well, I was
very happy with how easy it was. Here are some changes I made as part of
my work. The biggest one is replacing the custom DNS client with an
instance of net.Resolver.
The TUN type includes a DNS client for sending hostname lookup queries
to the DNS server(s) over the WireGuard connection. The builtin client
resolver in the net package also uses the dnsmessage package to
implement a DNS client, which can bypass the servers specified in
resolv.conf when a custom Dial func is provided. This is a patch to
replace the netstack package's DNS client with an instance of
net.Resolver configured to connect to the specified servers instead of
the system's configured DNS servers (via resolv.conf). It was tested on
and with support from Fly.io.
Also included is a change to update the modules in the netstack package
and fixes for the client & server examples, along with a change to add a
copyright file to the netstack package to fix https://pkg.go.dev/
support.