~ober/jerboa-dns

Imported from ~/mine/jerboa-dns

download snapshot

about

# jerboa-dns

Authoritative-only DNS server and zone compiler in Jerboa/Chez Scheme.
`jdns` serves both UDP and TCP DNS on the configured port.

## Build and Test

```sh
make build
make test
```

Release evidence:

```sh
make release-evidence
```

The release bundle records DNS soak/load status under
`dist/release-evidence/soak/`. By default this is a blocked, record-only status;
set `JDNS_RUN_RELEASE_SOAK=1` with production-duration `JDNS_SOAK_SECONDS` to
capture current UDP `dnsperf` evidence plus bounded DNS-over-TCP smoke.
Bounded local coverage-guided fuzz evidence is recorded with:

```sh
JDNS_RUN_COVERAGE_FUZZ=1 JDNS_FUZZ_RUNS=2048 make fuzz-evidence
```

When `JDNS_RUN_COVERAGE_FUZZ=1` is set for `make release-evidence`, the release
bundle also records `coverage_fuzz_status=local-smoke-recorded` under
`dist/release-evidence/fuzz-evidence/` after `dns_query`, `cdb_record`, and
`cdb_file` complete.
Authoritative-only, amplification, and edge rate-limit policy is documented in
`docs/rate-limit-amplification.md` and copied into
`dist/release-evidence/abuse-policy.txt`.

Static release targets live behind the `static-*` Make targets, for example:

```sh
make static-freebsd
```

## Runtime Security Defaults

`jdns` binds the UDP and TCP sockets first, then chroots to `ROOT` (default
`.`), drops supplementary groups with `setgroups(0, NULL)`, applies
`setgid`/`setuid`, verifies the resulting identities, and then applies platform
filesystem restrictions. A root launch requires both a non-zero `UID` and
non-zero `GID`. Root retention is denied unless the development-only
`JDNS_RETAIN_PRIVILEGES=1` / `--retain-privileges` override is explicit; that
override is audited in the log and is not a production configuration. See
`docs/runtime-hardening.md`.

TCP handling uses Jerboa fibers over nonblocking sockets when the fiber runtime
is available before chroot. Embedded static builds can fall back to bounded
nonblocking OS-thread tasks instead of failing at startup. TCP clients are capped
at 128 concurrent sessions, and idle TCP reads/writes time out after 15 seconds.

The daemon is authoritative-only and does not recurse or forward queries.
Amplification-prone `ANY` queries are minimized with a small REFUSED response.
Production deployments must still provide edge rate limits until native
response-rate limiting exists.

Filesystem sandbox setup fails closed by default. For local development only,
you can allow a weaker fallback with either:

```sh
JDNS_ALLOW_SANDBOX_FALLBACK=1 jdns ...
jdns --allow-sandbox-fallback ...
```

The WASM DNS parser and WASM CDB reader are embedded into the binary at build
time (`lib/jerboa-dns/wasm-embedded.ss`, regenerated by `wasm/build.sh`), so
the sandbox is active with no files on disk, including inside the chroot.
If the wasmi runtime itself is not linked in, startup/query handling fails
closed by default. The only in-process parser fallback is the explicit
development opt-out `JDNS_ALLOW_IN_PROCESS_PARSER=1` or
`JDNS_ALLOW_IN_PROCESS_CDB=1`.

`JDNS_PARSER_WASM` / `JDNS_CDB_WASM` point at on-disk modules as an explicit
development override (useful for testing a rebuilt parser without relinking);
they are not needed in production.

## Zone Updates

`jdns-data` writes a temporary CDB and atomically renames it into place. The
server validates one immutable in-memory snapshot at startup. A low-frequency
watcher detects device/inode/time/size changes, builds and validates a candidate
off the request path, and atomically swaps it; in-flight queries finish against
their old snapshot. Queries never reopen the CDB. The default one-second poll
can be changed with `JDNS_CDB_RELOAD_MS` (100–60000 ms), and malformed updates
leave the last valid snapshot serving.

## Zone Formats

`jdns-data` accepts the original tinydns `data` format and a grouped
s-expression format. The s-expression form starts with `(zone ...)`, so the
compiler auto-detects it:

```scheme
(zone
  (domain "example.com"
    (soa "ns1.example.com" "1.2.3.4" 259200)
    (ns "ns2.example.com" "1.2.3.5" 259200)
    (a "1.2.3.4" 86400)
    (mx "mail.example.com" 10 "1.2.3.6" 86400)
    (txt "v=spf1 include:example.net mx -all" 86400)
    (txt "_dmarc" "v=DMARC1; p=none" 86400))
  (domain "www.example.com"
    (a+ptr "1.2.3.4" 86400))
  (domain "static.example.com"
    (cname "www.example.com" 3600))
  (domain "ipv6.example.com"
    (aaaa "2001:db8::1" 86400)))
```

TXT records can optionally start with a relative owner name. For example,
`(txt "_dmarc" "v=DMARC1; p=none")` inside `example.com` compiles as a TXT
record for `_dmarc.example.com`.

Compile either format the same way:

```sh
make jdns-data ARGS="zones/example.sexp data.cdb"
```

recent commits