Last year when I was looking for a new little side project, I remembered a number of blog posts about abusing existing standard network protocols to store data in a somewhat hidden fashion online. pingfs and DNSFS, the results of these attempts, fascinated me.

The idea of taking an established protocol, taking it apart and trying to find ways to use it beyond its intended purpose is really compelling. So with those examples in mind, I started to ask myself: Could I build something similar myself?

My starting point for this was the DNS protocol. I remembered an idea I had during a lecture covering the service: Couldn’t the protocol be used for relaying chat messages between two machines? In theory, DNS would allow you to transmit arbitrary data without even breaking the protocol. TXT records were devised to do exactly that: serve arbitrary data connected to a domain over DNS. Some of you may even have used this for things like Let’s Encrypt’s certificate verification challenge or even more arcane things like SPF for mail servers.

So I thought: Couldn’t the DNS protocol be used for relaying chat messages between two machines? And born was the idea for kakure (隠れ, jap. for concealed, hidden), a DNS chat client written in Rust and available on Github.

Alright, so how does DNS work?

Let’s dig out some basics first for thosw who aren’t quite sure (anymore) how DNS works. If you’re familiar with the basic mechanism, feel free to skip ahead to the next section!

The Domain Name System (or DNS for short) is basically the phone book of the internet1. It resolves those fancy-sounding, easy-to-remember URLs like fedidwgugl.de into the IP address of the server behind the name. This address is then contacted to request the web page located under this URL. You can use tools like nslookup or dig to manually retrieve and inspect DNS records, if you wish:

[felix@tycho] $ nslookup -type=AAAA dummyco.de 9.9.9.9
Server:		9.9.9.9
Address:	9.9.9.9#53

Non-authoritative answer:
dummyco.de	has AAAA address 2a00:d0c0:200::c0bf:e0ff:fec1:af72

As we can see, the IPv6 address of this blog (at the point of me writing this) is: 2a00:d0c0:200::c0bf:e0ff:fec1:af72.

So, when you have a server and want to publish a website, e.g. dummyco.de, you not only have to purchase the domain, you also need to create a couple of DNS records pointing potential visitors in the right direction. For that, you can have different record types: Very basic ones like Address records denoting the location of your server (as shown above), but also more sophisticated ones advertising a mail server for instance. There’s also a TXT record type, allowing you to store plain text information for everyone to see. Today it’s used for instance to verify ownership of a domain when requesting a SSL certificate from Let’s Encrypt – if you’re able to publish a specific DNS record, you seem to be the rightful owner of a domain.

The neat thing about the DNS protocol? It’s decentralized meaning there’s a huge hierarchical mesh of servers out there, serving records to anyone asking. One or two more servers talking DNS among each other will hardly raise any eyebrows2.

So, let’s come up with an idea!

I know that DNSFS uses open resolvers to store the data saved by the user. Those are publicly available servers that offer to take care of traversing the DNS hierarchy and resolving server names for you, even caching the result. If you want to know more about how that can be (ab-)used to store data in the resolver, I recommend reading the DNSFS blog post. Following up on that idea I would’ve been able to realize asynchronous conversations, putting their messages as TXT records into the resolver’s cache. Yet, storing the chat messages in a resolver seemed impractical for several reasons:

  1. All chats would effectively be message boards. Everyone could interject and without proper identity checks you’d be unable to tell who’s saying what. While that idea sounds definitely fun, I wanted to create a 1:1 chatting solution, not a hidden message board (though I must admit the idea sounds fun! Maybe I’ll recycle my code some day and do this). And ensuring privacy in such a scenario would require using encryption which I definitely didn’t want to dip my feet into at that time.
  2. If enough people would decide to chat with one another the resolver could theoretically exhaust its cache, meaning messages would get lost. Message loss could also occur if you don’t check your messages frequently enough.3

For these reasons, I decided to implement the chat as peer-to-peer architecture, which works like this:

Depiction of the flow described below

Both conversational partners are running a DNS server and a client. Any messages a participant wishes to send get buffered in the server. The client periodically sends a TXT request for some domain to the the server of the partner. The domain requested is irrelevant here. Thinking further, a frequently requested domain like duckduckgo.com might make sense, as it will not stand out in the larger stream of daily DNS queries. When the server receives a request, it empties its message buffer, sending all contained messages back to the client as TXT records. At the same time, the requests also double as a presence indicator to the other side, which can be useful to detect if the other side is still online and receiving.

Why this indirection? Well, since DNS is a client-server protocol, the server is usually the one sending out large data streams, but by definition cannot do so unprompted. So the periodic requests from the client on the other side act as a request for new messages.

Let’s get to the code!

I started by intently studying RFC 1035, which first proposed the DNS protocol and is still the authority in that regard (ignoring a few errata). Normally, you’d do DNS over UDP, because it’s generally faster and you don’t really care whether or not a packet gets lost here.4 But the issue is, that you are constrained in the size of your requests and responses when using UDP. The RFC states that an UDP message shall have “512 octets or less” meaning a payload limit of 512 Bytes. That’s hardly enough for a long, heartfelt conversation. And I kinda didn’t want messages to get lost in transit. So I turned to TCP as a transmission protocol, which is also supported.

After looking at a number of libraries for DNS-related data structures, I decided they offered too many features or not the specific features I needed and since I was to write the message handling and serialization on my own anyway I opted for just implementing a bare minimum. In hindsight that was probably a stupid idea, given that I ended up re-implementing all the data structures according to the RFC anyway, excluding the serialization.

After numerous hours of spamming a DNS server I control with junk and debugging the transmitted packets with Wireshark, I managed to get the library to work correctly. I’ll spare you the details, but essentially, the code below the UI takes the messages written by the user, timestamps and chops them into handy DNS response packets which get delivered to the other participant as soon as they request new messages.

For the UI, I – having absolutely no clue how to do anything UI related at all – implemented something very simple using the tui library, just so that the program actually becomes usable.

Does it work?

asciicast

Yes, it does! 🎉

Conclusion

The chat client I created has a very basic design and is more a proof-of-concept than anything else. While transmitting text works reliably well, images and other types of messages are not supported yet as that would require serializing that data and possibly also compressing it to save some space. The same goes for encryption, which is definitely something I’d like to have, if it weren’t such a pain to implement. So, sorry @kiliankoe, I know you said this is a checkbox for you, but you’re gonna have to implement this yourself. 😛

In the end, it was a fun little side project that was interesting to pursue and dig into. And that’s all that matters to me.


  1. Do people nowadays even remember that brick of a book lying around in every home until the late 2000s? Geez, I feel old now. ↩︎

  2. Except for when the place where you go online filters DNS response packets which would pose an interesting challenge. ↩︎

  3. Though that would make for a very sad and one-sided conversation. ↩︎

  4. Obligatory TCP/UDP meme scnr ↩︎