Massively scalable authoritative DNS server
I needed a DNS server to make it easy to deploy stuff in Kubernetes with my own tool, k8deployer. The tool will generate all the required TLS certificates with Let's Encrypt and configure the ingress controller in Kubernetes automatically. This applies even for zones on local networks in the 192.168.. IP address space. That means that my tool need fast and reliable access to a DNS server so it can use Let's Encrypt DNS option for validating ownership of the domains.
I also needed something fun to work with after I ran into a severe work burn-out in 2022 that almost killed me. A DNS server was a perfect project for me to take on to re-gain my full energy, health and enthusiasm.
When I worked with a startup in 2022, we frequently bumped in to problems with the commercial DNS server provider the company was using (and paying a lot of cash for every month). When we exceeded 1 million host-names (fqdn's) we had reached our limit, and our tests failed. The number of production host-names, and host-names provided for QA and individual teams and developers were just enormous. Prior to this, I never even thought of a use-case for a DNS zone with more than a few hundred names.
When I worked with DNS servers in the past (yea, I was kind of a part time sysadmin back in the days) I always ended up writing my own user interfaces to simplify my work. I would use a DNS server that supported a SQL database back-end and then wrote a web-app or real application to manage the zones in the database server.
So, when I started on nsblast, I had a few requirements:
- Nsblast's primary role is to be the perfect Authoritative DNS server for me.
- It must be secure and reliable, following best practices and modern engineering principles.
- It must be very easy to install, manage and use (because I'm lazy and like things that are simple, yet powerful).
- It must have a REST API that is intuitive and easy to use for developers. I choose REST over for example gRPC because it's very simple to use directly for all kinds of use-cases, including bash scripts.
- It must support SaaS use-cases, where a user (for example "Alice") can sign up and create her own logical sub-zone (like alice.k8deployer.nsblast.com if k8deployer.nsblast.com is an existing zone for SaaS users). Then, Alice can manager a number of Resource Records in her sub-zone (or rather, k8deployer could do that for her and also create and deploy Let's Encrypt certificates for those host-names, giving her the opportunity to focus 100% on whatever she is making or experimenting with in the Kubernetes cluster).
- It must be massively scalable. A server should be able to handle millions of real zones and tens of millions of Resource Records for each zone.
- It must handle a huge number of DNS and API request per second on each instance.
- It must be CPU, memory and energy-efficient. Cloud infrastructure is expensive. Ideally, it should run comfortably on a raspberry PI 3 for most real use-cases (developers, developer-teams or small or medium companies and organizations running their own DNS servers for fun or to save costs).
Under initial development.
Public beta expected soon.
- RFC 1034 DOMAIN NAMES - CONCEPTS AND FACILITIES
- RFC 1035 DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
- RFC 1123 Requirements for Internet Hosts -- Application and Support
- RFC 1183 New DNS RR Definitions (Rr, Asfdb)
- RFC 1995 Incremental Zone Transfer in DNS
- RFC 1996 A Mechanism for Prompt Notification of Zone Changes (DNS NOTIFY)
- RFC 2181 Clarifications to the DNS Specification
- RFC 2782 A DNS RR for specifying the location of services (DNS SRV)
- RFC 3596 DNS Extensions to Support IP Version 6
- RFC 3597 Handling of Unknown DNS Resource Record (RR) Types
- RFC 4701 A DNS Resource Record (RR) for Encoding Dynamic Host Configuration Protocol (DHCP) Information (DHCID RR)
- RFC 5936 DNS Zone Transfer Protocol (AXFR)
- RFC 6891 Extension Mechanisms for DNS (EDNS(0))
- RFC 7766 DNS Transport over TCP - Implementation Requirements
- RFC 7929 DNS-Based Authentication of Named Entities (DANE) Bindings for OpenPGP
- RFC 8482 Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY
note: Things in old RFC's that has later been obsoleted are ignored.
- Standards compliant, authoritative DNS server
- Can ast as primary DNS server in a group of standard DNS servers
- Can act as a follower DNS server in a group of standard DNS servers
- HTTP REST API
- Optional swagger endpoint to document/experiment with the API
- RBAC (Role Based Access Control)
- Rust operator CLIs
nsblast-dynipfor DynIP capability-token updatesnsblastctlfor tenant, zone, RR, import/export, and basic admin workflows
- Support SaaS use cases with individual users grouped under organizations/tenants.
- Simple Cluster mode
- Allows a cluster of Nsblast server to work as a typical database cluster with one primary server for all writes, and any number of follower servers for reads.
- Replication on the database layer via fast/memory efficient gRPC streams (no separate streaming components like Kafka or Pulsar).
- In this mode, users of the REST API can wait until a change is propagated to all the servers before the request returns. Useful for example for letsencrypt wildchar certs or certs for fqdn's that resolve to private IP ranges.
- Uses RocksDB as it's internal database engine. This is one of the most battle-tested and fastest database engines available.
- Backup and restore at the database layer.
- Incremental backups
- Backups can be created, deleted, listed and verified from the REST API
- Backups can be listed, verified and restored from the command-line. The server must be off-line in order to perform a restore.
- Backups can easily be copied to/from remote storage via rsync or for example restic.
The project use CMake.
It uses C++ 20 features and require g++-13 / clang-15 or newer.
It requires boost version 1.85 or newer by default.
Operator CLIs are built from the Rust workspace in cli/:
cd cli
cargo build -p nsblastctl
cargo build -p dynip-clientBook documentation for the operator CLI is in nsblast-book/src/devops_cli.md.
Other dependencies that are handled automatically by CMake:
- logfault: For logging
- yahat-cpp: Embedded HTTP server for the REST API interface
- rocksdb: Database-engine
Required by default CMake options (NSBLAST_CLUSTER=ON, NSBLAST_WITH_SWAGGER=ON):
sudo apt update
sudo apt install -y \
build-essential cmake git pkg-config \
libboost-all-dev \
libssl-dev zlib1g-dev libbz2-dev liblz4-dev libsnappy-dev librocksdb-dev \
libprotobuf-dev protobuf-compiler \
libgrpc-dev libgrpc++-dev protobuf-compiler-grpcsudo dnf install -y \
gcc-c++ cmake git pkgconf-pkg-config make \
boost-devel \
openssl-devel zlib-devel bzip2-devel lz4-devel snappy-devel rocksdb-devel \
protobuf-devel protobuf-compiler \
grpc-devel grpc-pluginssudo pacman -S --needed \
base-devel cmake git pkgconf \
boost \
openssl zlib bzip2 lz4 snappy rocksdb \
protobuf grpcOptional features:
- Tests (
NSBLAST_WITH_TESTS=ON):libgtest-dev(Debian/Ubuntu),gtest-devel(Fedora),gtest(Arch) - Docs (
NSBLAST_WITH_DOCS=ON):doxygen - UI (
NSBLAST_WITH_UI=ON):npm/nodejs - UI local HTTP login override (
NSBLAST_UI_ALLOW_HTTP_LOGIN=ON): disable HTTPS-only login checks in the built UI (local testing only, defaultOFF)
Example on building the application (with custom built boost-library in /opt):
cd nsblast
mkdir build
cd build
cmake -DBOOST_ROOT=/opt/boost/boost_1_85_0 ..
make -j `nproc`
LD_LIBRARY_PATH=/opt/boost/boost_1_85_0/stage/lib/ ctest
LD_LIBRARY_PATH=/opt/boost/boost_1_85_0/stage/lib/ ./bin/nsblast --helpBuilding:
./build-docker-image.shOfficial image
The official image is published by GitHub Actions to GitHub Container Registry:
ghcr.io/jgaa/nsblast
The image does not declare Docker VOLUMEs on purpose.
For nsblast, explicit runtime mounts are the better operational model:
- mount the database directory explicitly so the data location is obvious
- mount TLS material explicitly so certificate lifecycle stays under operator control
- mount backup directories explicitly if you want them persisted outside the container filesystem
Important container paths:
/var/lib/nsblastDefault location for database state and generatedpassword.txt/etc/nsblastGood place to mount config files and TLS material
The image runs as UID 999, so mounted directories and files must be readable
or writable by that user as required.
Recommended practices:
- use a bind mount or named volume for
/var/lib/nsblast - mount certs read-only when possible
- keep private keys outside the image and inject them at runtime
- be explicit about
--db-pathand any backup path you want persisted
Starting: The example uses explicit mounts for state and certs. Substitute paths and IPs for your environment.
mkdir -p ./nsblast-data ./nsblast-certs
docker run --name nsblast --rm -it \
-v "$(pwd)/nsblast-data:/var/lib/nsblast" \
-v "$(pwd)/nsblast-certs:/etc/nsblast:ro" \
-p 172.17.0.1:53:5353/udp \
-p 172.17.0.1:53:5353/tcp \
-p 172.17.0.1:80:8080/tcp \
ghcr.io/jgaa/nsblast \
--db-path /var/lib/nsblast \
--dns-udp-port 5353 \
--dns-tcp-port 5353 \
--http-port 8080 \
--dns-endpoint 0.0.0.0 \
--http-endpoint 0.0.0.0Bootstrap a brand new database explicitly before normal startup:
docker run --name nsblast-bootstrap --rm -it \
-e NSBLAST_ADMIN_PASSWORD='change-me' \
-v "$(pwd)/nsblast-data:/var/lib/nsblast" \
ghcr.io/jgaa/nsblast \
--db-path /var/lib/nsblast \
bootstrap \
--cluster-role none