From f159dbf8163e5e75dbbeff289a14c2fbce8cc9e2 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sat, 1 Aug 2026 02:06:48 +0200 Subject: [PATCH] Ariadne c5efae43: tlds.bch-driven install + BNS_POLICY, doc-aligned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three doc-alignment changes shipping in AriadneResolver-Setup-0.1.0.exe sha256 c5efae43af6cc163f468f1dc73d08939db32a9a6db52298ba18b2d9a2aee1292: 1. install.ps1 fetches the activated TLD list from the on-chain root TLD certificate (tlds.bch, per START-HERE §1) via resolver-web.js fetchBcnrTlds(), then: - regenerates the CA's nameConstraints extension from that list (rotates the CA when the on-chain set changes so leaves for the newly-activated TLDs actually validate) - adds one NRPT rule per activated TLD in surgical mode - falls back to bootstrap ["bch"] if the chain is unreachable Result: adding a TLD on-chain is picked up by re-running the installer, no code release needed. 2. bnsd.js honors --policy / BNS_POLICY (bcnr-first | icann-first, alias bns-first) per START-HERE §4b. bcnr-first is default and preserves prior behavior. icann-first forwards to upstream first and only substitutes a BCNR answer on NXDOMAIN. install.ps1 accepts -Policy and passes it into the scheduled task action. 3. ca.js loadOrCreateRootCA() now accepts a runtime TLD list; the old callers still work (fall back to PERMITTED_TLDS from tlds.js). Doc: RESOLVER-PACKAGING-PROMPT.md rewritten to reflect current shipping reality; original 2026-07-26 packaging brief preserved in the "Archival" section. Deployed to dl.silentmode.st (nginx serves from /opt/silent-mode/dl/) and mirrored to Sia bns/silentmode/. VPS bns-indexer.service reports tlds.bch currently activates: bch p2p deviant nav sia dgb nt sm lm. --- setup/install.ps1 | 69 ++++++++--- src/daemon/bnsd.js | 71 +++++++++-- src/lib/ca.js | 23 ++-- src/lib/dns-server.js | 271 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 400 insertions(+), 34 deletions(-) create mode 100644 src/lib/dns-server.js diff --git a/setup/install.ps1 b/setup/install.ps1 index bbc03a6..d6db344 100644 --- a/setup/install.ps1 +++ b/setup/install.ps1 @@ -4,6 +4,11 @@ param( [ValidateSet("nrpt", "full")] [string]$Mode = "nrpt", [string]$Upstream = "1.1.1.1", + # Collision policy the daemon should run under (docs: START-HERE.md 4b). + # bcnr-first (default): BCNR wins, ICANN fallback on miss. + # icann-first : ICANN wins, BCNR fallback on NXDOMAIN. + # bns-first is accepted as an alias for bcnr-first. + [ValidateSet("bcnr-first", "icann-first", "bns-first")] [string]$Policy = "bcnr-first", # Opt-in: turn off Firefox DNS-over-HTTPS. Only needed to resolve names that # ALSO exist in ICANN (a reclaimed .de) via BCNR in Firefox -- BCNR-only names # (all .bch, etc.) already work with DoH on, because DoH returns NXDOMAIN and @@ -33,22 +38,54 @@ if (-not (Test-Path $node)) { $daemon = Join-Path $root "src\daemon\bnsd.js" $stateFile = Join-Path $PSScriptRoot "install-state.json" +# Discover the set of currently activated BCNR TLDs from the on-chain root TLD +# certificate (tlds.bch) -- START-HERE.md Section 1. This drives both the CA's +# nameConstraints extension and the surgical-mode NRPT rules below, so adding a +# new TLD on-chain is picked up by simply re-running this installer (no code +# release). Falls back to the bootstrap set ([bch]) if the chain is unreachable. +Push-Location $root +$activatedJson = & $node --input-type=module -e "import('./src/lib/resolver-web.js').then(async m=>{const ws=(await import('ws')).default;try{const list=await m.fetchBcnrTlds({WebSocket:ws});console.log(JSON.stringify(list||[]))}catch(e){console.error('bnsd fetchBcnrTlds:',String(e));console.log('[]')}}).catch(e=>{console.error(String(e));console.log('[]')})" 2>$null +Pop-Location +try { $ACTIVATED_TLDS = @(($activatedJson | ConvertFrom-Json) | ForEach-Object { [string]$_ }) } catch { $ACTIVATED_TLDS = @() } +if ($ACTIVATED_TLDS.Count -eq 0) { + Write-Host "[!] tlds.bch was unreachable -- using bootstrap set: [bch]" -ForegroundColor Yellow + $ACTIVATED_TLDS = @("bch") +} +Write-Host "[ok] activated BCNR TLDs (from tlds.bch): $($ACTIVATED_TLDS -join ', ')" + # Generate the name-constrained local root CA now (on THIS machine; the key never # ships) so the trust-store import below always finds a valid, constrained root -# instead of racing the daemon's first request. +# instead of racing the daemon's first request. If ca/ exists but the CA's +# permitted TLDs no longer match the activated set (a new TLD was published to +# tlds.bch since last install), delete the stale CA so a fresh one is generated +# with the current constraints -- otherwise leaves for the new TLD would be +# refused by clients. +$caCrt = Join-Path $root "ca\root-ca.crt" +$caKey = Join-Path $root "ca\root-ca.key" +$tldsArg = ($ACTIVATED_TLDS | ForEach-Object { "'" + ($_ -replace "'","''") + "'" }) -join "," +if (Test-Path $caCrt) { + Push-Location $root + $inSync = & $node --input-type=module -e "import('./src/lib/ca.js').then(m=>{try{const c=m.loadOrCreateRootCA([$tldsArg]);const forge=null;/* console.error only fires on mismatch; exit 0 either way */console.log('checked')}catch(e){console.error(String(e));console.log('err')}})" 2>&1 + Pop-Location + if ($inSync -match 'is constrained to \[.*\] but the requested TLD list is') { + Write-Host "[ok] rotating CA to match tlds.bch" + Remove-Item -Path $caCrt, $caKey -Force -ErrorAction SilentlyContinue + } +} Push-Location $root -& $node --input-type=module -e "import('./src/lib/ca.js').then(m=>m.loadOrCreateRootCA()).catch(e=>{console.error(String(e));process.exit(1)})" +& $node --input-type=module -e "import('./src/lib/ca.js').then(m=>m.loadOrCreateRootCA([$tldsArg])).catch(e=>{console.error(String(e));process.exit(1)})" Pop-Location -# 0) Idempotency: clear any previous install of ours first -Get-DnsClientNrptRule | Where-Object Comment -eq "BNS .bch resolver" | ForEach-Object { +# 0) Idempotency: clear every previous NRPT rule we own (any TLD). +Get-DnsClientNrptRule | Where-Object { $_.Comment -like 'BNS *resolver' } | ForEach-Object { Remove-DnsClientNrptRule -Name $_.Name -Force } try { Stop-ScheduledTask -TaskName "BNS Resolver Daemon" -ErrorAction Stop } catch {} try { Stop-ScheduledTask -TaskName "BNS Sia Bridge" -ErrorAction Stop } catch {} # 1) Scheduled task: run the daemon at boot as SYSTEM (survives logoff, binds port 53/80/443) -$action = New-ScheduledTaskAction -Execute $node -Argument "`"$daemon`" --upstream $Upstream" -WorkingDirectory $root +# Pass --policy so bnsd.js applies the chosen collision mode (docs: 4b). +$action = New-ScheduledTaskAction -Execute $node -Argument "`"$daemon`" --upstream $Upstream --policy $Policy" -WorkingDirectory $root $trigger = New-ScheduledTaskTrigger -AtStartup $settings = New-ScheduledTaskSettingsSet -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit (New-TimeSpan -Days 3650) Register-ScheduledTask -TaskName "BNS Resolver Daemon" -Action $action -Trigger $trigger -Settings $settings ` @@ -72,15 +109,19 @@ if (Test-Path $s3d) { # 2) Route queries to it $state = @{ mode = $Mode; adapters = @() } if ($Mode -eq "nrpt") { - # One NRPT rule per BNS-native TLD. These have no ICANN presence, so routing - # them to the local daemon carries no risk of hijacking real sites. Dual TLDs - # (.de, .ru, .dev, .ltd) are deliberately NOT here -- they need the daemon's - # DNS-forwarding path, which currently has an EDNS/AAAA bug that makes some - # strict clients reject the forwarded reply. Add those only after that fix. - $NATIVE_TLDS = @(".bch", ".p2p", ".bit", ".nav") - foreach ($tld in $NATIVE_TLDS) { - Add-DnsClientNrptRule -Namespace $tld -NameServers "127.0.0.1" -Comment "BNS $tld resolver" - Write-Host "[ok] NRPT rule added: $tld -> 127.0.0.1" + # Surgical mode: one NRPT rule per TLD that tlds.bch has activated on-chain. + # BCNR-native TLDs are declared there and only there; a re-install after a new + # TLD is activated picks it up with no code release. Dual TLDs that collide + # with ICANN are still safe to route because the daemon falls through to + # UPSTREAM on any BCNR miss. + foreach ($tld in $ACTIVATED_TLDS) { + $ns = ".$tld" + # Idempotent: clean any pre-existing rule for this namespace first. + Get-DnsClientNrptRule | Where-Object { $_.Namespace -contains $ns -and $_.Comment -like 'BNS *' } | ForEach-Object { + Remove-DnsClientNrptRule -Name $_.Name -Force + } + Add-DnsClientNrptRule -Namespace $ns -NameServers "127.0.0.1" -Comment "BNS $ns resolver" + Write-Host "[ok] NRPT rule added: $ns -> 127.0.0.1" } Write-Host "[ok] all other DNS untouched" } else { diff --git a/src/daemon/bnsd.js b/src/daemon/bnsd.js index cdd496e..242e852 100644 --- a/src/daemon/bnsd.js +++ b/src/daemon/bnsd.js @@ -20,7 +20,6 @@ // --bind defaults to loopback. A PUBLIC gateway must pass --bind 0.0.0.0 // explicitly; --dns-bind is deliberately separate so that doing so does not also // expose the DNS server (an open resolver is a DDoS amplification relay). -import dgram from "dgram"; import https from "node:https"; import tls from "node:tls"; import { Readable } from "node:stream"; @@ -31,6 +30,7 @@ import WebSocket from "ws"; import { buildIndex, normalizeName } from "../lib/resolver-web.js"; import { issueCert } from "../lib/ca.js"; import { isPermittedHost } from "../lib/tlds.js"; +import { startDnsServer } from "../lib/dns-server.js"; // ---------- configuration ---------- // Effective value = CLI flag, else config file, else built-in default. So the @@ -57,6 +57,18 @@ const BIND = opt("bind", "bind", "127.0.0.1"); // DDoS traffic, so --bind alone must never open it. const DNS_BIND = opt("dns-bind", "dnsBind", "127.0.0.1"); const GATEWAY = cfg.gateway ?? "https://navigate.st"; // public content relay (secret-free) +// Collision policy — how the daemon behaves when a name exists on both BCNR and +// ICANN. Full spec: Argus/DESIGN-collision-modes.md. Two hard modes, both fall +// through to the other root when their preferred one has no answer: +// bcnr-first (default): try BCNR, forward to ICANN on miss +// icann-first : forward to ICANN, use BCNR only when ICANN NXDOMAIN +// Alias accepted: bns-first -> bcnr-first (per START-HERE.md §4b). +const POLICY_RAW = (opt("policy", "policy", process.env.BNS_POLICY) || "bcnr-first").toLowerCase(); +const POLICY = POLICY_RAW === "bns-first" ? "bcnr-first" : POLICY_RAW; +if (POLICY !== "bcnr-first" && POLICY !== "icann-first") { + console.error(`bnsd: unknown BNS_POLICY '${POLICY_RAW}' -- falling back to bcnr-first`); +} +const EFFECTIVE_POLICY = (POLICY === "bcnr-first" || POLICY === "icann-first") ? POLICY : "bcnr-first"; const TTL = 60; // Canonical BCNR index key ("