mirror of
https://github.com/tladesignz/dnstt.git
synced 2026-09-29 02:17:58 +03:00
246 lines
9.7 KiB
Plaintext
246 lines
9.7 KiB
Plaintext
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.
|