Ariadne 0.1.5: route and certify every live registry TLD, not the legacy nine

Other browsers stopped opening most BNS names because two lists had
drifted. The installer took its TLD set from the gateway's `advertised`
field — the legacy tlds.bch record, nine TLDs — while the registry had
moved to per-TLD NFTs with twenty (including .x, where most Silent Mode
sites live). And the daemon's permitted-TLD list came from the bundled
ariadne.config.json (seven TLDs) rather than the CA the installer had
just minted, so only the overlap — .bch, .p2p, .deviant, .nav — actually
worked; .sia, .nt and the rest got no certificate, .x got no route at
all.

Now: the gateway publishes `routable` (every live TLD on the public
listing plus the legacy advertised set); the installer routes and
certifies that, skipping ICANN-delegated collision TLDs (.cash, .gt,
.sc — checked against the live IANA root zone, bundled snapshot as
fallback) since the local CA must never be constrained to a real
internet TLD; it writes the certified list into ariadne.config.json;
and the daemon reads its permitted list from the installed CA's own
name constraints, so the two can no longer disagree.
This commit is contained in:
Local Dev 2026-09-27 23:20:26 +02:00
parent 0b6c8093e1
commit 8904c1d7ba
3 changed files with 1494 additions and 2 deletions

1438
setup/iana-tlds.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -126,6 +126,19 @@ function Fetch-TldsFromIndexer([string]$IndexerUrl) {
$url = $IndexerUrl.TrimEnd('/') + "/api/tlds"
Write-Host "[..] indexer: $url"
$r = Invoke-RestMethod -Uri $url -TimeoutSec 5 -UseBasicParsing -Headers @{ 'User-Agent' = 'AriadneResolverInstaller/1' }
# Prefer the registry's routable list (every live per-TLD NFT plus the
# legacy advertised set); fall back to assembling it from tlds[] + advertised
# for a gateway that predates the field; last resort: advertised alone (the
# legacy tlds.bch record — nine TLDs, which is how 0.1.4 missed .x).
if ($r -and $r.routable -and $r.routable.Count -gt 0) {
return @{ tlds = @($r.routable | ForEach-Object { [string]$_ }); source = "indexer($IndexerUrl)" }
}
if ($r -and $r.tlds -and $r.tlds.Count -gt 0) {
$live = @($r.tlds | Where-Object { -not $_.hidden } | ForEach-Object { [string]$_.tld })
$adv = @(); if ($r.advertised) { $adv = @($r.advertised | ForEach-Object { [string]$_ }) }
$u = @($live + $adv | Where-Object { $_ } | Select-Object -Unique)
if ($u.Count -gt 0) { return @{ tlds = $u; source = "indexer($IndexerUrl)" } }
}
if ($r -and $r.advertised -and $r.advertised.Count -gt 0) {
return @{ tlds = @($r.advertised | ForEach-Object { [string]$_ }); source = "indexer($IndexerUrl)" }
}
@ -185,6 +198,31 @@ if ($result.source -like 'indexer(*)' -or $result.source -eq 'electrum') {
} catch { Write-Host "[!] could not write cache: $($_.Exception.Message)" -ForegroundColor Yellow }
}
# Split the activated set: TLDs that ICANN has delegated (.cash, .gt, .sc are
# registered in BNS on purpose as collision tests) are neither routed to the
# daemon nor certified by the local CA — the daemon's upstream forwarding has
# an open EDNS/AAAA concern, and a CA constrained to a real internet TLD could
# vouch for real sites. The IANA root-zone list is fetched live (8 s) and falls
# back to the snapshot bundled next to this script. "bcnr" is always certified:
# the daemon's default certificate is issued for gateway.bcnr.
$ianaList = @()
try {
$ianaText = (Invoke-WebRequest -Uri 'https://data.iana.org/TLD/tlds-alpha-by-domain.txt' -TimeoutSec 8 -UseBasicParsing).Content
$ianaList = @($ianaText -split "`n" | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ -and -not $_.StartsWith('#') })
Write-Host "[ok] IANA root zone: $($ianaList.Count) delegated TLDs (live)"
} catch {
$snap = Join-Path $PSScriptRoot 'iana-tlds.txt'
if (Test-Path $snap) { $ianaList = @(Get-Content $snap | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ }); Write-Host "[!] IANA list from bundled snapshot ($($ianaList.Count) TLDs)" -ForegroundColor Yellow }
else { Write-Host "[!] no IANA list available -- routing every activated TLD" -ForegroundColor Yellow }
}
$ianaSet = @{}; foreach ($t in $ianaList) { $ianaSet[$t] = $true }
$COLLISION_TLDS = @($ACTIVATED_TLDS | Where-Object { $ianaSet.ContainsKey($_.ToLower()) })
$ROUTE_TLDS = @($ACTIVATED_TLDS | Where-Object { -not $ianaSet.ContainsKey($_.ToLower()) })
if ($ROUTE_TLDS.Count -eq 0) { $ROUTE_TLDS = @('bch') }
$CA_TLDS = @($ROUTE_TLDS + 'bcnr' | Select-Object -Unique)
if ($COLLISION_TLDS.Count -gt 0) { Write-Host "[info] left to ICANN (delegated, resolved by the upstream): $($COLLISION_TLDS -join ', ')" }
Write-Host "[ok] routing + certifying: $($ROUTE_TLDS -join ', ')"
# Generate the name-constrained local root CA now (on THIS machine; the key
# never ships). If ca/ exists but its permitted-TLD set no longer matches, we
# rotate: delete the stale CA so loadOrCreateRootCA writes a fresh one with
@ -192,7 +230,7 @@ if ($result.source -like 'indexer(*)' -or $result.source -eq 'electrum') {
# newly-added TLD.
$caCrt = Join-Path $root "ca\root-ca.crt"
$caKey = Join-Path $root "ca\root-ca.key"
$tldsArg = ($ACTIVATED_TLDS | ForEach-Object { "'" + ($_ -replace "'","''") + "'" }) -join ","
$tldsArg = ($CA_TLDS | ForEach-Object { "'" + ($_ -replace "'","''") + "'" }) -join ","
if (Test-Path $caCrt) {
$r = Invoke-NodeCapture "import('./src/lib/ca.js').then(m=>{try{m.loadOrCreateRootCA([$tldsArg]);console.log('ok')}catch(e){console.log('err')}}).catch(e=>console.log('err'))"
@ -211,6 +249,17 @@ if ($r.ExitCode -ne 0) {
if (-not (Test-Path $caCrt)) { throw "CA generation produced no root-ca.crt" }
Write-Host "[ok] CA at $caCrt"
# The daemon reads its permitted-TLD list from ariadne.config.json (and, since
# 0.1.5, from the CA's own constraints); write the same list the CA was minted
# with so the two can never drift apart again.
$cfgPath = Join-Path $root 'ariadne.config.json'
try {
$cfg = Get-Content $cfgPath -Raw | ConvertFrom-Json
$cfg.tlds = @($CA_TLDS)
$cfg | ConvertTo-Json -Depth 10 | Set-Content -Path $cfgPath -Encoding utf8
Write-Host "[ok] ariadne.config.json tlds = $($CA_TLDS -join ', ')"
} catch { Write-Host "[!] could not update ariadne.config.json tlds: $($_.Exception.Message)" -ForegroundColor Yellow }
# 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
@ -249,7 +298,7 @@ if ($Mode -eq "nrpt") {
# 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) {
foreach ($tld in $ROUTE_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 {

View file

@ -847,6 +847,11 @@ const server = http.createServer(async (req, res) => {
updated_txid: snap.asOfTxid ?? null,
root: snap.root ?? null,
advertised,
// What a client resolver should route and certify: every live TLD
// on the public listing plus the legacy advertised set. Ariadne's
// installer used `advertised` alone until 0.1.5, which is why .x
// and ten other registry TLDs never reached other browsers.
routable: [...new Set([...tlds.map((t) => t.tld), ...advertised])],
hidden_count: all.filter((t) => t.hidden).length,
tlds,
}, null, 2));