⬆️ ⬇️

Compile a DNS query manually

About the author. James Ruthley is a backend developer at Monzo.



In this article, we will examine the domain name Domain Name Service (DNS) message format and write one message manually. This is more than what you need to use DNS, but I thought that for fun and educational purposes, it’s interesting to see what is under the hood.



We will learn how to:

')



Writing in binary format seems complicated, but in reality I found it to be quite accessible. DNS documentation is well written and understandable, and we will write a small message - only 29 bytes.



DNS overview



DNS is used to translate human-readable domain names (such as example.com ) into machine-readable IP addresses (such as 93.184.216.34). To use DNS, you need to send a request to the DNS server. This request contains the domain name we are looking for. The DNS server is trying to find the IP address of this domain in its internal data storage. If found, then returns it. If it cannot find it, it redirects the request to another DNS server, and the process repeats until an IP address is found. DNS messages are usually sent via the UDP protocol.



The DNS standard is described in RFC 1035 . All diagrams and most of the information for this article are taken in this RFC. I would recommend contacting him if something is not clear.



In this article, we use a hexadecimal format to simplify working with a binary. Below I have added a brief explanation of how they are translated into each other [1] .



Request format



All DNS messages have the same format:



  + --------------------- +
 |  Headline |
 + --------------------- +
 |  Question |  Question for name server
 + --------------------- +
 |  Answer |  Resource Records (RR) to answer the question
 + --------------------- +
 |  Authority |  RR records pointing to an authorized server
 + --------------------- +
 |  Advanced |  RR records with additional information
 + --------------------- + 


Question and answer are in different parts of the message. In our request there will be sections "Title" and "Question".



Headline



The header has the following format:



  0 1 2 3 4 5 6 7 8 9 ABCDEF
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  ID |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 | QR |  Opcode | AA | TC | RD | RA |  Z |  RCODE |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  QDCOUNT |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  ANCOUNT |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  NSCOUNT |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  ARCOUNT |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + 


In this diagram, each cell represents a single bit. Each line has 16 columns, which is two bytes of data. The diagram is divided into lines for ease of perception, but the actual message is a continuous series of bytes.



Since requests and responses have the same header format, some fields are irrelevant to the request and will be set to 0. For a complete description of each field, see RFC1035, Section 4.1.1 .



The following fields matter to us:





Full headline



By combining all the fields, you can write our header in hexadecimal format:



  AA AA - ID
 01 00 - Request parameters
 00 01 - Number of questions
 00 00 - Number of answers
 00 00 - Number of authorized server entries
 00 00 - Number of additional entries 


To get the request parameters, we combine the values ​​of the fields from QR to RCODE, remembering that the fields not mentioned above are set to 0. This gives the sequence 0000 0001 0000 0000 , which in hexadecimal format corresponds to 01 00 . This is what a standard DNS query looks like.



Question



The question section has the following format:



  0 1 2 3 4 5 6 7 8 9 ABCDEF
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  |
 / QNAME /
 / /
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  QTYPE |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  QCLASS |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + 




Now you can write the entire section of the question:



  07 65 - for 'example' length 7, e
 78 61 - x, a
 6D 70 - m, p
 6C 65 - l, e
 03 63 - at 'com' length 3, c
 6F 6D - o, m
 00 - zero byte for the end of the field QNAME 
 00 01 - QTYPE
 00 01 - QCLASS 


An odd number of bytes is allowed in the QNAME section, so stuffing bytes is not required before starting the QTYPE section.



Submit request



We send our DNS message in the body of a UDP request. The following Python code will take our hexadecimal DNS query, convert it to binary and send it to the Google DNS server at 8.8.8.8:53.



 import binascii import socket def send_udp_message(message, address, port): """send_udp_message sends a message to UDP server message should be a hexadecimal encoded string """ message = message.replace(" ", "").replace("\n", "") server_address = (address, port) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: sock.sendto(binascii.unhexlify(message), server_address) data, _ = sock.recvfrom(4096) finally: sock.close() return binascii.hexlify(data).decode("utf-8") def format_hex(hex): """format_hex returns a pretty version of a hex string""" octets = [hex[i:i+2] for i in range(0, len(hex), 2)] pairs = [" ".join(octets[i:i+2]) for i in range(0, len(octets), 2)] return "\n".join(pairs) message = "AA AA 01 00 00 01 00 00 00 00 00 00 " \ "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01 00 01" response = send_udp_message(message, "8.8.8.8", 53) print(format_hex(response)) 


You can run this script by copying the code into the query.py file and running the $ python query.py command in the console. It has no external dependencies, and it should work on Python 2 or 3.



Reading answer



After execution, the script displays the response from the DNS server. We divide it into parts and see what can be figured out.



Headline



The message begins with a header, as well as our request message:



  AA AA - Same ID as before
 81 80 - Other flags, analyze them below.
 00 01 - 1 question
 00 01 - 1 answer
 00 00 - No authorized server records
 00 00 - No additional entries. 


We convert 81 80 to binary format:



  8 1 8 0
 1000 0001 1000 0000 


By converting these bits according to the above scheme, you can see:





Question section



The question section is identical to the same section in the query:



  07 65 - for 'example' length 7, e
 78 61 - x, a
 6D 70 - m, p
 6C 65 - l, e
 03 63 - at 'com' length 3, c
 6F 6D - o, m
 00 - zero byte for the end of the field QNAME 
 00 01 - QTYPE
 00 01 - QCLASS 


Answer Section



The response section has a resource record format:



  0 1 2 3 4 5 6 7 8 9 ABCDEF
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  |
 / /
 / NAME /
 |  |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  TYPE |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  CLASS |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  TTL |
 |  |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
 |  RDLENGTH |
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - |
 / Rdata /
 / /
 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + 


  C0 0C - NAME
 00 01 - TYPE
 00 01 - CLASS
 00 00 
 18 4C - TTL
 00 04 - RDLENGTH = 4 bytes
 5D B8 
 D8 22 - RDDATA 




Extensions



We saw how to create a DNS query. Now you can try the following:








1. Hexadecimal numbers (base 16) are often used as a convenient shortcut for 4 bits of binary data. You can convert data between these formats according to the following table:



DecimalHexBinaryDecimalHexBinary
000000eighteight1000
oneone0001991001
220010tenA1010
330011elevenB1011
fourfour010012C1100
fivefive010113D1101
66011014E1110
77011115F1111


As you can see, you can represent any byte (8 bits) with two hexadecimal characters.

Source: https://habr.com/ru/post/346098/



All Articles