From 68d640a086397c121c4573a46c42e25bebf7826e Mon Sep 17 00:00:00 2001 From: quest Date: Tue, 14 Jul 2026 14:13:38 -1000 Subject: [PATCH] Add optional OpenNIC root support via OPENNIC=1 environment variable --- Dockerfile | 3 +++ README.md | 32 ++++++++++++++++++++++++++++++++ entrypoint.sh | 4 ++++ opennic.hints | 18 ++++++++++++++++++ opennic.key | 5 +++++ unbound.sh | 35 +++++++++++++++++++++++++++++++++-- 6 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 opennic.hints create mode 100644 opennic.key diff --git a/Dockerfile b/Dockerfile index af042f6..0ed5f3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,6 +64,9 @@ RUN mkdir -p \ COPY --chmod=644 encrypted-dns.toml.in /opt/encrypted-dns/etc/ COPY --chmod=644 undelegated.txt /opt/encrypted-dns/etc/ +COPY --chmod=644 opennic.hints /opt/unbound/etc/unbound/ +COPY --chmod=644 opennic.key /opt/unbound/etc/unbound/ + COPY --chmod=755 entrypoint.sh / COPY --chmod=755 unbound.sh /var/svc/unbound/run diff --git a/README.md b/README.md index d55c00e..b0e96c0 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Table of contents: - [Prometheus metrics](#prometheus-metrics) - [TLS (including HTTPS and DoH) forwarding](#tls-including-https-and-doh-forwarding) - [Filtering](#filtering) + - [OpenNIC](#opennic) - [Join the network](#join-the-network) - [Usage with Docker Compose](#usage-with-docker-compose) - [Usage with Kubernetes](#usage-with-kubernetes) @@ -186,6 +187,35 @@ And put the list of domains to block in a file named `/etc/dnscrypt-server/lists Then, follow the upgrade procedure, adding the following option to the `docker run` command: `-v /etc/dnscrypt-server/lists:/opt/encrypted-dns/etc/lists`. +## OpenNIC + +The server can resolve names using the [OpenNIC](https://www.opennic.org/) +root instead of the ICANN root, giving access to the OpenNIC TLDs (`.bbs`, +`.chan`, `.cyb`, `.dyn`, `.epic`, `.geek`, `.gopher`, `.indy`, `.libre`, +`.neo`, `.null`, `.o`, `.oss`, `.oz`, `.parody`, `.pirate`) in addition to +the regular namespace. + +Enable it by setting the `OPENNIC=1` environment variable when creating the +container (add `-e OPENNIC=1` to the `docker run` command). Without it, the +image keeps the default behavior and resolves from the ICANN root. + +In OpenNIC mode: + +- Unbound bootstraps from the OpenNIC root servers (`opennic.hints`) and never + contacts the ICANN root servers, internic.net, or IANA. +- DNSSEC validation is anchored to the OpenNIC root key (`opennic.key`) and + kept up to date automatically (RFC 5011). The OpenNIC root also serves the + DS records of the ICANN TLDs, so DNSSEC keeps working for the regular + namespace. + +If the OpenNIC root server set or root key ever changes, refresh both files +and rebuild the image: + +```sh +dig +https @dns1.slowb.ro . NS # compare against opennic.hints +dig +https @dns1.slowb.ro . DNSKEY # compare the KSK (flags 257) against opennic.key +``` + # Join the network If you want to help against DNS centralization and surveillance, @@ -295,6 +325,8 @@ docker rmi --force jedisct1/dnscrypt-server ||: - Caching resolver: [Unbound](https://www.unbound.net/), with DNSSEC, prefetching, and no logs. The number of threads and memory usage are automatically adjusted. Latest stable version, compiled from source. qname minimisation is enabled. +Optionally resolves from the [OpenNIC](https://www.opennic.org/) root (see +[OpenNIC](#opennic)). - [encrypted-dns-server](https://github.com/jedisct1/encrypted-dns-server). Compiled from source. diff --git a/entrypoint.sh b/entrypoint.sh index a24763d..763d593 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -186,6 +186,10 @@ start() { ln -s -f "$service" "${SERVICES_DIR}/" done + # runit wipes the environment before running services, so persist + # settings that service scripts need to a file. + printf '%s\n' "${OPENNIC:-}" >/etc/opennic-env + exec /etc/runit/2 /dev/null 2>/dev/null } diff --git a/opennic.hints b/opennic.hints new file mode 100644 index 0000000..b97ea6d --- /dev/null +++ b/opennic.hints @@ -0,0 +1,18 @@ +; OpenNIC root hints +; https://servers.opennicproject.org/ +; Verified 2026-07-14 against two independent OpenNIC DoH servers. +; Refresh with: dig +https @dns1.slowb.ro . NS (then A/AAAA for each NS) +. 3600000 NS ns2.opennic.glue. +. 3600000 NS ns4.opennic.glue. +. 3600000 NS ns6.opennic.glue. +. 3600000 NS ns7.opennic.glue. +. 3600000 NS ns8.opennic.glue. +ns2.opennic.glue. 3600000 A 161.97.219.84 +ns2.opennic.glue. 3600000 AAAA 2001:470:4212:10:0:100:53:10 +ns4.opennic.glue. 3600000 A 116.203.104.203 +ns4.opennic.glue. 3600000 AAAA 2a01:4f8:c2c:da9c::1 +ns6.opennic.glue. 3600000 A 45.79.189.158 +ns7.opennic.glue. 3600000 A 153.56.130.67 +ns7.opennic.glue. 3600000 AAAA 2001:470:7493::e0 +ns8.opennic.glue. 3600000 A 178.63.116.152 +ns8.opennic.glue. 3600000 AAAA 2a01:4f8:141:4281::999 diff --git a/opennic.key b/opennic.key new file mode 100644 index 0000000..3c87f9f --- /dev/null +++ b/opennic.key @@ -0,0 +1,5 @@ +; OpenNIC root DNSSEC trust anchor (KSK, key tag 60820, alg 8/RSASHA256) +; Corresponding DS: 60820 8 2 A01E33C8E95712E555FA9E6C09921830F3A518E36C5998F4ADBF5570AA86B538 +; Verified 2026-07-14 against two OpenNIC DoH servers and https://wiki.opennic.org/opennic/dnssec +; Seeds var/opennic.key on first start; unbound then maintains it via RFC 5011. +. 86400 IN DNSKEY 257 3 8 AwEAAbtbsu+wl3fbEDbgvMgJ1BDXeAk5t6BU7B1KGVvc13zMJtjvarxpWWrAb7fmWERX8kJawa3KpYty0EDFQ24nfQyhwEOld442ca89u4/ZU3jPuwKohbGn55vIQ7KjCIrDNvRYjGVn2MNwZnL4WVVclJYsa1cGwVQ9t575I5yvU+5g+jVcjUsGwFn6xmuJC0Z33ABKsC8b1cjfcnvE4wP3CrXOlDQ+Er4uPUtMKrmG+Sj1Bm5U+do78mwEXOlTz/sNj8tkpL0pYB2j+XNaDVrO0uS1beekejnttMsC4SHMCsiwMvigW2O54ByhzijU2v87d7U9WEMVfPvO6gearg1fo/1Tk4buzPZcS+W9WZgFAt7kT1ois3x0GGT7J55zENB9IZU4tMmWdYbZJOZsdAzmshuWJIUlTZdNN5671Rhc6P9TWnMlvb9iNT7G3DZ9PhBw1OF/OmmXobv3Wygbt5+u7q2CPPzwU4WTGpVNtr3Iry2SPW3XVpJSM3+nW7LfxxtWZJlN4MDQYC5IptU+A5EO80/yE38E9tKGDWC1+Nw59QLaBE7ff+Jkq7OMjTHjFhYivkJSv+8LEbkGjWoaMAS2CT3/ZVMYLiQn8THiZUBF+aOzJMw0EGPag1Qq4vfGgFkQMM3hOaH6bWN1yCvmspuiwLYkNCZZ/l8ThKc57bGYy9TX diff --git a/unbound.sh b/unbound.sh index 56cc301..1bd84d1 100755 --- a/unbound.sh +++ b/unbound.sh @@ -19,6 +19,14 @@ else threads=1 fi +if [ -z "${OPENNIC:-}" ] && [ -f /etc/opennic-env ]; then + OPENNIC=$(cat /etc/opennic-env) +fi +opennic_enabled=0 +case "${OPENNIC:-}" in +1 | true | yes | on) opennic_enabled=1 ;; +esac + provider_name=$(cat "$KEYS_DIR/provider_name") sed \ @@ -57,7 +65,6 @@ server: udp-connect: no chroot: "/opt/unbound/etc/unbound" directory: "/opt/unbound/etc/unbound" - auto-trust-anchor-file: "var/root.key" num-queries-per-thread: 4096 outgoing-range: 8192 msg-cache-size: @MSG_CACHE_SIZE@ @@ -130,6 +137,21 @@ server: remote-control: control-enable: yes control-interface: 127.0.0.1 +EOT + +if [ "$opennic_enabled" = 1 ]; then + cat >>/opt/unbound/etc/unbound/unbound.conf <>/opt/unbound/etc/unbound/unbound.conf </dev/null || :