Introduction

I’ve been looking to try out blockchain for quite some time, but struggled to come up with a good idea, however I found it while reading this blog post back in December. Lo and behold, here comes the inspirational quote:

Personally I think even DNS will be disrupted in the future 
by some kind of distributed ledger technology beyond even blockchain,
like hyperledger or hashgraph or some post-singularity quantum foam.

I am no post-singularity quantum foam expert, but I figured it would be able to write a PoC DNS server that uses blockchain as a DNS records repo.

DNS is distributed in a hierarchical way, with downstream servers caching DNS records managed by upstream servers for a specified time (TTL - time to live). On the other hand, blockhain is distributed in a peer to peer fashion. In other words, using blockchain to distribute DNS records is not such an outlandish idea, it makes sense to use blockchain to distribute DNS records as they get added or modified. In some ways blockchain makes things even simpler, for example by eliminating the need for TTL.

In the remainder of the post I’ll give an overview of the PoC I’ve written, though I am leaving it to you to learn about the little details by checking out the code.

P.S. I’ve used golang for the first time to write all this, hence I am still a golang n00b, so be gentle when you read the code.

Overview

The solution is organized into three parts; the Ethereum smart contract, DNS server and a simple CLI for adding/chaging DNS records. On top of that, the DNS server needs to connect to a Ethereum node, so there is that too. You might ask why Ethereum? Because it was the easiest option, but that is not to say it is the best option. A custom blockchain would make more sense, however in the interest of keeping things simple I went with Ethereum.

Custom blockhain is outside of the scope of this blog post, nevertheless, here is a link for those wanting to dig deeper into this subject: Code your own blockchain in less than 200 lines of Go!

Ethereum node

Both the CLI and DNS Server need to connect to an Ethereum node, so I am running one locally. Since this is just a PoC I am using Rinkeby test network and to keep things light in terms of downloaded data I decided to run it in light mode. It is a bit slower to process transactions, but saves disk space.

Ethereum smart contract

In Ethereum, one uses Solidity to define a smart contract. The contract I used looks like this:

pragma solidity 0.6.3;

contract InetDnsRecord {
    mapping (string => mapping(uint16 => string)) internal _dnsMapping;

    function addRecord(string memory key, uint16 recType, string memory recValue) public {
        _dnsMapping[key][recType] = recValue;
    }

    function getRecord(string memory key, uint16 recType) public view returns (string memory){        
        return _dnsMapping[key][recType];
    }
}

As you can see it essentially defines a dictionary where records are stored and two methods to set and get records. A new transaction is created each time a record is set and the network status is updated. Then, with small delay, all nodes in the network receive the latest blockchain status and to borrow “DNS speak” you could say that record was propagated. Note that the contract above is a bit too simplistic, but it is enough to suport A, AAAA and CNAME records, which are the only records I decided to support in my DNS server.

P.S. For those wanting to play with smart contracts, head over to Remix, experiment with your own contracts, deploy them to test network and try them out.

Registrar CLI

The second part of the solution is a simple CLI that permits you to set DNS records. There is no complex logic here, you enter the record info and the CLI then calls the addRecord method on the contract, generating a new transaction in the process.

If you were to run the CLI from the Docker container you can build from my source then adding a record would look a bit like this. Run:

registrar-client

And you will be presented with a fancy menu:

Pick an option:
1. Set record.
2. Show record.
3. Exit.
4. Reset and exit.

Let’s set a record by typing in the following into the CLI:

1
Type in the record type
A
Type in the record name
ayls.dev.
Type in the record value
1.2.3.4
Record set! Please wait for tx 0xe6c9d58eddee24e82cabacf697e3fefa0b87735cc47c5ebeaee19361c11a8269 to be confirmed.

A side note; you may wonder why the extra . in ayls.dev.. DNS registrars hide this from you, but when requesting a DNS record the dot is always in DNS query request, so we are setting it to keep things running properly.

After a few seconds you can verify that the record is there, in the CLI type the following:

2
Type in the record type
A
Type in the record name
ayls.dev.
Value: 1.2.3.4

As you can see we got back the value we entered before!

DNS Server

The server listens for UDP connection on port 53, parses the incoming message, queries the contract for the requested value and sends a response back to the caller. Again, the best way to do so is by building a Docker image and spinning it up. Follow the steps in the GitHub repo to build and run the container.

Once the container is up and running you will have to wait a few minutes as it syncs. The container is setup to use my DNS, hence it is an easy way to test things.

In the previous section we set an A record for ayls.dev. Now we can check if the DNS server knows anything about ayls.dev. Let’s run this from within container:

dig ayls.dev

It gives us a result like this:

;; Warning: query response not set

; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> ayls.dev
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8290
;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 317ca66319783675 (echoed)
;; QUESTION SECTION:
;ayls.dev.                      IN      A

;; ANSWER SECTION:
ayls.dev.               0       IN      A       1.2.3.4

;; Query time: 423 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Mar 21 19:06:47 UTC 2020
;; MSG SIZE  rcvd: 65

A more interesting example would be to add the records for an existing site, for example, example.com. Add these two records:

  • A record for example.com. with value 93.184.216.34
  • AAAA record for example.com. with value 2606:2800:220:1:248:1893:25c8:1946

Now run wget example.com and check if the downloaded index.html is what you expect it to be.

P.S. The Docker container exposes port 53, so you could use it as a simple DNS server if you feel like it.

Summary

There you go, a DNS server running. Not a very useful one, but still :)