April 13, 2022
So after 6 months into this new software engineering job, I finally managed to balance my work with my schedule. As a result, I have had much more free (and high quality) time.
One of the things I usually do in these available moments is to distract myself from work by working on my own code. This greatly helps me gain coding and designing experience.
And last month, I began working on my new Go project - an attempt to imitate stubby (I have been using stubby
since like 2019 or 2020), but this time with caching!
stubborn is a caching DNS stub resolver, with only DoT or DoH outgoing traffic. This is done to protect user privacy and for fun. It uses in-memory key-value cache, and can read UNIX-style /etc/hosts
file or a proprietary JSON hosts file for local network lookup table.
Assuming that you already have Go installed on your system, you can just use go install
to install stubborn
to your bin
directory:
go install github.com/soyart/stubborn/cmd/stubborn@latest
The above command should install stubborn
executable in $HOME/go/bin
, and pulls any build time depedencies down with it. If running stubborn
is what you want, the command above should be enough. Just be sure to add the bin
directory to your shell $PATH
.
If you only want the source code, use go get
:
go get github.com/soyart/stubborn/cmd/stubborn@latest
After you have saw and finished cursing stubborn
source code, you can build it with:
cd /tmp/stubborn # cd to your source root
go build ./cmd/stubborn
The above command will produce stubborn
binary in your working directory.
stubborn
supports only 2 outbound protocols - DoT and DoH. To run stubborn with either, use -c
flag (case-insensitive):
stubborn -c dot # Will spawn DoT client
stubborn -c doh # Will spawn DoH client
Short answer:
stubborn
, like the other 102% of all my projects, is written in Go, which is a statically compiled language.
With static linking by default in Go, imported code gets compiled into one big chunk of binary plus some Go runtime code (e.g. the GC or garbage compiled). You can see the list of Go modules used in this project in its go.mod
file. I swear I tried to minimize the libraries used.
Static linking makes Go applications appear to have much larger disk footprint than a C program. However, although stubborn is ~10MB when built, it is actually much smaller than some other C-based DNS resolvers that uses a lot of dynamic linking.
And because by default nothing is dynamically linked in Go, you can run this application without having to worry about the depedencies.
Well, it started out small, but I decided to restructure it according to uncle Bob’s clean architecture. stubborn
is also very easy to implement new features or new infrastructure, just like my other project todong which supports 2 data store types, and 4 web frameworks! All configurable with just a line in the config file.
This view is in stark contrast to my previously held view that software should be suckless, i.e. being minimalists and focus on peak efficiency. Now I don’t enjoy reading hacky code that’s fast but configurable. Today I enjoy code than can be maintained and picked up by others easily. And extensibility (which means proper isolation) is now one of my top priorities.
I intend to replace stubby with stubborn in some of my machines, so I decided to prefix the configuration path at /etc/stubborn
. This makes my life easier when configuring my spaghetti server services.
There are 2 files - (1) /etc/stubborn/config.yaml
and (2) /etc/stubborn/table.json
. These default locations can be changed in /cmd/etc.go
.
config.yaml
configures stubborn behaviors, and table.json
supplies stubborn with a key-value table to be used as local network domain lookup table.
If stubborn is in your path, just run $ stubborn
. If $GOPATH/bin
is not in your $PATH
, you can cd
to $GOPATH/bin
and just run ./stubborn
.
Either way, this is stupid. Who the fuck would launch this command every time a system comes up? That’s because as of this writing, I have not packaged stubborn as service yet. It’s just a Go program now, though in the future I plan to include a systemd unit file for stubborn.
So now, just bear with it and run it in the old-fashioned way.
I’ll admit it - the first geeky thing that drew me to tech was setting up my own DNS resolvers as ads blockers. During 2019-2020, I had been crazy with setting up my own DNS servers everywhere.
On most of my Linux computers, I usually have three (yes, 3) DNS programs running to meet my goals, which is predominantly ads blocking (and some privacy concerns).
Before stubborn
, my DNS server setup usually looks like this:
dnsmasq[:53] -> pihole[:5369] -> stubby[:6953] -> 1.1.1.1
<listens> <blocks ads> <outgoing> <upstream>
So I use the caching resolver dnsmasq
on the standard port 53 as the listener. This is where my other client computers ask and get replies from.
From there, dnsmasq
in turn asks pihole
, which acts as a blackhole for shitty domain names. pihole
also has caching feature, because pihole
is actually dnsmasq
+ ad blocking + web UI (if you installed it).
You can actually have pihole
asks the upstreams for answers, but unfortunately, pihole
did NOT support encrypted outbound queries at the time when I was a DNS simp, which is a big no-no for me. Here comes stubby - a non-caching privacy-first DNS resovler with DNSSEC support. I’ve been very happy with stubby
, so I just gave up on having encrypted outbound traffic from pihole
.
People usually say that they can just use systemd-resolved
or some NetworkManager plugins for this to work, but I really hate working with those Linuxy software from RedHat. These more integrated Linux tools are actually very difficult to wrap your head around, and I feel like they are highly coupled. Using dnsmasq
as NetworkManager’s resolver requires you to edit a lot of config files and dig deep into each component, and the worst thing is they fuck with /etc/hosts
or /etc/resolv.conf
, which usually requires you to install stupid packages like systemd-resolvconf
or openresolv
just for managing these files.
This is why I prefer running these 3 separate simple DNS resolvers. All you need to do is configure the listen addresses and the upstream addresses for each program, and boom, they just work together perfectly.
In other words, I like to fuck with DNS, and that’s why I wanted to try writing my own shitty version of stubby
. I’m testing stubborn
on some of my home servers now, and so far it worked great I did not feel any differences compared to using stubby
.