No description
  • Zig 94.7%
  • Shell 3%
  • TLA 1.5%
  • Roff 0.4%
  • Makefile 0.2%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-09 18:25:10 +02:00
ci docs(ci): define signed package repository flow 2026-09-02 20:53:24 +02:00
corpus Add fuzz testing and automated lint checks 2026-04-01 19:12:43 +02:00
docker Add Docker / Linux build, run, and test harness 2026-06-12 22:36:32 +02:00
examples docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
packaging release: v0.1.10 — authoritative systemd service (zdns@.service) 2026-07-01 23:10:55 +02:00
src style: apply Zig formatting 2026-09-05 20:13:04 +02:00
test style: apply Zig formatting 2026-09-05 20:13:04 +02:00
tla docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
.dockerignore chore: remove orphan files not wired into build.zig 2026-07-17 18:55:15 +02:00
.gitattributes fix(cicd): auto-resolve CHANGELOG land conflicts via .gitattributes merge=union 2026-07-05 12:14:44 +00:00
.gitignore chore: remove orphan files not wired into build.zig 2026-07-17 18:55:15 +02:00
bench-network.sh Add SQLite and PostgreSQL backends with Docker testing 2026-04-06 17:23:12 +02:00
bench_backends.zig docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
bench_control_socket.zig Port to Zig 0.16 2026-06-12 20:27:49 +02:00
bench_dns_comparison.sh Rename zig-dns references to zdns 2026-06-12 15:45:46 +02:00
bench_dnssec.zig Port to Zig 0.16 2026-06-12 20:27:49 +02:00
bench_generic.zig docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
bench_metrics_http.zig Port to Zig 0.16 2026-06-12 20:27:49 +02:00
bench_pdns_comparison.sh Rename zig-dns references to zdns 2026-06-12 15:45:46 +02:00
bench_recursor_comparison.sh Rename zig-dns references to zdns 2026-06-12 15:45:46 +02:00
bench_reload.zig Port to Zig 0.16 2026-06-12 20:27:49 +02:00
bench_results_summary.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
bench_socket_comparison.zig docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
bench_zone_load.zig Port to Zig 0.16 2026-06-12 20:27:49 +02:00
benchmark_test.zone Replace custom benchmark with industry-standard dnsperf 2026-04-06 19:48:22 +02:00
build.zig fix(build): link sqlite and libpq only when the backend needs them 2026-08-13 15:49:29 +00:00
build.zig.zon release: v0.1.12 (auto-release) 2026-07-21 09:05:22 +00:00
CHANGELOG.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
docker-compose.yml Add Docker / Linux build, run, and test harness 2026-06-12 22:36:32 +02:00
fuzz_authoritative.zone Split recursor into focused modules and harden test isolation 2026-06-12 15:39:38 +02:00
KNOT_DNS_BUG.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
lint.sh Rename zig-dns references to zdns 2026-06-12 15:45:46 +02:00
Makefile Add Docker / Linux build, run, and test harness 2026-06-12 22:36:32 +02:00
NOT_IMPLEMENTED.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
OPTIMIZATION_IDEAS.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
OPTIMIZATION_RESULTS.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
pdns-bench.conf Rename zig-dns references to zdns 2026-06-12 15:45:46 +02:00
pdns_bind.conf Rename zig-dns references to zdns 2026-06-12 15:45:46 +02:00
PERFORMANCE_ANALYSIS.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
README.md docs: explain native delivery and task telemetry 2026-09-09 18:25:10 +02:00
RECORD_TYPES.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
RECURSOR_BASELINE.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
RFC_COMPLIANCE.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
stress_test.zig docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
test-with-postgres.sh Rename zig-dns references to zdns 2026-06-12 15:45:46 +02:00
TEST_COVERAGE.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00
zdist.conf checkpoint: commit remaining zig-dns work 2026-04-04 09:38:02 +02:00
zdns.conf.example Rename zig-dns references to zdns 2026-06-12 15:45:46 +02:00
ZIG.md docs: rewrite documentation to ASD-STE100 2026-08-07 18:32:51 +02:00

zdns

zdns is the Zig PowerDNS port in this repository.

Binjovi Builds use ci/binjovi-package.sh as the one repository-owned package entry point. The fixed worker supplies a validated revision, version, mode, and output directory. A pull-request Build compiles and tests the source and creates one tar archive. After Binjovi integrates the selected Builds into trunk, the combined Release Build also creates a DEB package and an RPM package. A fixed task stages one signed private APT and DNF repository. Separate Debian and Alma tasks install through that repository. Their package-install containers receive no storage credential. After both tests pass, the Release copies the same package bytes and repository metadata to an immutable public release snapshot, adds the production signatures, and verifies the public files.

Goal:

  • Reach broader PowerDNS feature parity, not just authoritative-server parity.
  • Become a drop-in binary replacement for PowerDNS.
  • Use idiomatic Zig to reach this goal with better performance and maintainability.

The most mature supported subset today is the typed authoritative stack. It is built around DnsServer(BackendT) and MemoryBackend.

Current maturity: the authoritative path is mature. The recursor now has a supported UDP server binary with DNSSEC validation, control socket support, and HTTP metrics.

Status

Supported:

  • core DNS parsing and packet generation
  • authoritative serving with four backends:
    • MemoryBackend - in-memory HashMap (fastest, fully featured)
    • BindBackend - BIND zone files with binary index (read-only)
    • SqliteBackend - SQLite persistent storage (ACID, mutations supported)
    • PostgresBackend - PostgreSQL networked database
  • canonical serve handles authoritative UDP and TCP
  • zone load and reload on all backends
  • AXFR and IXFR client paths
  • typed AXFR, IXFR, and UPDATE server APIs
  • TSIG support in transfer and UPDATE codepaths
  • control socket and HTTP metrics services
  • compile-time backend selection (-Dbackend=memory|bind|sqlite|postgres)
  • authoritative DNS Cookies via --cookie-secret-hex
  • UDP ANY mitigation returns TC=1. TCP ANY still returns the full answer
  • control socket is local-user only by default, with 0600 pathname permissions

Example server config:

[server]
listen=127.0.0.1:5354
tcp_timeout_ms=5000
tcp_max_connections=100
tcp_max_queries_per_connection=1000
tcp_max_message_size=65535
control_socket_path=/tmp/zdns.sock
control_socket_mode=0600
control_socket_read_timeout_ms=1000
control_socket_write_timeout_ms=1000
control_socket_max_connections=10

You must set control_socket_path to enable the control socket. The mode, timeout, and connection keys above take effect only after you set it. The parser fails closed on unknown keys.

Incomplete or unsupported:

  • BindBackend mutations: read-only. It has no addRecord or deleteRecord support.
  • UPDATE is not transactional.
  • The recursor is UDP-only for now. TCP query serving is still not supported.
  • Benchmark claims for the recursor are not yet published here.
  • Full operational and end-to-end coverage is missing.
  • Benchmark accuracy is not yet verified.
  • Recursor operator commands are PING, STATS, and SHUTDOWN. STATS returns a raw JSON body after the OK line.
  • Recursor HTTP monitoring exposes /metrics, /stats, and /health. /zones returns 501 Not Implemented on the recursor.

Canonical status and roadmap: ./ZIG.md.

Formal models for the authoritative path live in tla/auth/. The listening-server shutdown handshake is modeled in tla/server/. This handshake models the macOS accept-join deadlock that the Zig 0.16 port fixed.

Architecture

Prefer typed backends and typed service composition. Under Zig 0.16, every init takes a std.Io. In a binary, io and the allocator come from the process init struct in main:

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;
    const io = init.io;

    // Memory backend (HashMap-based, fastest)
    const Server = dns.server.DnsServer(dns.backends.memory.MemoryBackend);
    var backend = dns.backends.memory.MemoryBackend.init(allocator, io);
    var server = try Server.init(allocator, io, &backend, .{});
    defer server.deinit();
    // ... serve ...
}

To swap the backend, change the two backend lines. The Server.init call stays the same for all four backends:

// BIND backend (zone file with binary index, read-only; 100-entry LRU cache)
const Server = dns.server.DnsServer(dns.backends.bind.BindBackend);
var backend = dns.backends.bind.BindBackend.init(allocator, io, 100);

// SQLite backend (persistent storage, ACID transactions)
const Server = dns.server.DnsServer(dns.backends.sqlite.SqliteBackend);
var backend = try dns.backends.sqlite.SqliteBackend.init(allocator, io, "zones.db");

// PostgreSQL backend (networked database)
const Server = dns.server.DnsServer(dns.backends.postgres.PostgresBackend);
var backend = try dns.backends.postgres.PostgresBackend.init(allocator, io, "host=localhost dbname=pdns");
  • treat the runtime-erased backend layer as compatibility glue, not the main path
  • use -Dbackend=memory|bind|sqlite|postgres to select backend at build time

Build And Test

On macOS, Zig may not find openssl/rsa.h. If that happens, set OPENSSL_PREFIX to the Homebrew OpenSSL install before you build:

export OPENSSL_PREFIX="$(brew --prefix openssl@3)"

The default zig build installs all binaries to zig-out/bin, including serve, serve_tcp, zdist, and recursor. There is no separate recursor step. Plain zig build produces zig-out/bin/recursor.

Binjovi validates pull requests and releases packages from trunk. The signed Debian and Alma-compatible repository indexes and the immutable release manifest are under https://pkg.sean.farm/zdns/. The package install tests run against both distribution families before Binjovi can publish a Release.

zig build
zig build -Dbackend=memory
zig build -Dbackend=bind
zig build -Dbackend=sqlite
zig build -Dbackend=postgres
zig build test
zig build test-integration
zig build test-integration-lb
zig build test-fuzz
zig build stress-test

Recursor unit tests run as part of zig build test, through src/recursor.zig. There is no dedicated recursor test step.

Docker / Linux

The host dev build and tests run natively. On this machine, that means macOS. docker/Dockerfile is a multi-stage build using Debian and Zig 0.16. It builds and runs zdns on Linux through a few make targets:

make docker-test    # build on Linux, run test + integration + integration-lb + fuzz
make docker-serve   # run `serve` as a DNS container on 5354 (UDP+TCP)
make docker-e2e     # compose: server + a dig client that asserts a real answer

make docker-test gives the most value. The integration suites bind real sockets. Running them on Linux exercises the platform branch of the accept-shutdown handshake that a macOS host never runs. On Linux, shutdown() wakes a blocked accept(). On macOS, it does not. See tla/server/ for details.

make docker-serve then dig +short @127.0.0.1 -p 5354 www.example.com A returns 93.184.216.34 from test/example.com.zone. The server image uses the memory backend. Postgres-backend tests skip unless wired up. make docker-test-pg runs the suite with PGCONNECT pointed at the compose Postgres so they execute. The zdns and e2e compose services live behind the e2e profile, so ./test-with-postgres.sh is unaffected. That script uses only the postgres service.

Running Integration Tests

The easiest way to run PostgreSQL integration tests:

# Run all tests with PostgreSQL (starts container, runs tests, cleans up)
./test-with-postgres.sh

# Or keep container running for repeated test runs
./test-with-postgres.sh --keep
zig build test  # Run again without restart
docker-compose down  # When done

PostgreSQL Tests with Local Install

If you have PostgreSQL installed locally:

# Create test database
createdb pdns_test
psql pdns_test -c "CREATE USER test WITH PASSWORD 'test';"
psql pdns_test -c "GRANT ALL PRIVILEGES ON DATABASE pdns_test TO test;"
psql pdns_test -c "GRANT CREATE ON SCHEMA public TO test;"

# Run tests
export PGCONNECT="host=localhost port=5432 dbname=pdns_test user=test password=test"
zig build test

Manual Docker Container Management

# Start PostgreSQL container
docker-compose up -d postgres

# Run tests
export PGCONNECT="host=localhost port=5432 dbname=pdns_test user=test password=test"
zig build test

# Stop PostgreSQL container
docker-compose down

PostgreSQL Backend for Server

To run the server with the PostgreSQL backend, build with -Dbackend=postgres and provide the connection string in PGCONNECT:

# Install PostgreSQL
brew install postgresql@16  # macOS
# or
sudo apt-get install postgresql libpq-dev  # Ubuntu

# Create production database
createdb powerdns
psql powerdns -c "CREATE USER pdns WITH PASSWORD 'secret';"
psql powerdns -c "GRANT ALL PRIVILEGES ON DATABASE powerdns TO pdns;"

# Build with the postgres backend and run
zig build -Dbackend=postgres
export PGCONNECT="host=localhost port=5432 dbname=powerdns user=pdns password=secret"
./zig-out/bin/serve test/example.com.zone example.com.

serve reads PGCONNECT from the environment. If it is unset, serve falls back to host=localhost port=5432 dbname=powerdns user=pdns.

Examples:

  • examples/serve.zig - canonical UDP and TCP authoritative server
  • examples/serve_tcp.zig - TCP-only variant
  • examples/zdist.zig - DNS load balancer

DNS Cookies

Set a cookie secret to enable stateless authoritative DNS Cookies:

./zig-out/bin/serve test/example.com.zone example.com. --cookie-secret-hex=0123456789abcdef

You can also set server.cookie_secret_hex in a config file. The DNS_COOKIE_SECRET_HEX env var is used only when a config file is loaded with --config or -c. It is not read for a standalone invocation.

Benchmarks

Runnable commands:

zig build bench-dnssec
zig build bench-generic
zig build bench-socket
zig build bench-zone-load
zig build bench-reload
zig build bench-control-socket
zig build bench-metrics-http
zig build bench-backends

Treat benchmark conclusions as unverified unless you rerun them yourself.

Documentation

  • ./ZIG.md: canonical status and roadmap

License

See the repository license.

Native pipeline timing

Native tasks build the packages and run the Debian and Alma install checks. The pipeline ends at the verified package Release. This project has no configured Deploy target.

Open the project dashboard and expand an execution attempt for the task timeline and available CPU, off-CPU, and syscall profiles. Cached BuildKit steps still appear as cached work; a missing sample does not prove that a task used no CPU.

Select an exact successful Build with binjovictl release zdns --build BUILD_ID --wait. Then use binjovictl status zdns to check its release and any configured deployment. Telemetry explains time spent; immutable artifact and revision evidence establish completion.