mirror of
https://github.com/tladesignz/dnstt.git
synced 2026-09-28 18:07:56 +03:00
813a8564e8d715478ef1a327c8b166a3583c418b
Userspace DNS tunnel with support for DoH and DoT David Fifield <david@bamsoftware.com> Public domain dnstt is a DNS tunnel with these features: * Works over DNS over HTTPS (DoH) and DNS over TLS (DoT) as well as plaintext UDP DNS. * Embeds a sequencing and session protocol (KCP/smux), which means that the client does not have to wait for a response before sending more data, and any lost packets are automatically retransmitted. * Encrypts the contents of the tunnel and authenticates the server by public key. It has these noteworthy limitations: * Requires intermediary resolvers to support large responses (1232 bytes, which is more than the mandated minimum of 512 bytes). dnstt is an application-layer tunnel that runs in userspace. It doesn't provide a TUN/TAP interface; it only hooks up a local TCP port with a remote TCP port (like netcat or `ssh -L`) by way of a DNS resolver. It does not itself provide a SOCKS or HTTP proxy interface, but you can get the same effect by running a proxy on the tunnel server and having the tunnel terminate at the proxy. ``` .------. .--------. .------. |tunnel|-- DoH / DoT --|resolver|-- UDP DNS --|tunnel| |client| '--------' |server| '------' '------' ``` ## DNS zone setup Because the server side of the tunnel acts like an authoritative name server, you need to own a domain name and set up a subdomain for the tunnel. Let's say your domain name is example.com and your server's IP addresses are 203.0.113.2 and 2001:db8::2. Go to your name registrar and add three new records: ``` A tns.example.com points to 203.0.113.2 AAAA tns.example.com points to 2001:db8::2 NS t.example.com is managed by tns.example.com ``` The labels `tns` and `t` can be anything you want, but the `tns` label should not be a subdomain of the `t` label (that space is reserved for the contents of the tunnel), and the `t` label should be short (because there is limited space available in a DNS message, and the domain name takes up part of that space). Now, when a recursive DNS resolver receives a query for a name like aaaa.t.example.com, it will forward the query to the tunnel server at 203.0.113.2 or 2001:db8::2. ## Tunnel server setup Compile the server: ``` $ cd dnstt-server $ go build ``` First you need to generate the server keypair that will be used to authenticate the server and encrypt the tunnel. ``` $ ./dnstt-server -gen-key -privkey-file server.key privkey written to server.key pubkey written to server.pub ``` Run the server. You need to provide an address that will listen for UDP DNS packets (`127.0.0.1:5300`), the private key file (`server.key`), the root of the DNS zone (`t.example.com`), and a TCP address to which incoming tunnel stream will be forwarded (`127.0.0.1:8000`). ``` $ ./dnstt-server -udp 127.0.0.1:5300 -privkey-file server.key t.example.com 127.0.0.1:8000 ``` The tunnel server needs to be able to receive packets on an external port 53. You can have it listen on port 53 directly using `-udp :53`, but that requires the program to run as root. It is better to run the program as an ordinary user and have it listen on a non-local port (`127.0.0.1:5300` above), and port-forward port 53 to it. On Linux, use this command to forward external port 53 to localhost port 5300: ``` # iptables -t nat -A PREROUTING -i eth0 -p udp --dport 53 -j DNAT --to :5353 ``` You need to also run something for the tunnel server to connect to. It can be a proxy server or anything else. For testing, you can use a Ncat listener: ``` $ ncat -lkv 127.0.0.1 8000 ``` ## Tunnel client setup Compile the client: ``` $ cd dnstt-client $ go build ``` Copy the server.pub file from the server to the client. You don't need server.key on the client; leave it on the server. Choose a public DoH or DoT resolver. There is a list of DoH resolvers here: * https://github.com/curl/curl/wiki/DNS-over-HTTPS#publicly-available-servers And DoT resolvers here: * https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Public+Resolvers#DNSPrivacyPublicResolvers-DNS-over-TLS%28DoT%29 * https://dnsencryption.info/imc19-doe.html To run the tunnel client using DoH, you need to provide the URL of the DoH resolver (`https://doh.example/dns-query`), the server's public key files (`server.pub`), the root of the DNS zone (`t.example.com`), and the local TCP port that will receive connections and forward them through the tunnel (`127.0.0.1:7000`): ``` $ ./dnstt-client -doh https://doh.example/dns-query -pubkey-file server.pub t.example.com 127.0.0.1:7000 ``` For DoT, it's the same, but use the `-dot` option instead: ``` $ ./dnstt-client -dot dot.example:853 -pubkey-file server.pub t.example.com 127.0.0.1:7000 ``` Once the tunnel client is running, you can connect to the local end of the tunnel, type something, and see it appear at the remote end. ``` $ ncat -v 127.0.0.1 7000 ``` The client also has a plaintext UDP mode that can work through a recursive resolver or directly to the tunnel server (`-udp tns.example.com`), but it does not provide any covertness for the tunnel and should only be used for testing. ## How to make a proxy You can make the tunnel into a general-purpose proxy by running a proxy server and connecting the server end of the tunnel to it. For example, Ncat has a built-in simple HTTP server: ``` $ ncat -lkv --proxy-type http 127.0.0.1 8000 $ ./dnstt-server -udp 127.0.0.1:5300 -privkey-file server.key t.example.com 127.0.0.1:8000 ``` On the client, have the tunnel client listen on 127.0.0.1:7000, and configure your applications to use http://127.0.0.1:7000/ as an HTTP proxy. ``` $ ./dnstt-client -doh https://doh.example/dns-query -pubkey-file server.pub t.example.com 127.0.0.1:7000 $ curl -x http://127.0.0.1:7000/ http://example.com/ ``` ## Covertness Support for DoH and DoT is only to make it more difficult for a local observer to see that a DNS tunnel is being used, not for the overall security of the connection. There is a separate encryption layer inside the tunnel that protects the contents of the tunnel from the resolver itself. The encryption of DoH or DoT prevents a network observer between the tunnel client and the resolver from seeing the remote destination of the tunnel. An observer can see that the tunnel client is connecting to a resolver, but cannot see where the resolver is forwarding its queries. An observer can probably infer, based on volume and other traffic characteristics, that a tunnel is being used, though it cannot tell where the remote end of the tunnel is, nor what the contents of the tunnel are. If the tunnel client is not using DoH or DoT but instead UDP (`-udp` option), then even an observer between the tunnel client and the resolver can see that a tunnel is being used and where the remote end of the tunnel is. An observer between the resolver and the tunnel server (this includes the resolver itself) can easily tell that a tunnel is being used and where the remote end of the tunnel is, because there is no DoH or DoT encryption at that point. This kind of observer still cannot read the contents of the tunnel, because there is an additional layer of end-to-end encryption between the tunnel client and the tunnel server. An observer who watches what leaves the tunnel server will be able to see anything that the tunnel server forwards to some other host (if the tunnel server is acting as a proxy, for example), unless that data has been separately encrypted before being sent through the tunnel. ## Encryption and authentication The tunnel uses a Noise protocol (https://noiseprotocol.org/noise.html) for end-to-end security between the tunnel client and tunnel server. This protocol is independent of the DoH or DoT encryption between the tunnel client and resolver. The specific protocol is Noise_NK_25519_ChaChaPoly_BLAKE2s (https://noiseprotocol.org/noise.html#protocol-names-and-modifiers). The NK handshake pattern authenticates the server but not the client. The Noise layer is sandwiched between two other protocol layers: KCP (https://github.com/xtaci/kcp-go) which creates a reliable stream on top of unreliable datagrams, and smux (https://github.com/xtaci/smux) which provides stream multiplexing and session features. An observer (such as the intermediary resolver) may read the headers of the KCP layer, but not of the smux layer nor of the streams that are inside. The model is similar to what you would get with TLS or SSH over TCP: an observer can see TCP-level ACKs and sequence numbers, but cannot read the stream data inside. ``` application data smux Noise KCP DNS messages DoH / DoT / UDP DNS ``` When you run `dnstt-server -gen-key`, you can save the private and public keys to a file using the `-privkey-file` and `-pubkey-file` options. You can then load the keys later using `-privkey-file` on the server and `-pubkey-file` on the client. Alternatively, you can deal with the keys as literal hexadecimal strings rather than files. If you run `dnstt-server -gen-key` without the `-privkey-file` and `-pubkey-file` options, it will display the keys rather than save them to files. You can then use the keys with `-privkey` on the server and `-pubkey` on the client. ``` $ ./dnstt-server -gen-key privkey 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef pubkey 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff $ ./dnstt-server -udp 127.0.0.1:5300 -privkey 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef t.example.com 127.0.0.1:8000 $ ./dnstt-client -dot dot.example:853 -pubkey 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff t.example.com 127.0.0.1:7000 ``` If you run the server without `-privkey-file` or `-privkey`, it will generate a temporary keypair and print the public key in the log. But the key will be different the next time you restart the server, and you will have to reconfigure clients.
Languages
Go
100%