README 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. Userspace DNS tunnel with support for DoH and DoT
  2. David Fifield <[email protected]>
  3. Public domain
  4. dnstt is a DNS tunnel with these features:
  5. * Works over DNS over HTTPS (DoH) and DNS over TLS (DoT) as well as
  6. plaintext UDP DNS.
  7. * Embeds a sequencing and session protocol (KCP/smux), which means that
  8. the client does not have to wait for a response before sending more
  9. data, and any lost packets are automatically retransmitted.
  10. * Encrypts the contents of the tunnel and authenticates the server by
  11. public key.
  12. It has these noteworthy limitations:
  13. * Requires intermediary resolvers to support large responses (1232 bytes,
  14. which is more than the mandated minimum of 512 bytes).
  15. dnstt is an application-layer tunnel that runs in userspace. It doesn't
  16. provide a TUN/TAP interface; it only hooks up a local TCP port with a
  17. remote TCP port (like netcat or `ssh -L`) by way of a DNS resolver. It
  18. does not itself provide a SOCKS or HTTP proxy interface, but you can get
  19. the same effect by running a proxy on the tunnel server and having the
  20. tunnel terminate at the proxy.
  21. ```
  22. .------. .--------. .------.
  23. |tunnel|-- DoH / DoT --|resolver|-- UDP DNS --|tunnel|
  24. |client| '--------' |server|
  25. '------' '------'
  26. ```
  27. ## DNS zone setup
  28. Because the server side of the tunnel acts like an authoritative name
  29. server, you need to own a domain name and set up a subdomain for the
  30. tunnel. Let's say your domain name is example.com and your server's IP
  31. addresses are 203.0.113.2 and 2001:db8::2. Go to your name registrar and
  32. add three new records:
  33. ```
  34. A tns.example.com points to 203.0.113.2
  35. AAAA tns.example.com points to 2001:db8::2
  36. NS t.example.com is managed by tns.example.com
  37. ```
  38. The labels `tns` and `t` can be anything you want, but the `tns` label
  39. should not be a subdomain of the `t` label (that space is reserved for
  40. the contents of the tunnel), and the `t` label should be short (because
  41. there is limited space available in a DNS message, and the domain name
  42. takes up part of that space).
  43. Now, when a recursive DNS resolver receives a query for a name like
  44. aaaa.t.example.com, it will forward the query to the tunnel server at
  45. 203.0.113.2 or 2001:db8::2.
  46. ## Tunnel server setup
  47. Compile the server:
  48. ```
  49. $ cd dnstt-server
  50. $ go build
  51. ```
  52. First you need to generate the server keypair that will be used to
  53. authenticate the server and encrypt the tunnel.
  54. ```
  55. $ ./dnstt-server -gen-key -privkey-file server.key -pubkey-file server.pub
  56. privkey written to server.key
  57. pubkey written to server.pub
  58. ```
  59. Run the server. You need to provide an address that will listen for UDP
  60. DNS packets (`:5300`), the private key file (`server.key`), the root of
  61. the DNS zone (`t.example.com`), and a TCP address to which incoming
  62. tunnel stream will be forwarded (`127.0.0.1:8000`).
  63. ```
  64. $ ./dnstt-server -udp :5300 -privkey-file server.key t.example.com 127.0.0.1:8000
  65. ```
  66. The tunnel server needs to be able to receive packets on an external
  67. port 53. You can have it listen on port 53 directly using `-udp :53`,
  68. but that requires the program to run as root. It is better to run the
  69. program as an ordinary user and have it listen on an unprivileged port
  70. (`:5300` above), and port-forward port 53 to it. On Linux, use this
  71. command to forward external port 53 to localhost port 5300:
  72. ```
  73. # iptables -I INPUT -p udp --dport 5300 -j ACCEPT
  74. # iptables -t nat -I PREROUTING -i eth0 -p udp --dport 53 -j REDIRECT --to-ports 5300
  75. # ip6tables -I INPUT -p udp --dport 5300 -j ACCEPT
  76. # ip6tables -t nat -I PREROUTING -i eth0 -p udp --dport 53 -j REDIRECT --to-ports 5300
  77. ```
  78. You need to also run something for the tunnel server to connect to. It
  79. can be a proxy server or anything else. For testing, you can use an
  80. Ncat listener:
  81. ```
  82. $ ncat -lkv 127.0.0.1 8000
  83. ```
  84. ## Tunnel client setup
  85. Compile the client:
  86. ```
  87. $ cd dnstt-client
  88. $ go build
  89. ```
  90. Copy the server.pub file from the server to the client. You don't need
  91. server.key on the client; leave it on the server.
  92. Choose a public DoH or DoT resolver. There is a list of DoH resolvers
  93. here:
  94. * https://github.com/curl/curl/wiki/DNS-over-HTTPS#publicly-available-servers
  95. And DoT resolvers here:
  96. * https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Public+Resolvers#DNSPrivacyPublicResolvers-DNS-over-TLS%28DoT%29
  97. * https://dnsencryption.info/imc19-doe.html
  98. To run the tunnel client using DoH, you need to provide the URL of the
  99. DoH resolver (`https://doh.example/dns-query`), the server's public key
  100. files (`server.pub`), the root of the DNS zone (`t.example.com`), and
  101. the local TCP port that will receive connections and forward them
  102. through the tunnel (`127.0.0.1:7000`):
  103. ```
  104. $ ./dnstt-client -doh https://doh.example/dns-query -pubkey-file server.pub t.example.com 127.0.0.1:7000
  105. ```
  106. For DoT, it's the same, but use the `-dot` option instead:
  107. ```
  108. $ ./dnstt-client -dot dot.example:853 -pubkey-file server.pub t.example.com 127.0.0.1:7000
  109. ```
  110. Once the tunnel client is running, you can connect to the local end of
  111. the tunnel, type something, and see it appear at the remote end.
  112. ```
  113. $ ncat -v 127.0.0.1 7000
  114. ```
  115. The client also has a plaintext UDP mode that can work through a
  116. recursive resolver or directly to the tunnel server
  117. (`-udp tns.example.com`), but it does not provide any covertness for the
  118. tunnel and should only be used for testing.
  119. ## How to make a proxy
  120. You can make the tunnel into a general-purpose proxy by running a proxy
  121. server and connecting the server end of the tunnel to it. For example,
  122. Ncat has a built-in simple HTTP server:
  123. ```
  124. $ ncat -lkv --proxy-type http 127.0.0.1 8000
  125. $ ./dnstt-server -udp :5300 -privkey-file server.key t.example.com 127.0.0.1:8000
  126. ```
  127. On the client, have the tunnel client listen on 127.0.0.1:7000, and configure
  128. your applications to use http://127.0.0.1:7000/ as an HTTP proxy.
  129. ```
  130. $ ./dnstt-client -doh https://doh.example/dns-query -pubkey-file server.pub t.example.com 127.0.0.1:7000
  131. $ curl -x http://127.0.0.1:7000/ http://example.com/
  132. ```
  133. ## Covertness
  134. Support for DoH and DoT is only to make it more difficult for a local
  135. observer to see that a DNS tunnel is being used, not for the overall
  136. security of the connection. There is a separate encryption layer inside
  137. the tunnel that protects the contents of the tunnel from the resolver
  138. itself.
  139. The encryption of DoH or DoT prevents a network observer between the
  140. tunnel client and the resolver from seeing the remote destination of the
  141. tunnel. An observer can see that the tunnel client is connecting to a
  142. resolver, but cannot see where the resolver is forwarding its queries.
  143. An observer can probably infer, based on volume and other traffic
  144. characteristics, that a tunnel is being used, though it cannot tell
  145. where the remote end of the tunnel is, nor what the contents of the
  146. tunnel are. If the tunnel client is not using DoH or DoT but instead UDP
  147. (`-udp` option), then even an observer between the tunnel client and the
  148. resolver can see that a tunnel is being used and where the remote end of
  149. the tunnel is.
  150. An observer between the resolver and the tunnel server (this includes
  151. the resolver itself) can easily tell that a tunnel is being used and
  152. where the remote end of the tunnel is, because there is no DoH or DoT
  153. encryption at that point. This kind of observer still cannot read the
  154. contents of the tunnel, because there is an additional layer of
  155. end-to-end encryption between the tunnel client and the tunnel server.
  156. An observer who watches what leaves the tunnel server will be able to
  157. see anything that the tunnel server forwards to some other host (if the
  158. tunnel server is acting as a proxy, for example), unless that data has
  159. been separately encrypted before being sent through the tunnel.
  160. ## Encryption and authentication
  161. The tunnel uses a Noise protocol (https://noiseprotocol.org/noise.html)
  162. for end-to-end security between the tunnel client and tunnel server.
  163. This protocol is independent of the DoH or DoT encryption between the
  164. tunnel client and resolver. The specific protocol is Noise_NK_25519_ChaChaPoly_BLAKE2s
  165. (https://noiseprotocol.org/noise.html#protocol-names-and-modifiers).
  166. The NK handshake pattern authenticates the server but not the client.
  167. The Noise layer is sandwiched between two other protocol layers: KCP
  168. (https://github.com/xtaci/kcp-go) which creates a reliable stream on top
  169. of unreliable datagrams, and smux (https://github.com/xtaci/smux) which
  170. provides stream multiplexing and session features. An observer (such as
  171. the intermediary resolver) may read the headers of the KCP layer, but not
  172. of the smux layer nor of the streams that are inside. The model is
  173. similar to what you would get with TLS or SSH over TCP: an observer can
  174. see TCP-level ACKs and sequence numbers, but cannot read the stream data
  175. inside.
  176. ```
  177. application data
  178. smux
  179. Noise
  180. KCP
  181. DNS messages
  182. DoH / DoT / UDP DNS
  183. ```
  184. When you run `dnstt-server -gen-key`, you can save the private and
  185. public keys to a file using the `-privkey-file` and `-pubkey-file`
  186. options. You can then load the keys later using `-privkey-file` on the
  187. server and `-pubkey-file` on the client. Alternatively, you can deal
  188. with the keys as literal hexadecimal strings rather than files. If you
  189. run `dnstt-server -gen-key` without the `-privkey-file` and
  190. `-pubkey-file` options, it will display the keys rather than save them
  191. to files. You can then use the keys with `-privkey` on the server and
  192. `-pubkey` on the client.
  193. ```
  194. $ ./dnstt-server -gen-key
  195. privkey 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
  196. pubkey 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff
  197. $ ./dnstt-server -udp :5300 -privkey 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef t.example.com 127.0.0.1:8000
  198. $ ./dnstt-client -dot dot.example:853 -pubkey 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff t.example.com 127.0.0.1:7000
  199. ```
  200. If you run the server without `-privkey-file` or `-privkey`, it will
  201. generate a temporary keypair and print the public key in the log. But
  202. the key will be different the next time you restart the server, and you
  203. will have to reconfigure clients.
  204. ## Payload sizes
  205. In the client, the available space for user data per query depends on
  206. the length of the domain name in use. Shorter domain names leave more
  207. space for user data.
  208. In the server, the available space for user data per response depends on
  209. the maximum UDP payload size. The larger the UDP payload size, the more
  210. space there is for user data. You want to use as large a UDP payload
  211. size as possible, but not larger than what is supported by the resolver
  212. you are using. Values above 1452 may cause IP fragmentation which can
  213. reduce performance. You can control the maximum UDP payload size with
  214. the `-mtu` option. The default is 1232 bytes; this ought to be supported
  215. by most resolvers that understand EDNS(0) (RFC 6891). For maximum
  216. compatibility, set the maximum to 512, but know that doing so will
  217. reduce downstream bandwidth.
  218. ```
  219. $ ./dnstt-client -mtu 512 -doh https://doh.example/dns-query -pubkey-file server.pub t.example.com 127.0.0.1:7000
  220. ```
  221. The client and server emit an "effective MTU" log line when starting up
  222. that shows how much space is available for user data in each query or
  223. response. For the server, there may be more space available in some
  224. responses and less in others (depending on the size of the corresponding
  225. query); the logged value is the minimum that is guaranteed to be
  226. supported in any response.