Traditional DNS queries and responses are sent over UDP and TCP without any encryption. Thus, this protocol is vulnerable to privacy tracking and DNS spoofing. Almost all traditional DNS queries are monitored and falsified during transmission in specific countries including China to block websites and injecting advertisements.
According to the image above, the ‘A’ record of ‘reddit.com’ is altered to a wrong IP address which belongs to services of Facebook.
To solve these problems, researchers designed DNS-over-TLS protocol which provides DNS resolutions over TLS-encrypted TCP connection delineated in RFC7858. DNS-over-TLS protocol improves privacy and security between client and servers since TLS is invulnerable to ‘Man-in-the-middle attack’ and cannot be deciphered easily.
Nowadays, a few public DNS resolvers such as Google Public DNS and Cloudflare DNS support this protocol, and famous operating systems such as Android and web browsers provides related functions to implement them.
By default, a DNS server that supports DNS-over-TLS must listen and accept TCP connection on port 853, unless both client and server have a mutual agreement to use other specific ports. To keep security and privacy, both DNS servers and clients must not use port 853 to transmit plain DNS query and response texts or encrypted texts mixed plain ones.
Once the client established a connection via TCP with DNS server, it muse process the regular TLS handshake. During that procedure, the client might authenticate the DNS server according to its configuration. After the TLS negotiation completes, the connection will be encrypted and guarded against monitoring.
All DNS messages including queries and responses must provide the two-octet field to clarify the length of the subsequent message. To minimize latency, the client must send multiple queries without waiting for a reply from the DNS server. To reduce TLS connection setup cost and save resources, client and server should not close the connection immediately after sending a response. In other words, they should reuse existing connections to transmit subsequent messages. In some cases, both the client and server might keep idling for a long period.
Since DNS-over-TLS might be the monopoly DNS resolver of the system and the server is specified with the hostname, the client might use other DNS server, which is unanimously called ‘the bootstrap DNS’, to perform a plain query to obtain the IP address corresponding with the intended encrypted TLS server. Otherwise, users should manually provide the IP address for security considerations or simply use a DNS-over-TLS server which could be reached by IP address, such as ‘220.127.116.11:853’ (Cloudflare DNS).
To implement a DNS-over-TLS client, socket and ssl module provided by Python could be used to establish a connection with DNS server, perform TLS handshake, and transmit messages. In order to send multiple queries without waiting for a response, a threading module could be imported to create a separate thread for receiving messages from the server.
import socket import ssl import threading class TLSUpstream: def __init__(self, ip, server, timeout, port=853): self.ip = ip self.server = server self.timeout = timeout self.port = port self.wrap_sock = None def shake_hand(self): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) context.verify_mode = ssl.CERT_REQUIRED context.check_hostname = True context.load_default_certs() with socket.create_connection((self.ip, self.port), timeout=self.timeout) as sock: self.wrap_sock = context.wrap_socket(sock, server_hostname=self.server) receive_thread = threading.Thread(target=self.receive, args=()) receive_thread.daemon = True receive_thread.start() def query(self, query): query = "\x00".encode() + chr(len(query)).encode() + query print('version:', self.wrap_sock.version()) self.wrap_sock.send(query) def receive(self): while True: query_header = self.wrap_sock.recv(2) query_length = int.from_bytes(query_header[1:2], "big") query_result = self.wrap_sock.recv(query_length) print('response:', query_result)
For using the class implemented above, simply replace the variable ‘query_data’ with other plain DNS query messages.
query_data = b'' # plain DNS query data tls_upstream = TLSUpstream('18.104.22.168', 'dns.google', 10, 853) tls_upstream.shake_hand() tls_upstream.query(query_data)
DNS-over-HTTPS Introduction and Implementation: https://siujoeng-lau.com/2019/09/dns-over-https/
RFC 7858: https://tools.ietf.org/html/rfc7858
DNS-over-TLS Wikipedia: https://en.wikipedia.org/wiki/DNS_over_TLS
DNS-over-TLS Introduction by Google Public DNS: https://developers.google.com/speed/public-dns/docs/dns-over-tls