Repository navigation
Answer fake DNS queries directly from the tunnel path - #544
Closed
Stanislav-Povolotsky wants to merge 6 commits into
Closed
Stanislav-Povolotsky wants to merge 6 commits into
Stanislav-Povolotsky wants to merge 6 commits into
Conversation
…t-fatal Error handling fix
This commit implements fake DNS. Fake DNS implements a UDP listener DNS A record queries on port 53. It replies with an unused IP address from an address pool, 198.18.0.0/15 by default. When obtaining a new address from the pool, tun2socks needs to memorize which name the address belongs to, so that when a client connects to the address, it can instruct the proxy to connect to the FQDN. To implement this IP to name mapping, the FakeIP module from clash is used.
- fakeip.Pool.get(): fix off-by-one in the address-cycling modulo that made the pool's top address unreachable (collapsed a /30 pool to a single address for every hostname). - tunnel/udp.go: fix symmetric-NAT source check that was permanently disabled for every UDP flow, not just fake-DNS ones, because net.ParseIP() was called on a "host:port" string and always failed. - dns package: guard fakePool/fakeDNSenabled with a mutex instead of unsynchronized globals, close the nil-pool panic window on startup, and drop the dead TTL=600 record header immediately overwritten by TTL=1. - engine: validate fake DNS config and build its pool before bringing up the TUN device/stack, so a bad config can't leave a half-started stack running; wire dns.DisableFakeDNS() into engine.Stop().
Remote DNS intercepts UDP DNS queries for A records on port 53. It replies with an unused IP address from an address pool, 198.18.0.0/15 by default. When obtaining a new address from the pool, tun2socks needs to memorize which name the address belongs to, so that when a client connects to the address, it can instruct the proxy to connect to the FQDN. To implement this IP to name mapping, ttlcache is used. To prevent using multiple addresses for the same name, ttlcache is also used to implement a name to IP mapping. If an IP address is already cached for a name, that address is returned instread. When building a connection, the connection metadata is inspected and if the destination address is associated with a DNS name, the proxy is instructed to use this name instead of the IP address.
ReCreateServer's fake DNS listener binds a real OS-level UDP socket, which only receives traffic on platforms where DNS queries reach it outside the VPN tunnel (e.g. a desktop system resolver pointed at 127.0.0.1). On platforms like Android, VpnService captures all app traffic -- including DNS -- into the TUN device, so it never reaches that socket. Recognize UDP/TCP flows addressed to the configured fake DNS listen address in tunnel/udp.go and tunnel/tcp.go, and answer them locally via the same fake IP pool (dns.HandleQuery), independent of whether the real socket bind succeeded. This mirrors how ordinary TCP/UDP flows are already intercepted in the tunnel, and lets fake DNS work wherever the TUN device captures the traffic, not just when a real loopback socket is reachable.
Author
|
Missed the repository again, sorry |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
dns.ReCreateServer(added by the fake DNS feature) binds a real OS-levelUDP socket for
-fakedns-listen-addr. That only works when DNS queriesreach it outside the tunnel — e.g. a desktop system resolver pointed at
127.0.0.1. On platforms where the VPN/TUN implementation captures allapp traffic, including DNS, into the TUN device (notably Android's
VpnService), queries to the configured fake DNS address never reach thatsocket, so fake DNS silently does nothing there.
This PR adds a second, tunnel-native path:
tunnel/udp.goandtunnel/tcp.gonow recognize UDP/TCP flows addressed to the configured fakeDNS listen address and answer them locally via the same fake IP pool
(
dns.HandleQuery), instead of dialing out through the proxy. This worksindependently of whether the real OS-level socket bind in
ReCreateServersucceeded, so:
127.0.0.1:*) keep working exactly as before, via the real socket.VpnService,where
net.ListenUDPon the fake DNS address isn't even a real bindablelocal interface) now get correct answers too, via the new in-tunnel path.
Both paths share the same DNS message handling and fake IP pool — the
tunnel path just unpacks/repacks the raw query itself instead of going
through
miekg/dns'sD.Server.What changed
dns/fakedns.go: track the resolvedfakeListenAddr(guarded by theexisting mutex); add
IsFakeDNSQuery(dst netip.AddrPort) boolandHandleQuery(data []byte) ([]byte, error)for callers that intercept rawDNS traffic directly from the tunnel.
dns/server.go:ReCreateServernow recordsfakeListenAddras soon asthe address resolves, before attempting the real socket bind — so it's
set even when the bind can't succeed (e.g. binding a virtual/non-local
address on Android). Normalizes the address with
.Unmap(), sincenet.ResolveUDPAddr(...).AddrPort()can return an IPv4-in-6 form whileaddresses coming from the gVisor-based tunnel are always plain IPv4 —
without this,
IsFakeDNSQuery's==comparison would never match.tunnel/udp.go/tunnel/tcp.go: before dialing out, checkdns.IsFakeDNSQuery(metadata.DestinationAddrPort()); if true, hand theconnection to a new
handleFakeDNSUDP/handleFakeDNSTCPthat answerslocally instead. DNS-over-TCP follows RFC 1035 §4.2.2 (2-byte
big-endian length prefix per message).
dns/fakedns_test.go: new tests coveringIsFakeDNSQuery(including theIPv4-in-6 normalization) and the
HandleQueryround trip (query → fakeIP →
LookBackresolves back to the original hostname).