@serve.zone/dcrouter 34.0.0 → 34.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '34.0.0',
6
+ version: '34.2.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLE9BQU8sRUFBRSxRQUFRO0lBQ2pCLFdBQVcsRUFBRSwwRUFBMEU7Q0FDeEYsQ0FBQSJ9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "34.0.0",
4
+ "version": "34.2.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -20,8 +20,11 @@
20
20
  "@git.zone/tsrepo": "^1.1.1",
21
21
  "@push.rocks/smartdb-v2": "npm:@push.rocks/smartdb@2.14.6",
22
22
  "@push.rocks/smartdb-v5.2": "npm:@push.rocks/smartdb@5.2.2",
23
+ "@serve.zone/dcrouter-v33": "npm:@serve.zone/dcrouter@33.0.0",
24
+ "@types/dns-packet": "^5.6.5",
23
25
  "@types/node": "26.6.5",
24
26
  "@types/web-push": "3.6.4",
27
+ "dns-packet": "^5.6.1",
25
28
  "tsx": "^4.23.15"
26
29
  },
27
30
  "dependencies": {
@@ -41,14 +44,14 @@
41
44
  "@push.rocks/smartbucket": "^4.13.0",
42
45
  "@push.rocks/smartchallenge": "^1.1.0",
43
46
  "@push.rocks/smartdb": "5.7.1",
44
- "@push.rocks/smartdns": "^8.2.1",
47
+ "@push.rocks/smartdns": "^11.1.0",
45
48
  "@push.rocks/smartfs": "^1.7.0",
46
49
  "@push.rocks/smartguard": "^3.1.2",
47
50
  "@push.rocks/smartjwt": "^2.5.1",
48
51
  "@push.rocks/smartlog": "^3.2.2",
49
52
  "@push.rocks/smartmcp": "^0.3.0",
50
53
  "@push.rocks/smartmetrics": "^3.0.3",
51
- "@push.rocks/smartmigration": "2.0.1",
54
+ "@push.rocks/smartmigration": "2.1.0",
52
55
  "@push.rocks/smartmta": "^13.0.1",
53
56
  "@push.rocks/smartnetwork": "^4.10.1",
54
57
  "@push.rocks/smartpath": "^6.0.0",
@@ -68,6 +71,7 @@
68
71
  "@tsclass/tsclass": "^9.5.1",
69
72
  "@types/qrcode": "^1.5.6",
70
73
  "argon2": "^0.45.1",
74
+ "bson": "7.3.2",
71
75
  "ipaddr.js": "^2.5.0",
72
76
  "lru-cache": "^11.5.2",
73
77
  "qrcode": "^1.5.4",
package/readme.md CHANGED
@@ -63,6 +63,31 @@ Use the package as a TypeScript library:
63
63
  pnpm add @serve.zone/dcrouter
64
64
  ```
65
65
 
66
+ ## CLI
67
+
68
+ ```bash
69
+ dcrouter # start the router (DCROUTER_MODE=OCI_CONTAINER for the container configuration)
70
+ dcrouter --version
71
+ dcrouter --help
72
+ dcrouter migrate --storage-path <absolute root> --to <version> [--db-name dcrouter] [--plan]
73
+ ```
74
+
75
+ `dcrouter` starts the router only without arguments; from 34.2.0 on it refuses any argument it does not know (exit code 2) and starts nothing. Releases up to 34.1.0 started the router on an unknown argument, so check `dcrouter --version` before running a command a later release added.
76
+
77
+ `dcrouter migrate` walks the data of a **stopped** dcrouter back to an earlier data version through the migration steps' down handlers; forward migrations happen when dcrouter starts. The supported downgrade is data version `34.0.0` → `32.6.0`, the data dcrouter 33.0.0 reads, which returns the route DNS ownership the `release-annexed-gateway-route-dns-records` step released:
78
+
79
+ ```bash
80
+ # the embedded database's default root is ~/.serve.zone/dcrouter/tsmdb (dbConfig.storagePath overrides it)
81
+ dcrouter migrate --storage-path /root/.serve.zone/dcrouter/tsmdb --to 32.6.0 --plan # report only
82
+ dcrouter migrate --storage-path /root/.serve.zone/dcrouter/tsmdb --to 32.6.0
83
+ ```
84
+
85
+ - `--storage-path` (required, absolute, no default) is the embedded database's storage root; `--db-name` is the database inside it (default `dcrouter`). The root is checked offline first and the engine's owner lock is taken, so a root a running dcrouter owns is refused.
86
+ - `--to` must be a step boundary behind the ledger. A forward target, a ledger a newer dcrouter wrote and a database without a ledger are refused; a target equal to the ledger is a no-op.
87
+ - A database that dcrouter 34.0.0 or 34.1.0 upgraded is refused: those releases did not record the ownership they released, so restore the export taken before that upgrade instead. Upgrade a 33.0.0 database straight to 34.2.0 or later to keep the way back.
88
+ - Records written after the upgrade are kept: a record deleted since is not recreated, an edited one gets its ownership back, and any record whose ownership conflicts with the recorded one refuses the whole downgrade with nothing changed.
89
+ - The last output line is `DCROUTER_MIGRATE_RESULT:{json}` (`ok`, the versions and the steps, or the error); the exit code is 0, 1 on a refusal or failure, and 2 on a command line it does not accept.
90
+
66
91
  ## Quick Start
67
92
 
68
93
  This starts the gateway on unprivileged ports and stores data under the default `~/.serve.zone/dcrouter` base directory.
@@ -530,13 +555,55 @@ records and testing DNS keep their own durable records and are not journaled her
530
555
  Which zones the embedded DNS server may answer for authoritatively is database
531
556
  state, not deployment configuration. There is no `dnsScopes` option.
532
557
 
533
- A zone enters the authority set exactly one way: its **public delegation must
534
- name one of `dnsNsDomains`**, observed through a single DNS-over-HTTPS lookup
535
- against a public resolver. The system resolver is never used for this — on a
536
- dcrouter host it may be dcrouter itself, which would answer with the very NS
537
- records dcrouter generated and make the proof self-confirming. An ops-API caller
538
- cannot repoint somebody else's delegation, so writing the record is not the same
539
- as manufacturing the proof.
558
+ A zone enters the authority set exactly one way: its **delegation in the parent
559
+ zone must name one of `dnsNsDomains`**, read from the parent zone's own servers.
560
+ An ops-API caller cannot repoint somebody else's delegation, so writing the
561
+ record is not the same as manufacturing the proof.
562
+
563
+ The probe never looks the zone up through a recursive resolver: for a zone that
564
+ is delegated to dcrouter but not claimed yet, the resolver would ask dcrouter,
565
+ which REFUSES the unclaimed zone, and the lookup would fail with SERVFAIL. It
566
+ works like this instead:
567
+
568
+ 1. It finds the parent zone over DNS-over-HTTPS: the nearest proper suffix of the
569
+ zone that has NS records (at most 8 levels up). The zone itself and names
570
+ below it are never asked over DoH, and a failed lookup at any level makes the
571
+ probe `undeterminable` rather than skipping to a grandparent.
572
+ 2. It looks up A and AAAA of up to 8 of the parent's nameserver names over DoH
573
+ and picks up to 4 distinct addresses: one per name first, IPv4 before IPv6.
574
+ Fewer than 2 names with an address is `undeterminable`.
575
+ 3. It asks each picked server for the zone's NS records without recursion
576
+ (RD=0) and with DO=1, over UDP and TCP when the answer is truncated.
577
+ 4. Only two answers are conclusive. A referral: no AA bit, an empty answer
578
+ section, authority NS records all owned by the zone, and an OPT record that
579
+ echoes DO=1. A negative: an authoritative NXDOMAIN or NODATA with an empty
580
+ answer section and the parent zone's SOA. Anything else is inconclusive,
581
+ including an authoritative answer from a server that hosts both the parent
582
+ and the zone.
583
+ 5. At least 2 conclusive answers, through at least 2 different parent
584
+ nameserver names, must be identical (the NS set and whether a DS came with
585
+ it; glue, TTLs, case and order are ignored). Any disagreement is
586
+ `undeterminable`.
587
+
588
+ The result is `delegated` when the agreed referral names one of `dnsNsDomains`,
589
+ and `not-delegated` when it names none of them (a foreign delegation) or the
590
+ parent has no delegation for the zone. `observedNameservers` is the NS set the
591
+ parent delegates to, and the probe also returns `parentZone`, `dsPresent` and
592
+ every parent server's classified answer (`parentAnswers`). DoH never decides a
593
+ verdict, and the system resolver is never used: on a dcrouter host it may be
594
+ dcrouter itself, which would make the proof self-confirming.
595
+
596
+ A zone whose parent publishes a **DS** for it is reported as `delegated` with
597
+ `dsPresent: true`, and `verifyDnsAuthorityZone` refuses it: remove the DS at the
598
+ registrar, then verify. The drift audit says so too. A zone below a zone dcrouter
599
+ already serves is `undeterminable`: its parent servers are dcrouter's own, which
600
+ answer for the parent authoritatively instead of referring.
601
+
602
+ DoH lookups time out after 5 s and each parent server attempt after 2 s (a
603
+ truncated UDP answer gets one more TCP attempt). No probe step starts after
604
+ 30 s, so the probe's lookups and queries end within 35 s (plus starting and
605
+ stopping the probe's DNS client); a claim holds the DNS mutation lock about
606
+ that long at most while it probes.
540
607
 
541
608
  | Operation | Scope | Effect |
542
609
  | --- | --- | --- |
@@ -549,7 +616,13 @@ as manufacturing the proof.
549
616
  Writes accept an admin identity or an API token carrying `dns-authority:write`;
550
617
  a non-admin identity is refused. Probes return three verdicts, never two: `delegated`, `not-delegated`, and
551
618
  `undeterminable`. A timeout is not evidence that a zone is not ours, so a
552
- mutation refuses on `undeterminable` rather than assuming either way.
619
+ mutation refuses on `undeterminable` rather than assuming either way, and a
620
+ claim also refuses a `delegated` zone with a DS.
621
+
622
+ The embedded DNS server synthesizes every SOA itself, with TTL 3600, refresh
623
+ 3600, retry 600, expire 604800, minimum 86400, rname `hostmaster.<zone>` and
624
+ mname the first `dnsNsDomains` entry (smartdns falls back to `ns1.<dnssecZone>`, the DNSSEC signing zone, when
625
+ `dnsNsDomains` is empty); negative answers carry the SOA with TTL 3600.
553
626
 
554
627
  Claiming or revoking a zone takes effect in-process, with no restart: it moves
555
628
  the running DNS server's authoritative zone set, its generated apex NS records,
@@ -785,8 +858,9 @@ const router = new DcRouter({
785
858
  emailOutboundMode: 'remoteIngress',
786
859
  dnsNsDomains: ['ns1.example.com', 'ns2.example.com'],
787
860
  // Which zones the embedded DNS server answers for is not configured here.
788
- // A zone earns authority by its public delegation naming dnsNsDomains, and
789
- // that proof lives in the database — see the "DNS Authority" section above.
861
+ // A zone earns authority by its delegation in the parent zone naming
862
+ // dnsNsDomains, and that proof lives in the database — see the "DNS
863
+ // Authority" section above.
790
864
  publicIp: '203.0.113.10',
791
865
  remoteIngressConfig: {
792
866
  enabled: true,
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '34.0.0',
6
+ version: '34.2.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -13,6 +13,24 @@ type TDnsRecordSeed = { name: string; type: string; value: string; ttl?: number;
13
13
  /** The required DNSSEC keying zone when dcrouter currently claims no DNS zone. */
14
14
  export const NO_AUTHORITY_SENTINEL_ZONE = 'no-authority.invalid';
15
15
 
16
+ /**
17
+ * The server-wide SOA values of the embedded DNS server.
18
+ *
19
+ * smartdns 11 lowered its default SOA `minimum` from 86400 to 300, which would
20
+ * cut the negative-answer SOA TTL from min(3600, 86400) = 3600 to 300. The pin
21
+ * keeps the wire values dcrouter has always served: TTL 3600, refresh 3600,
22
+ * retry 600, expire 604800, minimum 86400, rname `hostmaster.<zone>`, and mname
23
+ * the primary nameserver, else smartdns' `ns1.<dnssecZone>`. smartdns refuses
24
+ * `soaDefaults.mname` together with the deprecated `primaryNameserver` with a
25
+ * TypeError, so dcrouter sets the mname here and never passes the latter.
26
+ */
27
+ export const buildDnsServerSoaDefaults = (
28
+ primaryNameserverArg: string | undefined,
29
+ ): Partial<plugins.smartdns.dnsServerMod.IDnsSoaSettings> => ({
30
+ ...(primaryNameserverArg ? { mname: primaryNameserverArg } : {}),
31
+ minimum: 86400,
32
+ });
33
+
16
34
  /**
17
35
  * Sets up and feeds the embedded authoritative smartdns server: validates the
18
36
  * DNS configuration, generates authoritative/email/DKIM records, applies
@@ -139,7 +157,8 @@ export class DnsServerRuntime {
139
157
  dnssecZone: bootAuthorityZones[0] || NO_AUTHORITY_SENTINEL_ZONE,
140
158
  // SmartDNS copies this boot snapshot; later changes use its live setter.
141
159
  authoritativeZones: bootAuthorityZones,
142
- primaryNameserver: primaryNameserver, // Automatically generates correct SOA records
160
+ // smartdns synthesizes every SOA (apex and negative answers) from these.
161
+ soaDefaults: buildDnsServerSoaDefaults(primaryNameserver),
143
162
  // For now, use self-signed cert until we integrate with Let's Encrypt
144
163
  httpsKey: '',
145
164
  httpsCert: ''
@@ -828,7 +847,7 @@ export class DnsServerRuntime {
828
847
  }
829
848
 
830
849
  // Apex NS records are owned by DnsManager (see the doc comment above), and
831
- // SOA is synthesized by smartdns from `primaryNameserver`.
850
+ // SOA is synthesized by smartdns from `soaDefaults` (buildDnsServerSoaDefaults).
832
851
  return records;
833
852
  }
834
853