- Zig 95.2%
- Shell 2.6%
- TLA 1.5%
- Roff 0.3%
- Makefile 0.2%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| corpus | ||
| docker | ||
| examples | ||
| packaging | ||
| src | ||
| test | ||
| tla | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| bench | ||
| bench-network.sh | ||
| bench.zig | ||
| bench_authoritative_cache.zig | ||
| bench_backends.zig | ||
| bench_control_socket.zig | ||
| bench_dns_comparison.sh | ||
| bench_dnssec.zig | ||
| bench_generic.zig | ||
| bench_metrics_http.zig | ||
| bench_pdns_comparison.sh | ||
| bench_recursor_comparison.sh | ||
| bench_recursor_workers.zig | ||
| bench_reload.zig | ||
| bench_results_summary.md | ||
| bench_socket_comparison.zig | ||
| bench_zone_load.zig | ||
| benchmark_test.zone | ||
| build.zig | ||
| build.zig.zon | ||
| CHANGELOG.md | ||
| docker-compose.yml | ||
| fuzz_authoritative.zone | ||
| KNOT_DNS_BUG.md | ||
| lint.sh | ||
| Makefile | ||
| NOT_IMPLEMENTED.md | ||
| OPTIMIZATION_IDEAS.md | ||
| OPTIMIZATION_RESULTS.md | ||
| pdns-bench.conf | ||
| pdns_bind.conf | ||
| PERFORMANCE_ANALYSIS.md | ||
| README.md | ||
| RECORD_TYPES.md | ||
| RECURSOR_BASELINE.md | ||
| RFC_COMPLIANCE.md | ||
| stress_test.zig | ||
| test-with-postgres.sh | ||
| TEST_COVERAGE.md | ||
| zdist.conf | ||
| zdist.conf.example | ||
| zdns.conf.example | ||
| ZIG.md | ||
zdns
zdns is the Zig PowerDNS port in this repository.
Goal:
- reach broader PowerDNS feature parity, not just authoritative-server parity
- become a drop-in binary replacement for PowerDNS
- get there with better performance and maintainability through idiomatic Zig
The most mature supported subset today is the typed authoritative stack built
around DnsServer(BackendT) and MemoryBackend.
Current maturity: the authoritative path is mature, and 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
servehandles 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
ANYmitigation returnsTC=1; TCPANYstill returns the full answer - control socket is local-user only by default, with
0600pathname 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
control_socket_path must be set for the control socket to be enabled; the
mode/timeout/connection keys above only take effect once it is. The parser
fails closed on unknown keys.
Incomplete or unsupported:
- BindBackend mutations (read-only, no addRecord/deleteRecord support)
- UPDATE is not transactional
- 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
- verified benchmark accuracy
- recursor operator commands are
PING,STATS, andSHUTDOWN;STATSreturns a raw JSON body after the OK line - recursor HTTP monitoring exposes
/metrics,/stats, and/health;/zonesreturns501 Not Implementedon the recursor
Canonical status and roadmap: ./ZIG.md.
Formal models for the authoritative path live in tla/auth/; the
listening-server shutdown handshake (the macOS accept/join deadlock the Zig 0.16
port fixed) is modeled in tla/server/.
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 ...
}
Swap the backend by changing the two backend lines (the Server.init call is
identical for all four):
// 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|postgresto select backend at build time
Build And Test
On macOS, if Zig cannot find openssl/rsa.h, set OPENSSL_PREFIX to the
Homebrew OpenSSL install before building:
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.
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 (via src/recursor.zig);
there is no dedicated recursor test step.
Docker / Linux
The host dev build and tests run natively (so on macOS, here). docker/Dockerfile
is a multi-stage build (Debian + Zig 0.16) that builds and runs zdns on Linux,
driven by 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 is the one that earns its keep: the integration suites bind
real sockets, so running them on Linux exercises the platform branch of the
accept/shutdown handshake that a macOS host never runs (shutdown() wakes a
blocked accept() on Linux but not macOS — see tla/server/).
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/e2e compose services live behind the e2e profile, so
./test-with-postgres.sh (which uses only the postgres service) is unaffected.
Running Integration Tests
PostgreSQL Tests with Docker (Recommended)
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, falling back to
host=localhost port=5432 dbname=powerdns user=pdns when it is unset.
Examples:
examples/serve.zig- canonical UDP and TCP authoritative serverexamples/serve_tcp.zig- TCP-only variantexamples/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 only consulted when a config file is loaded
(via --config/-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.