Skip to content

Golang-based DNS server which maps DNS records with embedded IP addresses to those addresses.

License

Notifications You must be signed in to change notification settings

cunnie/sslip.io

Repository files navigation

sslip.io

Test Type Status
Production Nameservers ci.nono.io
DNS Server Unit Tests ci.nono.io

sslip.io is a DNS server that maps specially-crafted DNS A records to IP addresses (e.g. "127-0-0-1.sslip.io" maps to 127.0.0.1). It is similar to, and inspired by, xip.io.

If you'd like to use sslip.io as a service, refer to the website (sslip.io) for more information. This README targets developers; the website targets users.

Quick Start

git clone https://github.com/cunnie/sslip.io.git
cd sslip.io
go mod tidy
sudo go run main.go
 # sudo is required on Linux, but not on macOS, to bind to privileged port 53

In another window:

dig @localhost 192.168.0.1.sslip.io +short
 # should return "192.168.0.1"

Quick Start Tests

go mod tidy
go generate
ginkgo -r -p .

Running Your Own Nameservers

We can customize our nameserver and address records (NS, A, and AAAA), which can be particularly useful in an internetless (air-gapped) environment. This can be done with a combination of the -nameservers flag and the -addresses flag.

For example, let's say we're the DNS admin for pivotal.io, and we'd like to have a subdomain, "xip.pivotal.io", that does sslip.io-style lookups (e.g. "127.0.0.1.xip.pivotal.io" would resolve to "127.0.0.1"). Let's say we have two servers that we've set aside for this purpose:

  • ns-sslip-0.pivotal.io, 10.8.8.8 (IPv4)
  • ns-sslip-1.pivotal.io, fc88:: (IPv6)

First, we delegate the subdomain "xip.pivotal.io" to our two nameservers, and then we run the following command run on each of the two servers:

# after we've cloned our repo & cd'ed into it
go run main.go \
  -nameservers=ns-sslip-0.pivotal.io,ns-sslip-1.pivotal.io \
  -addresses ns-sslip-0.pivotal.io=10.8.8.8,ns-sslip-1.pivotal.io=fc88::

Note: These nameservers are not general-purpose nameservers; for example, they won't look up google.com. They are not recursive. Don't ever configure a machine to point to these nameservers.

Running with Docker

Probably the easiest way to run the nameserver is with the official Docker image, cunnie/sslip.io-dns-server:

docker run \
  -it \
  --rm \
  -p 53:53/udp \
  cunnie/sslip.io-dns-server

If we see the error, "Error starting userland proxy: listen udp4 0.0.0.0:53: bind: address already in use.", we turn off the systemd resolver: sudo systemctl stop systemd-resolved

Let's try a more complicated setup: we're on our workstation, jammy.nono.io, whose IP addresses are 10.9.9.114 and 2601:646:0100:69f0:0:ff:fe00:72. We'd like our workstation to be the DNS server:

docker run \
  -it \
  --rm \
  -p 53:53/udp \
  cunnie/sslip.io-dns-server \
    -nameservers jammy.nono.io \
    -addresses jammy.nono.io=10.9.9.114,jammy.nono.io=2601:646:100:69f0:0:ff:fe00:72

From another machine, we look up the DNS NS record for "127.0.0.1.com", and we see the expected reply:

dig ns 127.0.0.1.com @jammy.nono.io +short
...
  ;; ANSWER SECTION:
  127.0.0.1.com.		604800	IN	NS	jammy.nono.io.

  ;; ADDITIONAL SECTION:
  jammy.nono.io.		604800	IN	A	10.9.9.114
  jammy.nono.io.		604800	IN	AAAA	2601:646:100:69f0:0:ff:fe00:72

The Docker image is multi-platform, supporting both x86_64 architecture as well as ARM64 (AWS Graviton, Apple M1/M2).

Command-line Flags

  • -port overrides the default port, 53, which the server binds to. This can be especially useful when running as a non-privileged user, unable to bind to privileged ports (<1024) ("listen udp :53: bind: permission denied"). For example, to run the server on port 9553: go run main.go -port 9553. To query, dig @localhost 127.0.0.1.sslip.io -p 9553
  • -nameservers overrides the default NS records ns-azure.sslip.io, ns-gce.sslip.io, and ns-ovh.sslip.io; flag, e.g. go run main.go -nameservers ns1.example.com,ns2.example.com). If you're running your own nameservers, you probably want to set this. Don't forget to set address records for the new name servers with the -addresses flag (see below). Exception: _acme-challenge records are handled differently to accommodate the procurement of Let's Encrypt wildcard certificates; you can read more about that procedure here
  • -addresses overrides the default A/AAAA (IPv4/IPv6) address records. For example, here's how we set the IPv4 record & IPv6 record for our nameserver (in the -nameservers example above), ns1.example.com: -addresses ns1.example.com=10.8.8.8,ns1.example.com=fc::8888. Note that you can set many addresses for a single host, e.g. ns1.example.com=1.1.1.1,ns1.example.com=8.8.8.8,ns1.example.com=9.9.9.9
  • -blocklistURL overrides the default block list, (https://raw.githubusercontent.com/cunnie/sslip.io/main/etc/blocklist.txt). It's not necessary to override this if you're in an internetless environment: if the DNS server can't download the blocklist, it prints out a message and continues to serve DNS queries

DNS Server Miscellany

  • it binds to both UDP and TCP.
  • The SOA record is hard-coded except the MNAME (primary master name server) record, which is set to the queried hostname (e.g. dig big.apple.com @ns-ovh.nono.io would return an SOA with an MNAME record of big.apple.com.
  • The MX records are hard-coded to the queried hostname with a preference of 0, except sslip.io itself, which has custom MX records to enable email delivery to ProtonMail
  • There are no SRV records

Directory Structure

  • ci/ contains the Concourse continuous integration (CI) pipeline and task
  • spec/ contains the tests for the production nameservers. To run the tests locally:
    DOMAIN=sslip.io rspec --format documentation --color spec/
  • k8s/document_root_sslip.io/ contains the HTML content of the sslip.io website. Please run tidy -im -w 120 k8s/document_root_sslip.io/index.html before submitting pull requests

Acknowledgements

  • Sam Stephenson (xip.io), Roopinder Singh (nip.io), and the other DNS developers out there
  • The contributors (@normanr, @jpambrun come to mind) who improved sslip.io
  • Jenessa Petersen of Let's Encrypt who bumped the rate limits
  • JetBrains who provided a free license for open source development