Kyle Harding's Docker image and Oznu's zone list are great, and a wealth of information when you scan through their Dockerfile, and javascript. I'll bet it inspires your own ideas.
My first thought was to port the javascript to Golang. I liked the template usage and knew Golang supports templates. Also, having 3 output formats made me wonder whether I could make each into a transform step of a pipeline.
Let's focus on the templates. Right away, we learn that there are two ways to designate a domain in our sinkhole:
redirect
- the domain is mapped to a local IP address. This can give
you the option to spin up a web server to respond with a pixel or
your own content in place of the ad. The IP 0.0.0.0
can be chosen
when running without the web server.nxdomain
- the domain is nonexistent. This is the response to signal
clients that the domain is not defined.This explains why for each of the three formats (BIND / Unbound / Dnsmasq), there are always two …templates. For Unbound, the documentation shows local-zone examples that use a dot terminator. So the port from javascript to Golang becomes:
const (
unbtempl = `
local-zone: "{{.}}." redirect
local-data: "{{.}}. A 0.0.0.0"`
unbnxdomain = `
local-zone: "{{.}}." always_nxdomain`
)
Next, the question is whether subdomains get folded under the parent. In other words, will blocking the parent automatically suppress its descendants?
According to the unbound.conf.5 man page, the local-zone definition already includes subdomains:
redirect The query is answered from the local data for the zone name. There may be no local data beneath the zone name. This answers queries for the zone, and all subdomains of the zone with the local data for the zone. It can be used to redirect a domain to return a different address record to the end user, with local-zone: "example.com." redirect and local-data: "example.com. A 127.0.0.1" queries for www.exam- ple.com and www.foo.example.com are redirected, so that users with web browsers cannot access sites with suffix exam- ple.com.
Which makes sense intuitively, and behavior we definitely want to take advantage of. Our approach is to track each domain inside a map.
Then the way we detect a subdomain is:
// calc parent domain
var i = strings.IndexRune(host, '.')
if i != -1 && i+1 < len(host) {
var parent = host[i+1:]
if _, ok := m[parent]; ok {
// The parent domain has already been recorded.
// We can skip this subdomain.
return ""
}
}
if _, ok := m[host]; !ok {
// This is the first instance of the host name.
m[host] = true
return host
}
Those are key highlights of the Golang port. Some implementation we skipped, like sorting and whitelisting. So we’ll revisit again in the future.
The whole port is available on the Github repo
Going into the Docker image, the first change is usually to switch base
to Alpine. On examining the
Dockerfile,
we see that it already uses
Alpine. So our real modification is to incorporate our Golang port
(redirect
zones) which configures Unbound into a sinkhole.
Wishlist:
redirect
zonesapk
redirect
zonesTo create the file of redirect
zones (unbound.bl), we compile our
Golang port and run it inside the container. Most of the work is done
for us because we start with the Golang base image.
RUN go build -o /go/bin/sinkhole cmd/*.go ; \
/go/bin/sinkhole ; \
Also, note that we separate the Dockerfile into two stages. The first
stage is build-env
dedicated to the preparation of the
redirect
zones. It's very convenient for doing tasks and discarding
unnecessary artifacts.
With Unbound, we choose the package manager (apk
) install. This is
the major difference from Kyle's Dockerfile. So it was a relief that
the apk
package works without any tinkering. I think it's also worth
pointing out that the package includes the root cache (roots.hint)
which saves a step in one of the guides we followed (recursive DNS).
# final stage
FROM alpine:3.11
RUN apk update && apk --no-cache add unbound ; \
rm -rf /apk /tmp/* /var/cache/apk/*
Learning from the work of others sparked questions. Answering questions
meant experiments with redirect
and nxdomain
configuration. Doing
experiments reminded us how Docker saves a lot of repitition.
Again the Github repo contains our Golang port, and Dockerfile. The CI is also configured to auto build on Docker hub:
docker run -ti -p 5300:5300/udp patterns/sinkhole