##Overview DNS is an application layer protocol. Comparing to the HTTP protocol which is text-based, DNS is a binary protocol. As underlying transport protocol it can use `TCP` or `UDP`. According to the [RFC](https://tools.ietf.org/rfc/rfc1035.txt) if the message is less or equal to 512 bytes UDP is used, otherwise TCP is used. Default port for the application that handles DNS requests is `53`. ##Structure of DNS packet Here is described data that is incapsulated into UDP (TCP) packet. All communications inside of the domain protocol are carried in a single format called a message. [Image Name 6]:https://secretnotes.space/articleimage?id=66 ![Image Name 6] ###DNS Headers First comes DNS headers which are 12 bytes long. [Image Name 2]:https://secretnotes.space/articleimage?id=62 =600x400 ![Image Name 2] `ID` - 2 bytes. To make sure that answer came from the DNS server you asked and the right question was answered, DNS server has to extract the `ID` field from packet and send it back in the response. `QR` - 1 bit field that specifies is this a query or response. `OPCODE` - four bit field that specifies what kind of query is it. In this example it will be allways 0. - 0 - Standard query - 1 - An inverse query - 2 - A server status request - 3 - 15 - Reserved for future usage `AA` - Used in responses. Specifies that the responding name server is an authority for the domain name in question section. `TC` - Truncation. Specifies that message was truncated due to lenght limitations of the channel. `RD` - Recursion Desired. Client asks server to pursue query recursively. This field is set in query and may be copied to answer. In this example it will be 0. `RA` - Recursion Available. This bit is set in response and informs if server supports recursive resolving. In this example it will be 0. `Z` - 3 bits.Reserved for future use. `RCODE` - 4 bits. Response code. It is set in response. For simplicity it is assumed that all queries are successfull. - 0 - No error condition - 1 - Format error - 2 - Server failure - 3 - Name error - 4 - Not implemented - 5 - Refused `QCOUNT` - 2 bytes. Number of questions in question section. Usually it is 1 because we resolve 1 domain name. `ANCOUNT` - 2 bytes. Number of resource records in answer section. Not static value and can be more than 1. For example DNS server can return several `A` records. `NSCOUNT` - 2 bytes. Number of name server resource records in the authority records section. For simplicity it will be set to 0. `ARCOUNT` - 2 bytes. Number of resource records in the additional records section. For simplicity it will be set to 0. ###DNS Question Section [Image Name 3]:https://secretnotes.space/articleimage?id=63 ![Image Name 3] `QNAME` - Variable length. Represents a sequence of labels. Each label starts with the length. Than goes number of bytes specified before in length. Ends with a null byte `\x00`. For example we have domain name `somesite.com`. This name consists from 2 parts. First part is 8 symbols. So first byte will be `\x08`. Than goes actuall 8 bytes of the first part of the name. Each byte is a symbol in ASCII table. So it will be `\x73\x6f\x6d\x65\x73\x69\x73\x65`. Next part is `com`. It is 3 bytes long. So next comes `\x03` and is continued by `\x63\x6f\x6d`. Last byte is null byte `'\x00` that ends `QNAME` section. `QTYPE` - 2 bytes. Specifies what type of question it is. For example if we look for `A`record it will be encoded as `\x00\x01`. `QCLASS` - 2 bytes. Usually it is `IN` which stands for `Internet`. Hex value of it will be `\x00\x01`. ###DNS Answer Section [Image Name 4]:https://secretnotes.space/articleimage?id=64 ![Image Name 4] `NAME` - contains domain name from question section. But thanks to the `Message Compression` usually here is presented the offset from the begining of DNS packet to the part where domain name begins. It takes 2 bytes where 2 left bits in this sequence are both 1. Headers take 12 bytes so usually OFFSET will be 12. [Image Name 5]:https://secretnotes.space/articleimage?id=65 ![Image Name 5] For example 2 bytes that points to the start of the `QNAME` field will be `\xc0\x0c`. `TYPE` - 2 bytes. Specifies what type of record it is. It can be `A` record which is IPv4 address and field takes value of `\x00\x01`, or `AAAA` record which is IPv6 address so field will be encoded as `\x00\x1c`. `CLASS` - 2 bytes. Ussualy in has `IN` value which stands for `Internet`. Field is encoded as `\x00\x01`. `TTL` - 4 bytes value that specifies time interval in seconds that the record may be cached before it should be discharged. `RDLENGTH` - 2 bytes value that specifies length of the `RDATA` field. It depends on what type of record is in answer. If it is `A` record we need 4 bytes to encode it so `RDLENGTH` will be `\x00\x04`, if it is `AAAA` record we need 16 bytes so `RDLENGTH` will be `\x00\x10`. `RDATA` - Variable length field that contains information about asked resource. Depends on `TYPE` and `CLASS` fields. For example if `TYPE` is `A` record which is `\x00\x01` and `CLASS` is `IN` which is also `\x00\x01` then `RDATA` field will store IPv4 address encoded in 4 bytes. ##Format of Zone Files There are actually rules that specify how to write zone files and what records should be there. This is a simple DNS server so format can be any. It is important to parse those data in proper way and return proper result. For example format of zone file can be `json` and look like this: ```json { "$origin":"somesite.com", "$ttl":3600, "soa":{ "mname":"ns1.somesite.com", "rname":"admin.somesite.com", "serial":"{time}", "refresh":3600, "retry":600, "expire":604800, "minimum":86400 }, "ns":[ { "host": "ns1.somesite.com" }, { "host": "ns2.somesite.com" } ], "a":[ { "name":"@", "ttl":400, "value":"192.168.1.21"} ], "aaaa":[ { "name":"@", "ttl":400, "value":"fe80::7925:707e:dada:cc10"} ] } ``` ##Realization Realization of Python DNS server is based on these [video series](https://www.youtube.com/watch?v=HdrPWGZ3NRo&list=PLBOh8f9FoHHhvO5e5HF_6mYvtZegobYX2) with few changes. This server can handle AAAA records and if this server does not have requested records in its local zone files it will recursively look for it. ```python import socket import glob import json import binascii port = 53 host = '192.168.1.21' sc = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) sc.bind((host,port)) zones = [] def get_zones(): global zones zones = glob.glob('./Zones/*.json') get_zones() def buildresponse(data): transactionID = data[0:2] flags = get_flags(data[2:4]) q_count = b'\x00\x01' domain_name, record_type = get_question(data[12:]) for string in zones: sub_domain_list = string.split('/')[-1].split('.')[:-1] sub_domain_name = '' for i in sub_domain_list: sub_domain_name += i sub_domain_name += '.' sub_domain_name = sub_domain_name[:-1] if domain_name == sub_domain_name: records, length = get_answer_count(domain_name,record_type) a_count = length.to_bytes(2,byteorder='big') ns_count = (0).to_bytes(2,byteorder='big') ar_count = (0).to_bytes(2,byteorder='big') dns_header = transactionID + flags + q_count + a_count + ns_count + ar_count dns_question = build_dns_question(domain_name,record_type) dns_body = build_dns_body(record_type,records) return dns_header + dns_question + dns_body else: sc2 = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) sc2.sendto(data,('8.8.8.8',53)) data,addr = sc2.recvfrom(512) return data def build_dns_body(records_type,records): bytes_repr = b'' for record in records: bytes_repr += b'\xc0\x0c' if records_type == 1: bytes_repr += (1).to_bytes(2,byteorder='big') elif records_type == 28: bytes_repr += (28).to_bytes(2,byteorder='big') bytes_repr += (1).to_bytes(2,byteorder='big') bytes_repr += record['ttl'].to_bytes(4,byteorder='big') if records_type == 1: bytes_repr += (4).to_bytes(2,byteorder='big') value_parts = record['value'].split('.') for part in value_parts: bytes_repr += int(part).to_bytes(1,byteorder='big') elif records_type == 28: bytes_repr += (16).to_bytes(2,byteorder='big') value_parts = record['value'].split(':') count = len(value_parts) empty_index = value_parts.index('') if (count < 8): del(value_parts[empty_index]) amount_to_fill = 8 - len(value_parts) for i in range(amount_to_fill): value_parts.insert(empty_index,'0000') for part in value_parts: bytes_repr += binascii.unhexlify(part) return bytes_repr def build_dns_question(domain_name,record_type): bytes_repr = b'' for part in domain_name.split('.'): word_lenght = len(part).to_bytes(1,byteorder='big') bytes_repr += word_lenght for character in part: bytes_repr += ord(character).to_bytes(1,byteorder='big') bytes_repr += (0).to_bytes(1,byteorder='big') if record_type == 1: bytes_repr += (1).to_bytes(2,byteorder='big') elif record_type == 28: bytes_repr += (28).to_bytes(2,byteorder='big') bytes_repr += (1).to_bytes(2,byteorder='big') return bytes_repr def get_flags(flags): byte1 = bytes(flags[0]) QR = '1' mask = 0b01111000 QPCODE = bin(int.from_bytes(byte1,'big') & mask)[2:].zfill(8)[1:5] AA = '1' TC = '0' RD = '0' #Second Byte RA = '0' Z = '000' RCODE = '0000' return int(QR + QPCODE + AA + TC + RD,2).to_bytes(1,byteorder='big') + int(RA + Z + RCODE,2).to_bytes(1,byteorder='big') def get_question(data): domain = '' length_byte_index = 0 start_byte = length_byte_index + 1 bytes_to_read = data[length_byte_index] while True: domain_str = data[start_byte:start_byte + bytes_to_read].decode('ascii') domain += domain_str + '.' length_byte_index = start_byte + bytes_to_read if data[length_byte_index] == 0: break start_byte = length_byte_index + 1 bytes_to_read = data[length_byte_index] domain_name = domain[:-1] record_type_bytes = data[length_byte_index + 1:length_byte_index + 3] record_type = int.from_bytes(record_type_bytes,byteorder='big') return domain_name, record_type def get_answer_count(domain_name,record_type): if record_type == 1: record_type = 'a' elif record_type == 28: record_type = 'aaaa' needed_filename = '' global zones for i in zones: if domain_name in i: needed_filename = i results = [] if needed_filename != '': with open(needed_filename,'r') as zonedata: zone_dict = json.load(zonedata) for i in zone_dict[record_type]: results.append(i) return results, len(results) while 1: data,addr = sc.recvfrom(512) response = buildresponse(data) if response is not None: sc.sendto(response,addr) ``` It can be launched with command ```bash sudo python3 dns.py ``` Now from other system we can make request to this dns server. It can be done by `dig` command ```bash dig [domain name] @[ip address of dns server] ------------ example ------------------- dig somesite.com @192.168.1.46 ``` Also we can request record of specific type ```bash ------------- IPv6 Address ---------- dig somesite.com @192.168.1.46 AAAA ------------- IPv4 Address ---------- dig somesite.com @192.168.1.46 A ``` Another command with the help of which we can resolve dns is `nslookup`. This command is accessible on Linux and Windows. ```bash nslookup [domain-name] nslookup [domain-name] [dns-server] --------- example ------------- nslookup somesite.com 192.168.1.46 ``` ##Configuring Linux Domain Names Resolving In Linux there are several source from which system can resolve the hostname. All the functionality comes with `glibc` library. Order in which system resolves hostname is defined with the help of `Name Service Switch (NSS)`. Configuration file is `/etc/nsswitch.conf`. Especially we have to be interested in `hosts:` row. Example of /etc/nsswitch.conf file ```bash # /etc/nsswitch.conf # # Example configuration of GNU Name Service Switch functionality. # If you have the `glib-doc-reference` and `info` packages installed, try: # `info libc "Name Service Switch"' for information about this file. passwd: files systemd group: files systemd shadow: files gshadow: files hosts: files mdns4_minimal [NOTFOUND=return] dns networks: files protocols: db files services: db files ethers: db files rpc: db files netgroup: nis ``` Here we have 3 sources for resolving hostnames: - files - usually means `/etc/hosts` file. - mdns4_minimal - Multicast DNS Service usually provided by avahi daemon. Uses by default `.local` domain. Record `[NOTFOUND=return]` means that if this mdns4\_minimal is accessible and has no record for what user has requested, hostname resolving will stop. More details about writing nsswitch.conf file can be found at [Format of the nsswitch.conf file] (https://docs.oracle.com/cd/E19683-01/817-4843/a12swit-84565/index.html). - dns - DNS server(s) where system will make request in order to resolve hostname. Addresses of these dns servers are located in `/etc/resolv.conf` file. Here can be specified ip addresses of the dns servers or file can be a symlink that points to the `/run/systemd/resolve/stub-resolv.conf` which is a configuration file used by `systemd-resolved` service which provides network name resolution to local applications. Usually as a DNS server there is specified `127.0.0.53`. To make system use actuall data in `/etc/resolv.conf` it has to be file not a symlink, so symlink has to be removed and `systemd-resolved` service stopped. Order in which these sources are specified is important. If hostname was not resolved with the help of first source it will continue resolving with the help of next source unless creteria of resolving was not changed (like `[NOTFOUND=return]` shown above). Stop and turn off `systemd-resolved` service ```bash sudo systemctl disable systemd-resolved sudo systemctl stop systemd-resolved ``` Delete the symlink `/etc/resolv.conf` that points to `/run/systemd/resolve/stub-resolv.conf` ```bash sudo rm /etc/resolv.conf ``` Now create file /etc/resolv.conf ```bash sudo touch /etc/resolv.conf ``` Add new dns server ip address to the resolv.conf file ```bash #nameserver [ip address] nameserver 192.168.1.46 ``` Edit file `/etc/NetworkManager/NetworkManager.conf`. Under section main add __dns=none__ string to stop NetworkManager from rewriting `/etc/resolv.conf` file. ```bash [main] plugins=ifupdown,keyfile dns=none [ifupdown] managed=false [device] wifi.scan-rand-mac-address=no ``` Restart Network Manager ```bash sudo systemctl restart NetworkManager ``` Now system will use dns server on 192.168.1.46 to resolve domain names. ##Configuring Windows Domain Names Resolving `Control Panel` -> `Network and Internet` -> `Network and Sharing Center`. Then choose your interface. Then go `Properties`, choose Internet `Protocol Version 4 (TCP/Ipv4)`, then `Properties` and write address of new DNS server to `Use the following DNS server addresses` section. Last step press `OK`. [Image Name 1]:https://secretnotes.space/articleimage?id=60 ![Image Name 1]