06 July 2023

My very own DNS server

I wrote my own DNS server in Python and tested it for some time. Here are some details about this project.

Motivation

I was reading about the new “scriptable DNS” feature of BunnyCDN, the CDN that I was using at the time. I was familiar with some fancy DNS stuff you can do with Amazon AWS’s Route 53 DNS service, and now there is another DNS service that lets you run code for every DNS request in order to respond dynamically.

I had to get in on the fun, so I decided to write my own scriptable DNS name server. Aside from learning a lot about DNS, this project also ties in nicely to some future project plans about building my own CDN.

Implementation

RFC 1035 has all the information you need if you want to write your own DNS server or client. For this project I only wrote the server side, but in the future I also want to write my own DNS client.

In order to get DNS requests to my own server, I had to set up something called a Glue Record, and then enter the domain of the server I wanted to use for DNS as the name server. After this, I started receiving UDP packets to port 53 whenever I ran a dig command.

Deserializing the DNS header and query are rather simple using Python’s int.from_bytes method and following the RFC. Responding to queries is simplified because DNS queries and DNS responses follow the same format.

EDNS and EDNS client subset

I also implemented EDNS (RFC 6891). EDNS specifies how extensions to the DNS protocol should be handled.

The reason I looked at EDNS was to use the EDNS Client Subset extension (RFC 7871).

This extension passes a truncated prefix of the user IP to the DNS server if the user is using a public resolver such as Google’s 8.8.8.8. This can be useful for returing responses that are geographically closer to the user and improving performance.

Results

I ended up making my server respond to “normal” queries that resolve websites, along with some special behaviour like

  • time.example.com resolving to the current date and time.
  • random.example.com resolving to a random number.
  • Transmitting data over DNS by decoding hex or Base32 encoded subdomains.

In the future, I want to do stuff like automatically responding with a random IP from AWS spot instances so I can domains that resolve to a cheap and rotating fleet of ephemeral servers.

Maybe doing health-checks and removing servers that are down from DNS rotation would be good too.

Some findings

Google’s DNS resolver randomizes the letter case of domains. In DNS, domain names are case-insensitive. But I forgot this fact when I was writing my own DNS server. Everything seemed to work fine with other resolvers and clients, but Google’s DNS resolver randomizing it actually uncovered this bug for me.

I also noticed that after I implemented the EDNS client subset extension, Cloudflare still wouldn’t send anything. It turns out, they intentionally do not support this for “privacy reasons”. Some people suspect it’s because they want to prevent other CDNs from working optimally by routing users to servers that are closer to them while Cloudflare can do it for their customers because they know the real IP.

17 July 2023

Google Play Console really misses me

Back in 2017-2018, I got interested in Android app development. I read a couple tutorials, created a Google Play developer account, and published a few apps that I made while learning from the internet.

Soon after, I got busy with school work, and then work work, and I totally forgot that I even had a Google Play developer account.

I logged in to the Google Play Console today, and found that most of my apps could not be updated. Most of those apps were blocked from being updated because Google published some new policies in the years since I looked at the console, and I had to accept some new conditions or answer some new questions about my apps. In some cases, I had to point to a privacy policy URL.

Those changes are mostly approved now. For one of the apps, I need to push an update by adding a contact details page inside the app. For another app, I just need to bump up the minimum Android API version, recompile and push an update.

Overall, it’s not a very difficult process. But it showed me that there is still some ongoing maintenance cost to having your apps on the Google Play Store.

19 July 2023

GQRX scanner

GQRX has a handy “remote control” feature that allows you to connect to it over a TCP socket, and send commands to it.

Aside from changing the frequency, it also allows you to read the current signal strength on the tuned frequency.

This combination of features allows you to write a simple scanner that will scan a range of frequencies, and turn on the squelch when it detects a signal.

24 July 2023

In LeoSearch, I keep the URL, title, manually entered tags and a small description of the pages I index. Results are found with an inverted index and ranked using the Okapi BM25 ranking function. I implemented both of those in ClickHouse when I wrote the initial prototype of LeoSearch.

This works really well for doing keyword matches and ranking them. I am really satisfied with the result quality, it always seems to find what I want.

This technique only takes advantage of the user input (in the form of the search query), and the information from the results that have keyword matches.

26 July 2023

VPN server and client in Python

I wrote my own VPN with Python using the TUN/TAP interface. I also wrote my own VPN server using Python and its asyncio library.

29 July 2023

Non-blocking ChatGPT UI

I have a small Qt application written in Python that I use to access ChatGPT. It’s an app that has a built-in prompt database and editor, and has keyboard shortcuts that make it convenient for me to use.

The reason for writing this app was because it was getting annoying to log in to ChatGPT every time I wanted to use it, and the laggy ChatGPT UI left a lot to be desired. I wanted something that was fast and responsive, and made in a way that was designed to be useful for me and not designed for the lowest common denominator.

I’ve used it for some time now, and it’s been working well. However, there’s a tiny problem that has been bugging me for a while: the UI blocks whenever I make an API call to ChatGPT. This is because the API call is made in the main thread, and the UI is blocked until the API call returns.

Even worse; if the API call fails, the whole app crashes. I’ve lost some content because of this, and it’s not a good feeling. I decided that after not touching the code since I wrote it, it was time to go in once again and fix this problem.

The solution was to make the API call into a QThread. Instead of calling a method directly, you just call it from your QThread’s run() method, and send the result back to the main thread using a pyqtSignal. Here is the code I ended up using.

class ChatGptThread(QThread):
    gotResponse = pyqtSignal(str)

    def __init__(self, parent: QObject):
        super().__init__(parent)

    def run(self):
        response = chatgpt_response()
        self.gotResponse.emit(response)


def ask_question():
    t = ChatGptThread(app)

    def cb(resp):
        add_message(ChatMessage("assistant", resp))
        add_message(ChatMessage("user", ""))

    t.gotResponse.connect(cb)
    t.finished.connect(t.deleteLater)
    t.start()

Now the UI is no longer blocked when I make an API call, and I am happy.