@serfab/cadre-host 1.13.0 → 1.14.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.
- package/README.md +332 -423
- package/dist/bin/host.d.ts +6 -3
- package/dist/bin/host.js +301 -505
- package/dist/bin/host.js.map +1 -1
- package/dist/bin/join-output.d.ts +56 -0
- package/dist/bin/join-output.js +92 -0
- package/dist/bin/join-output.js.map +1 -0
- package/dist/bin/nat-output.d.ts +49 -0
- package/dist/bin/nat-output.js +127 -0
- package/dist/bin/nat-output.js.map +1 -0
- package/dist/hosted/claim-details.d.ts +44 -0
- package/dist/hosted/claim-details.js +66 -0
- package/dist/hosted/claim-details.js.map +1 -0
- package/dist/hosted/hosted-node-service.d.ts +318 -0
- package/dist/hosted/hosted-node-service.js +666 -0
- package/dist/hosted/hosted-node-service.js.map +1 -0
- package/dist/hosted/hosted-node-store.d.ts +40 -0
- package/dist/hosted/hosted-node-store.js +126 -0
- package/dist/hosted/hosted-node-store.js.map +1 -0
- package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
- package/dist/hosted/hosted-node-supervisor.js +396 -0
- package/dist/hosted/hosted-node-supervisor.js.map +1 -0
- package/dist/hosted/hosted-node-watcher.d.ts +83 -0
- package/dist/hosted/hosted-node-watcher.js +224 -0
- package/dist/hosted/hosted-node-watcher.js.map +1 -0
- package/dist/hosted/index.d.ts +16 -0
- package/dist/hosted/index.js +13 -0
- package/dist/hosted/index.js.map +1 -0
- package/dist/hosted/node-status.d.ts +35 -0
- package/dist/hosted/node-status.js +61 -0
- package/dist/hosted/node-status.js.map +1 -0
- package/dist/hosted/types.d.ts +134 -0
- package/dist/hosted/types.js +22 -0
- package/dist/hosted/types.js.map +1 -0
- package/dist/index.d.ts +12 -24
- package/dist/index.js +9 -19
- package/dist/index.js.map +1 -1
- package/dist/installer/config.d.ts +4 -34
- package/dist/installer/config.js +3 -18
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/index.d.ts +7 -19
- package/dist/installer/index.js +14 -69
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/paths.d.ts +0 -2
- package/dist/installer/paths.js +0 -4
- package/dist/installer/paths.js.map +1 -1
- package/dist/installer/wizard.d.ts +0 -7
- package/dist/installer/wizard.js +1 -8
- package/dist/installer/wizard.js.map +1 -1
- package/dist/nat/address-resolver.d.ts +18 -17
- package/dist/nat/address-resolver.js +79 -25
- package/dist/nat/address-resolver.js.map +1 -1
- package/dist/nat/address-watch.d.ts +37 -0
- package/dist/nat/address-watch.js +84 -0
- package/dist/nat/address-watch.js.map +1 -0
- package/dist/nat/ddns/duckdns.d.ts +2 -1
- package/dist/nat/ddns/duckdns.js +5 -1
- package/dist/nat/ddns/duckdns.js.map +1 -1
- package/dist/nat/ddns/index.d.ts +6 -0
- package/dist/nat/ddns/index.js.map +1 -1
- package/dist/nat/index.d.ts +12 -9
- package/dist/nat/index.js +6 -5
- package/dist/nat/index.js.map +1 -1
- package/dist/nat/nat-service.d.ts +203 -77
- package/dist/nat/nat-service.js +602 -237
- package/dist/nat/nat-service.js.map +1 -1
- package/dist/nat/nat-store.d.ts +12 -5
- package/dist/nat/nat-store.js +114 -30
- package/dist/nat/nat-store.js.map +1 -1
- package/dist/nat/port-mapper.d.ts +83 -79
- package/dist/nat/port-mapper.js +148 -189
- package/dist/nat/port-mapper.js.map +1 -1
- package/dist/nat/reachability.d.ts +31 -15
- package/dist/nat/reachability.js +97 -22
- package/dist/nat/reachability.js.map +1 -1
- package/dist/nat/types.d.ts +78 -27
- package/dist/nat/types.js +8 -5
- package/dist/nat/types.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +29 -71
- package/dist/orchestrator/host-process-orchestrator.js +78 -218
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
- package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
- package/dist/orchestrator/identity-file.js.map +1 -0
- package/dist/orchestrator/index.d.ts +2 -3
- package/dist/orchestrator/index.js +1 -1
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/node-identity.d.ts +6 -6
- package/dist/orchestrator/node-identity.js +7 -7
- package/dist/orchestrator/node-identity.js.map +1 -1
- package/dist/orchestrator/port-allocator.d.ts +5 -6
- package/dist/orchestrator/port-allocator.js +9 -11
- package/dist/orchestrator/port-allocator.js.map +1 -1
- package/dist/orchestrator/state-store.d.ts +3 -5
- package/dist/orchestrator/state-store.js +2 -10
- package/dist/orchestrator/state-store.js.map +1 -1
- package/dist/orchestrator/types.d.ts +33 -22
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/server/error-handler.d.ts +5 -25
- package/dist/server/error-handler.js +11 -74
- package/dist/server/error-handler.js.map +1 -1
- package/dist/server/events/types.d.ts +7 -11
- package/dist/server/events/types.js +2 -1
- package/dist/server/events/types.js.map +1 -1
- package/dist/server/index.d.ts +11 -37
- package/dist/server/index.js +30 -48
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/hosted-nodes.d.ts +28 -0
- package/dist/server/routes/hosted-nodes.js +68 -0
- package/dist/server/routes/hosted-nodes.js.map +1 -0
- package/dist/server/routes/nat.d.ts +4 -1
- package/dist/server/routes/nat.js +13 -2
- package/dist/server/routes/nat.js.map +1 -1
- package/dist/server/routes/nodes.d.ts +6 -20
- package/dist/server/routes/nodes.js +7 -86
- package/dist/server/routes/nodes.js.map +1 -1
- package/dist/server/routes/settings.d.ts +2 -7
- package/dist/server/routes/settings.js +3 -7
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/status.d.ts +2 -25
- package/dist/server/routes/status.js +1 -11
- package/dist/server/routes/status.js.map +1 -1
- package/dist/server/server.js +8 -8
- package/dist/server/settings-store.d.ts +2 -2
- package/dist/server/settings-store.js +2 -2
- package/dist/server/static.js +1 -2
- package/dist/server/static.js.map +1 -1
- package/dist/ui/assets/index-4FWuU8ye.js +19 -0
- package/dist/ui/assets/index-4FWuU8ye.js.map +1 -0
- package/dist/ui/assets/index-Di9Q4d01.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/store.d.ts +1 -1
- package/dist/update/store.js +1 -1
- package/dist/update/types.d.ts +1 -1
- package/dist/update/types.js +1 -1
- package/package.json +5 -6
- package/src/__tests__/cli-nat.smoke.test.ts +219 -212
- package/src/__tests__/cli.smoke.test.ts +1 -1
- package/src/__tests__/global-setup.ts +2 -3
- package/src/__tests__/orchestrator-claim-secret.test.ts +112 -0
- package/src/__tests__/orchestrator-handle-contract.ts +134 -134
- package/src/__tests__/orchestrator-node-identity.test.ts +182 -203
- package/src/__tests__/orchestrator-ports.test.ts +124 -143
- package/src/__tests__/orchestrator-push.test.ts +138 -146
- package/src/__tests__/orchestrator.test.ts +1019 -1063
- package/src/bin/host.ts +368 -553
- package/src/bin/join-output.ts +120 -0
- package/src/bin/nat-output.ts +177 -0
- package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +173 -173
- package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +265 -243
- package/src/hosted/__tests__/hosted-node-service.test.ts +609 -0
- package/src/hosted/__tests__/hosted-node-supervisor.test.ts +474 -0
- package/src/hosted/__tests__/test-invitation.ts +33 -0
- package/src/hosted/claim-details.ts +96 -0
- package/src/hosted/hosted-node-service.ts +795 -0
- package/src/hosted/hosted-node-store.ts +144 -0
- package/src/hosted/hosted-node-supervisor.ts +439 -0
- package/src/hosted/hosted-node-watcher.ts +250 -0
- package/src/hosted/index.ts +53 -0
- package/src/hosted/node-status.ts +71 -0
- package/src/hosted/types.ts +153 -0
- package/src/index.ts +72 -91
- package/src/installer/__tests__/config.test.ts +15 -40
- package/src/installer/__tests__/installer.smoke.test.ts +5 -86
- package/src/installer/__tests__/wizard.test.ts +56 -64
- package/src/installer/config.ts +135 -175
- package/src/installer/index.ts +15 -83
- package/src/installer/paths.ts +104 -109
- package/src/installer/wizard.ts +87 -103
- package/src/nat/__tests__/address-resolver.test.ts +107 -82
- package/src/nat/__tests__/nat-service.test.ts +433 -408
- package/src/nat/__tests__/nat-store.test.ts +127 -93
- package/src/nat/__tests__/port-mapper.test.ts +30 -191
- package/src/nat/__tests__/reachability.test.ts +96 -13
- package/src/nat/address-resolver.ts +94 -52
- package/src/nat/address-watch.ts +92 -0
- package/src/nat/ddns/duckdns.ts +5 -1
- package/src/nat/ddns/index.ts +6 -0
- package/src/nat/index.ts +87 -71
- package/src/nat/nat-service.ts +889 -501
- package/src/nat/nat-store.ts +237 -154
- package/src/nat/port-mapper.ts +241 -329
- package/src/nat/reachability.ts +134 -25
- package/src/nat/types.ts +209 -159
- package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
- package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
- package/src/orchestrator/host-process-orchestrator.ts +81 -243
- package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
- package/src/orchestrator/index.ts +9 -10
- package/src/orchestrator/node-identity.ts +41 -41
- package/src/orchestrator/port-allocator.ts +71 -73
- package/src/orchestrator/state-store.ts +76 -90
- package/src/orchestrator/types.ts +34 -23
- package/src/server/__tests__/bus.test.ts +38 -38
- package/src/server/__tests__/error-handler.test.ts +81 -118
- package/src/server/__tests__/fakes.ts +40 -44
- package/src/server/__tests__/hosted-nodes-route.test.ts +160 -0
- package/src/server/__tests__/nodes-route.test.ts +114 -283
- package/src/server/__tests__/publishers.test.ts +193 -180
- package/src/server/__tests__/server.smoke.test.ts +134 -223
- package/src/server/__tests__/settings-route.test.ts +179 -195
- package/src/server/__tests__/sse-route.test.ts +119 -119
- package/src/server/__tests__/status-route.test.ts +82 -121
- package/src/server/error-handler.ts +112 -178
- package/src/server/events/types.ts +20 -24
- package/src/server/index.ts +223 -267
- package/src/server/routes/hosted-nodes.ts +83 -0
- package/src/server/routes/nat.ts +75 -63
- package/src/server/routes/nodes.ts +96 -188
- package/src/server/routes/settings.ts +115 -123
- package/src/server/routes/status.ts +102 -134
- package/src/server/server.ts +8 -8
- package/src/server/settings-store.ts +2 -2
- package/src/server/static.ts +1 -2
- package/src/update/index.ts +2 -2
- package/src/update/store.ts +1 -1
- package/src/update/types.ts +1 -1
- package/dist/auth/duration.d.ts +0 -1
- package/dist/auth/duration.js +0 -32
- package/dist/auth/duration.js.map +0 -1
- package/dist/auth/index.d.ts +0 -11
- package/dist/auth/index.js +0 -9
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/self-label.d.ts +0 -36
- package/dist/auth/self-label.js +0 -57
- package/dist/auth/self-label.js.map +0 -1
- package/dist/auth/trust-circle-store.d.ts +0 -26
- package/dist/auth/trust-circle-store.js +0 -129
- package/dist/auth/trust-circle-store.js.map +0 -1
- package/dist/auth/trust-circle.d.ts +0 -156
- package/dist/auth/trust-circle.js +0 -307
- package/dist/auth/trust-circle.js.map +0 -1
- package/dist/auth/types.d.ts +0 -84
- package/dist/auth/types.js +0 -24
- package/dist/auth/types.js.map +0 -1
- package/dist/donation/donation-service.d.ts +0 -389
- package/dist/donation/donation-service.js +0 -828
- package/dist/donation/donation-service.js.map +0 -1
- package/dist/donation/donation-store.d.ts +0 -51
- package/dist/donation/donation-store.js +0 -152
- package/dist/donation/donation-store.js.map +0 -1
- package/dist/donation/donation-supervisor.js +0 -364
- package/dist/donation/donation-supervisor.js.map +0 -1
- package/dist/donation/grant-service.d.ts +0 -80
- package/dist/donation/grant-service.js +0 -210
- package/dist/donation/grant-service.js.map +0 -1
- package/dist/donation/grant-store.d.ts +0 -38
- package/dist/donation/grant-store.js +0 -135
- package/dist/donation/grant-store.js.map +0 -1
- package/dist/donation/index.d.ts +0 -20
- package/dist/donation/index.js +0 -17
- package/dist/donation/index.js.map +0 -1
- package/dist/donation/types.d.ts +0 -224
- package/dist/donation/types.js +0 -40
- package/dist/donation/types.js.map +0 -1
- package/dist/installer/identity.js.map +0 -1
- package/dist/owner/index.d.ts +0 -11
- package/dist/owner/index.js +0 -11
- package/dist/owner/index.js.map +0 -1
- package/dist/owner/owner-node-client.d.ts +0 -110
- package/dist/owner/owner-node-client.js +0 -184
- package/dist/owner/owner-node-client.js.map +0 -1
- package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
- package/dist/server/routes/bootstrap-node-validation.js +0 -197
- package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
- package/dist/server/routes/grants-admin.d.ts +0 -29
- package/dist/server/routes/grants-admin.js +0 -58
- package/dist/server/routes/grants-admin.js.map +0 -1
- package/dist/server/routes/grants.d.ts +0 -28
- package/dist/server/routes/grants.js +0 -165
- package/dist/server/routes/grants.js.map +0 -1
- package/dist/server/routes/provision-request-validation.d.ts +0 -45
- package/dist/server/routes/provision-request-validation.js +0 -97
- package/dist/server/routes/provision-request-validation.js.map +0 -1
- package/dist/server/routes/strands.d.ts +0 -20
- package/dist/server/routes/strands.js +0 -38
- package/dist/server/routes/strands.js.map +0 -1
- package/dist/server/routes/trust-circle.d.ts +0 -17
- package/dist/server/routes/trust-circle.js +0 -35
- package/dist/server/routes/trust-circle.js.map +0 -1
- package/dist/strands/index.d.ts +0 -7
- package/dist/strands/index.js +0 -6
- package/dist/strands/index.js.map +0 -1
- package/dist/strands/strand-service.d.ts +0 -50
- package/dist/strands/strand-service.js +0 -103
- package/dist/strands/strand-service.js.map +0 -1
- package/dist/strands/types.d.ts +0 -93
- package/dist/strands/types.js +0 -23
- package/dist/strands/types.js.map +0 -1
- package/dist/ui/assets/index-C6nELNfC.js +0 -23
- package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
- package/dist/ui/assets/index-TtIYSeZO.css +0 -1
- package/src/__tests__/cli-invite.smoke.test.ts +0 -192
- package/src/__tests__/host-owner.smoke.test.ts +0 -123
- package/src/__tests__/orchestrator-owner.test.ts +0 -234
- package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
- package/src/auth/__tests__/duration.test.ts +0 -28
- package/src/auth/__tests__/self-label.test.ts +0 -85
- package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
- package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
- package/src/auth/__tests__/trust-circle.test.ts +0 -445
- package/src/auth/duration.ts +0 -33
- package/src/auth/index.ts +0 -21
- package/src/auth/self-label.ts +0 -74
- package/src/auth/trust-circle-store.ts +0 -155
- package/src/auth/trust-circle.ts +0 -395
- package/src/auth/types.ts +0 -99
- package/src/donation/__tests__/donation-service.test.ts +0 -1193
- package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
- package/src/donation/__tests__/grant-service.test.ts +0 -213
- package/src/donation/__tests__/grant-store.test.ts +0 -113
- package/src/donation/donation-service.ts +0 -1033
- package/src/donation/donation-store.ts +0 -176
- package/src/donation/donation-supervisor.ts +0 -411
- package/src/donation/grant-service.ts +0 -252
- package/src/donation/grant-store.ts +0 -152
- package/src/donation/index.ts +0 -65
- package/src/donation/types.ts +0 -260
- package/src/owner/__tests__/owner-node-client.test.ts +0 -194
- package/src/owner/index.ts +0 -12
- package/src/owner/owner-node-client.ts +0 -259
- package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
- package/src/server/__tests__/grants-admin-route.test.ts +0 -171
- package/src/server/__tests__/grants-route.test.ts +0 -394
- package/src/server/__tests__/strands-route.test.ts +0 -234
- package/src/server/routes/bootstrap-node-validation.ts +0 -206
- package/src/server/routes/grants-admin.ts +0 -74
- package/src/server/routes/grants.ts +0 -193
- package/src/server/routes/provision-request-validation.ts +0 -105
- package/src/server/routes/strands.ts +0 -54
- package/src/server/routes/trust-circle.ts +0 -49
- package/src/strands/__tests__/strand-service.test.ts +0 -165
- package/src/strands/index.ts +0 -14
- package/src/strands/strand-service.ts +0 -135
- package/src/strands/types.ts +0 -102
|
@@ -1,52 +1,94 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Build the multiaddrs
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export interface
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Build the public multiaddrs of one hosted node from the host's current
|
|
3
|
+
* understanding of its dialable surface.
|
|
4
|
+
*
|
|
5
|
+
* Pure so it's trivial to unit-test, and so `NatService` is the only place
|
|
6
|
+
* that turns settings and routes into addresses. The addresses carry no
|
|
7
|
+
* `/p2p/` suffix: the node appends its own (cadre-core `normalizeSelfAddrs`,
|
|
8
|
+
* libp2p for announce addresses). The node's own LAN addresses are not here
|
|
9
|
+
* either — libp2p reports those itself.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { PortRoute } from './types.js';
|
|
13
|
+
|
|
14
|
+
export interface PublicAddressInput {
|
|
15
|
+
ddnsHostname: string | null;
|
|
16
|
+
/** Last known public IPv4; null when unknown. */
|
|
17
|
+
externalIp: string | null;
|
|
18
|
+
cgnatDetected: boolean;
|
|
19
|
+
tcp: PortRoute;
|
|
20
|
+
ws: PortRoute | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function buildPublicAddresses(input: PublicAddressInput): string[] {
|
|
24
|
+
const host = publicHost(input);
|
|
25
|
+
if (!host) return [];
|
|
26
|
+
const out: string[] = [];
|
|
27
|
+
const tcpPort = routedPort(input.tcp, input.cgnatDetected);
|
|
28
|
+
if (tcpPort !== null) out.push(`${host}/tcp/${tcpPort}`);
|
|
29
|
+
const wsPort = input.ws ? routedPort(input.ws, input.cgnatDetected) : null;
|
|
30
|
+
if (wsPort !== null) out.push(`${host}/tcp/${wsPort}/ws`);
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `/dns4/<hostname>` when a DDNS hostname is configured (externally managed
|
|
36
|
+
* included), else `/ip4/<externalIp>` when that is a public IPv4, else none.
|
|
37
|
+
*
|
|
38
|
+
* A hostname that is not a DNS name is skipped rather than used: these addresses
|
|
39
|
+
* reach each node as `CADRE_APPEND_ANNOUNCE_ADDRS`, a comma-separated list that
|
|
40
|
+
* cadre-core validates at start, so one bad entry (a pasted URL, a comma) would
|
|
41
|
+
* stop every hosted node from starting.
|
|
42
|
+
*/
|
|
43
|
+
function publicHost(input: PublicAddressInput): string | null {
|
|
44
|
+
if (input.ddnsHostname && isDnsHostname(input.ddnsHostname)) return `/dns4/${input.ddnsHostname}`;
|
|
45
|
+
if (input.externalIp && isPublicIpv4(input.externalIp)) return `/ip4/${input.externalIp}`;
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** One RFC 1123 label: letters, digits and inner hyphens, 1–63 characters. */
|
|
50
|
+
const DNS_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
51
|
+
|
|
52
|
+
/** True for a hostname of dot-separated RFC 1123 labels, at most 253 characters. */
|
|
53
|
+
export function isDnsHostname(s: string): boolean {
|
|
54
|
+
return s.length <= 253 && s.split('.').every((label) => DNS_LABEL.test(label));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Under CGNAT a UPnP route produces nothing: the router's mapping is on a
|
|
59
|
+
* carrier-private address. A manual route still does, since the user asserted
|
|
60
|
+
* the forward (the CGNAT check can misfire).
|
|
61
|
+
*/
|
|
62
|
+
function routedPort(route: PortRoute, cgnatDetected: boolean): number | null {
|
|
63
|
+
if (route.externalPort === null || route.source === null) return null;
|
|
64
|
+
if (cgnatDetected && route.source === 'upnp') return null;
|
|
65
|
+
return route.externalPort;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Private, carrier-grade, loopback, link-local, multicast and reserved IPv4 ranges. */
|
|
69
|
+
const NON_PUBLIC_IPV4: ReadonlyArray<[number, number]> = [
|
|
70
|
+
[0x00000000, 8], // 0.0.0.0/8
|
|
71
|
+
[0x0a000000, 8], // 10.0.0.0/8
|
|
72
|
+
[0x64400000, 10], // 100.64.0.0/10 (carrier-grade NAT)
|
|
73
|
+
[0x7f000000, 8], // 127.0.0.0/8
|
|
74
|
+
[0xa9fe0000, 16], // 169.254.0.0/16
|
|
75
|
+
[0xac100000, 12], // 172.16.0.0/12
|
|
76
|
+
[0xc0a80000, 16], // 192.168.0.0/16
|
|
77
|
+
[0xe0000000, 4], // 224.0.0.0/4 (multicast)
|
|
78
|
+
[0xf0000000, 4], // 240.0.0.0/4 (reserved, broadcast)
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/** True for a dotted-quad IPv4 outside every non-public range above. */
|
|
82
|
+
export function isPublicIpv4(s: string): boolean {
|
|
83
|
+
const octets = parseIpv4(s);
|
|
84
|
+
if (!octets) return false;
|
|
85
|
+
const value = ((octets[0]! << 24) | (octets[1]! << 16) | (octets[2]! << 8) | octets[3]!) >>> 0;
|
|
86
|
+
return !NON_PUBLIC_IPV4.some(([base, prefix]) => (value >>> (32 - prefix)) === (base >>> (32 - prefix)));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseIpv4(s: string): number[] | null {
|
|
90
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(s);
|
|
91
|
+
if (!m) return null;
|
|
92
|
+
const octets = m.slice(1).map(Number);
|
|
93
|
+
return octets.every((n) => n >= 0 && n <= 255) ? octets : null;
|
|
94
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
|
|
3
|
+
import type { ManagedNodeInfo } from '../orchestrator/types.js';
|
|
4
|
+
|
|
5
|
+
const log = debug('cadre:host:nat-address-watch');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The shortest gap between two address restarts of one node. Bounds a flapping
|
|
9
|
+
* router (a mapping that keeps failing and coming back) to one restart per node
|
|
10
|
+
* per window; a difference found inside the window waits for the first check
|
|
11
|
+
* after it ends.
|
|
12
|
+
*/
|
|
13
|
+
export const NAT_ADDRESS_RESTART_MIN_INTERVAL_MS = 10 * 60_000;
|
|
14
|
+
|
|
15
|
+
/** Asked to restart one node so it starts announcing its current public addresses. */
|
|
16
|
+
export type NodeAddressesStaleListener = (nodeId: string) => void | Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Notices a running node whose announced public addresses differ from the ones
|
|
20
|
+
* it should announce now, and asks its listeners to restart that node. A node
|
|
21
|
+
* learns its public addresses only at start (`CADRE_APPEND_ANNOUNCE_ADDRS`), so
|
|
22
|
+
* a restart is how a changed mapping, forward, DDNS hostname or external IP
|
|
23
|
+
* reaches it.
|
|
24
|
+
*/
|
|
25
|
+
export class AddressWatch {
|
|
26
|
+
private readonly listeners = new Set<NodeAddressesStaleListener>();
|
|
27
|
+
/** When each node was last asked to restart (ms since epoch). */
|
|
28
|
+
private readonly lastRestartAt = new Map<string, number>();
|
|
29
|
+
|
|
30
|
+
constructor(private readonly now: () => number) {}
|
|
31
|
+
|
|
32
|
+
/** Register a restart listener. Returns an unsubscribe fn. */
|
|
33
|
+
subscribe(listener: NodeAddressesStaleListener): () => void {
|
|
34
|
+
this.listeners.add(listener);
|
|
35
|
+
return () => { this.listeners.delete(listener); };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Compare every running node's announced addresses with `expected(node)`, as
|
|
40
|
+
* sets, and ask for a restart of each that differs and was not restarted
|
|
41
|
+
* within {@link NAT_ADDRESS_RESTART_MIN_INTERVAL_MS}.
|
|
42
|
+
*/
|
|
43
|
+
check(nodes: ManagedNodeInfo[], expected: (node: ManagedNodeInfo) => string[]): void {
|
|
44
|
+
this.forgetMissing(nodes);
|
|
45
|
+
if (this.listeners.size === 0) return;
|
|
46
|
+
for (const node of nodes) {
|
|
47
|
+
if (node.status !== 'running') continue;
|
|
48
|
+
if (sameAddressSet(node.announcedAddrs, expected(node))) continue;
|
|
49
|
+
if (this.restartedRecently(node.id)) {
|
|
50
|
+
log('node %s announces stale addresses; restart deferred by the rate limit', node.id);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
this.lastRestartAt.set(node.id, this.now());
|
|
54
|
+
log('node %s announces stale addresses; asking for a restart', node.id);
|
|
55
|
+
this.notify(node.id);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A terminated node's id may come back as a fresh node, which owes nothing to the old window. */
|
|
60
|
+
private forgetMissing(nodes: ManagedNodeInfo[]): void {
|
|
61
|
+
const listed = new Set(nodes.map((n) => n.id));
|
|
62
|
+
for (const id of this.lastRestartAt.keys()) {
|
|
63
|
+
if (!listed.has(id)) this.lastRestartAt.delete(id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private restartedRecently(nodeId: string): boolean {
|
|
68
|
+
const last = this.lastRestartAt.get(nodeId);
|
|
69
|
+
return last !== undefined && this.now() - last < NAT_ADDRESS_RESTART_MIN_INTERVAL_MS;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Fire-and-forget: a restart takes seconds and must not hold up the NAT pass that found it. */
|
|
73
|
+
private notify(nodeId: string): void {
|
|
74
|
+
for (const listener of this.listeners) {
|
|
75
|
+
try {
|
|
76
|
+
void Promise.resolve(listener(nodeId)).catch((err: unknown) => { logRestartFailure(nodeId, err); });
|
|
77
|
+
} catch (err) {
|
|
78
|
+
logRestartFailure(nodeId, err);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sameAddressSet(a: readonly string[], b: readonly string[]): boolean {
|
|
85
|
+
const left = new Set(a);
|
|
86
|
+
const right = new Set(b);
|
|
87
|
+
return left.size === right.size && [...left].every((addr) => right.has(addr));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function logRestartFailure(nodeId: string, err: unknown): void {
|
|
91
|
+
log('address restart of node %s failed: %s', nodeId, err instanceof Error ? err.message : String(err));
|
|
92
|
+
}
|
package/src/nat/ddns/duckdns.ts
CHANGED
|
@@ -13,7 +13,8 @@ const log = debug('cadre:host:nat-ddns:duckdns');
|
|
|
13
13
|
* Response is plain text — `OK` on success, `KO` on failure.
|
|
14
14
|
*
|
|
15
15
|
* `hostname` may be supplied as the bare subdomain (`foo`) or the FQDN
|
|
16
|
-
* (`foo.duckdns.org`);
|
|
16
|
+
* (`foo.duckdns.org`); the update strips the suffix, and `qualifyHostname`
|
|
17
|
+
* adds it.
|
|
17
18
|
*/
|
|
18
19
|
export const duckDnsProvider: DdnsProvider = {
|
|
19
20
|
id: 'duckdns',
|
|
@@ -21,6 +22,9 @@ export const duckDnsProvider: DdnsProvider = {
|
|
|
21
22
|
configFields: [
|
|
22
23
|
{ key: 'token', label: 'DuckDNS token', secret: true },
|
|
23
24
|
],
|
|
25
|
+
qualifyHostname(hostname: string): string {
|
|
26
|
+
return `${stripSuffix(hostname)}${SUFFIX}`;
|
|
27
|
+
},
|
|
24
28
|
async update(
|
|
25
29
|
config: Record<string, string>,
|
|
26
30
|
hostname: string,
|
package/src/nat/ddns/index.ts
CHANGED
|
@@ -19,6 +19,12 @@ export interface DdnsProvider {
|
|
|
19
19
|
readonly id: string;
|
|
20
20
|
readonly displayName: string;
|
|
21
21
|
readonly configFields: ReadonlyArray<DdnsConfigField>;
|
|
22
|
+
/**
|
|
23
|
+
* The fully qualified name the provider publishes for a hostname as the user
|
|
24
|
+
* typed it. Stored in place of the typed form because hosted nodes announce the
|
|
25
|
+
* stored hostname as `/dns4/<hostname>`, so it has to resolve on its own.
|
|
26
|
+
*/
|
|
27
|
+
qualifyHostname(hostname: string): string;
|
|
22
28
|
/** Push `ip` for `hostname`. Throws NatError(ddns_update_failed) on failure. */
|
|
23
29
|
update(
|
|
24
30
|
config: Record<string, string>,
|
package/src/nat/index.ts
CHANGED
|
@@ -1,71 +1,87 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NAT / DDNS substrate for cadre-host.
|
|
3
|
-
*
|
|
4
|
-
* See ./nat-service.ts for the top-level orchestrator and `docs/cadre-host.md`
|
|
5
|
-
* for integration notes (the long-running process owner is `cadre-host
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} from './
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
} from './
|
|
64
|
-
export {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
1
|
+
/**
|
|
2
|
+
* NAT / DDNS substrate for cadre-host.
|
|
3
|
+
*
|
|
4
|
+
* See ./nat-service.ts for the top-level orchestrator and `docs/cadre-host.md`
|
|
5
|
+
* for integration notes (the long-running process owner is `cadre-host start`).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
NatService,
|
|
10
|
+
createNatHandlers,
|
|
11
|
+
NAT_UNMAP_GRACE_MS,
|
|
12
|
+
NAT_RECONCILE_INTERVAL_MS,
|
|
13
|
+
NAT_IP_REDETECT_INTERVAL_MS,
|
|
14
|
+
} from './nat-service.js';
|
|
15
|
+
export type { NatServiceOptions, NatNodeSource, NatChangeListener } from './nat-service.js';
|
|
16
|
+
export { NAT_ADDRESS_RESTART_MIN_INTERVAL_MS } from './address-watch.js';
|
|
17
|
+
export type { NodeAddressesStaleListener } from './address-watch.js';
|
|
18
|
+
export { NatStore } from './nat-store.js';
|
|
19
|
+
export {
|
|
20
|
+
NatError,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
export type {
|
|
23
|
+
NatStatusSnapshot,
|
|
24
|
+
NatGatewayStatus,
|
|
25
|
+
NatDdnsStatus,
|
|
26
|
+
NatSettingsFile,
|
|
27
|
+
NatDdnsSettings,
|
|
28
|
+
NatHandlers,
|
|
29
|
+
NatErrorCode,
|
|
30
|
+
DirectReachability,
|
|
31
|
+
NodeReachability,
|
|
32
|
+
NodeVerdict,
|
|
33
|
+
PortKind,
|
|
34
|
+
PortRoute,
|
|
35
|
+
PortRouteSource,
|
|
36
|
+
ManualForward,
|
|
37
|
+
ManualForwardPatch,
|
|
38
|
+
DdnsProviderInfo,
|
|
39
|
+
DdnsConfigField,
|
|
40
|
+
} from './types.js';
|
|
41
|
+
export {
|
|
42
|
+
ExternalIpDetector,
|
|
43
|
+
isPlausibleIp,
|
|
44
|
+
} from './external-ip.js';
|
|
45
|
+
export type {
|
|
46
|
+
ExternalIpResult,
|
|
47
|
+
RouterIpProbe,
|
|
48
|
+
ExternalIpDetectorOptions,
|
|
49
|
+
} from './external-ip.js';
|
|
50
|
+
export {
|
|
51
|
+
UpnpPortMapper,
|
|
52
|
+
pickLanAddress,
|
|
53
|
+
DEFAULT_LEASE_TTL_MS,
|
|
54
|
+
DEFAULT_REFRESH_MS,
|
|
55
|
+
GATEWAY_DISCOVERY_TIMEOUT_MS,
|
|
56
|
+
} from './port-mapper.js';
|
|
57
|
+
export type {
|
|
58
|
+
PortMapper,
|
|
59
|
+
GatewayInfo,
|
|
60
|
+
PortMapRequest,
|
|
61
|
+
PortMapResult,
|
|
62
|
+
} from './port-mapper.js';
|
|
63
|
+
export { evaluateNodeReachability, evaluateHostReachability } from './reachability.js';
|
|
64
|
+
export type { NodeVerdictInput, NodeVerdictResult } from './reachability.js';
|
|
65
|
+
export { buildPublicAddresses, isPublicIpv4 } from './address-resolver.js';
|
|
66
|
+
export type { PublicAddressInput } from './address-resolver.js';
|
|
67
|
+
export {
|
|
68
|
+
BUILTIN_PROVIDERS,
|
|
69
|
+
getProvider,
|
|
70
|
+
listProviders,
|
|
71
|
+
duckDnsProvider,
|
|
72
|
+
DdnsUpdater,
|
|
73
|
+
} from './ddns/index.js';
|
|
74
|
+
export type {
|
|
75
|
+
DdnsProvider,
|
|
76
|
+
DdnsProviderDeps,
|
|
77
|
+
DdnsUpdaterOptions,
|
|
78
|
+
DdnsUpdateOutcome,
|
|
79
|
+
} from './ddns/index.js';
|
|
80
|
+
export {
|
|
81
|
+
KeytarSecretsStore,
|
|
82
|
+
FileSecretsStore,
|
|
83
|
+
createSecretsStore,
|
|
84
|
+
ddnsAccount,
|
|
85
|
+
KEYTAR_SERVICE,
|
|
86
|
+
} from './secrets/index.js';
|
|
87
|
+
export type { SecretsStore, KeytarLike } from './secrets/index.js';
|