@serfab/cadre-host 1.12.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
package/dist/nat/port-mapper.js
CHANGED
|
@@ -1,225 +1,184 @@
|
|
|
1
|
+
import { BlockList } from 'node:net';
|
|
2
|
+
import { networkInterfaces } from 'node:os';
|
|
1
3
|
import debug from 'debug';
|
|
4
|
+
import { upnpNat } from '@achingbrain/nat-port-mapper';
|
|
2
5
|
import { NatError } from './types.js';
|
|
3
6
|
const log = debug('cadre:host:nat-port-mapper');
|
|
4
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Default lease TTL: 1 hour. `@achingbrain/nat-port-mapper` takes the TTL in
|
|
9
|
+
* milliseconds and floors the lease it asks the router for at 3600 s, so a
|
|
10
|
+
* shorter value here would still lease an hour.
|
|
11
|
+
*/
|
|
5
12
|
export const DEFAULT_LEASE_TTL_MS = 60 * 60 * 1000;
|
|
6
|
-
/** Default
|
|
13
|
+
/** Default renewal cadence: half the TTL, so two renewals per lease. */
|
|
7
14
|
export const DEFAULT_REFRESH_MS = 30 * 60 * 1000;
|
|
15
|
+
/** How long to listen for a UPnP gateway's SSDP answer before giving up. */
|
|
16
|
+
export const GATEWAY_DISCOVERY_TIMEOUT_MS = 10000;
|
|
17
|
+
/** Bound on one SOAP call to the router (map, unmap, external IP). */
|
|
18
|
+
export const GATEWAY_OPERATION_TIMEOUT_MS = 10000;
|
|
19
|
+
/** Signal that aborts after `ms`; the timer is cleared by `done()` so it never holds the process open. */
|
|
20
|
+
function deadline(ms) {
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const timer = setTimeout(() => controller.abort(new Error(`timed out after ${ms} ms`)), ms);
|
|
23
|
+
timer.unref();
|
|
24
|
+
return { signal: controller.signal, done: () => clearTimeout(timer) };
|
|
25
|
+
}
|
|
8
26
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
27
|
+
* Pick the IPv4 address of the local interface whose subnet contains the router.
|
|
28
|
+
*
|
|
29
|
+
* Not `mapAll`: that maps on every local address and leaves stray mappings for
|
|
30
|
+
* VPN and container interfaces. The address returned is the one a user is told
|
|
31
|
+
* to forward to, so it must be the one on the router's own network.
|
|
11
32
|
*/
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
export function pickLanAddress(routerHost, interfaces = networkInterfaces()) {
|
|
34
|
+
for (const entries of Object.values(interfaces)) {
|
|
35
|
+
for (const entry of entries ?? []) {
|
|
36
|
+
if (entry.family !== 'IPv4' || entry.internal || !entry.cidr)
|
|
37
|
+
continue;
|
|
38
|
+
const prefix = Number(entry.cidr.split('/')[1]);
|
|
39
|
+
if (!Number.isInteger(prefix))
|
|
40
|
+
continue;
|
|
41
|
+
const subnet = new BlockList();
|
|
42
|
+
try {
|
|
43
|
+
subnet.addSubnet(entry.address, prefix, 'ipv4');
|
|
44
|
+
if (subnet.check(routerHost, 'ipv4'))
|
|
45
|
+
return entry.address;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
log('skipping interface entry %s (%s): %s', entry.address, entry.cidr, err.message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* UPnP port mapper over `@achingbrain/nat-port-mapper` v4: `upnpNat()` hands
|
|
56
|
+
* out a client that discovers gateways; mapping, unmapping and the external IP
|
|
57
|
+
* live on the discovered `Gateway`.
|
|
58
|
+
*
|
|
59
|
+
* The library's auto-refresh stays off: cadre-host renews leases itself so it
|
|
60
|
+
* can report lease expiry and per-mapping failures.
|
|
61
|
+
*/
|
|
62
|
+
export class UpnpPortMapper {
|
|
63
|
+
constructor(opts = {}) {
|
|
64
|
+
this.gateway = null;
|
|
65
|
+
this.lanAddress = null;
|
|
66
|
+
this.client = upnpNat({
|
|
23
67
|
description: opts.description ?? 'sereus-cadre-host',
|
|
24
|
-
|
|
68
|
+
autoRefresh: false,
|
|
25
69
|
});
|
|
26
70
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
catch (err) {
|
|
45
|
-
log('externalIp() failed: %s', err.message);
|
|
46
|
-
}
|
|
71
|
+
async discover() {
|
|
72
|
+
const gateway = await this.findIpv4Gateway();
|
|
73
|
+
if (!gateway)
|
|
74
|
+
return null;
|
|
75
|
+
const lanAddress = pickLanAddress(gateway.host);
|
|
76
|
+
if (!lanAddress) {
|
|
77
|
+
throw new NatError('router_unreachable', `found a UPnP router at ${gateway.host} but no local IPv4 interface shares its subnet`);
|
|
78
|
+
}
|
|
79
|
+
this.gateway = gateway;
|
|
80
|
+
this.lanAddress = lanAddress;
|
|
81
|
+
log('discovered gateway %s (%s), mapping from %s', gateway.id, gateway.host, lanAddress);
|
|
82
|
+
return { lanAddress, routerHost: gateway.host };
|
|
83
|
+
}
|
|
84
|
+
async map(req) {
|
|
85
|
+
const { gateway, lanAddress } = this.requireGateway();
|
|
86
|
+
const op = deadline(GATEWAY_OPERATION_TIMEOUT_MS);
|
|
47
87
|
try {
|
|
48
|
-
await
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
88
|
+
const mapping = await gateway.map(req.internalPort, lanAddress, {
|
|
89
|
+
externalPort: req.internalPort,
|
|
90
|
+
protocol: req.protocol,
|
|
91
|
+
ttl: req.ttlMs,
|
|
92
|
+
autoRefresh: false,
|
|
93
|
+
signal: op.signal,
|
|
53
94
|
});
|
|
95
|
+
return { externalPort: mapping.externalPort, leaseExpiresAt: new Date(Date.now() + req.ttlMs) };
|
|
54
96
|
}
|
|
55
97
|
catch (err) {
|
|
56
|
-
throw new NatError('mapping_failed', `router refused mapping for port ${
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
98
|
+
throw new NatError('mapping_failed', `router refused the mapping for port ${req.internalPort}: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
op.done();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// NOTE: the library tracks the mappings this process made and `unmap` only sends a
|
|
105
|
+
// delete for those, so after a host restart a port mapped by the previous process is
|
|
106
|
+
// left to expire with its lease rather than deleted. It also names the INTERNAL port
|
|
107
|
+
// as the external one in that delete, so a mapping the router granted on another
|
|
108
|
+
// external port is not deleted either; it, too, expires with its lease (1 h). And it
|
|
109
|
+
// appends one tracked entry per successful `map` of a port, so after N renewals an
|
|
110
|
+
// unmap sends N deletes (the first is the one that counts); they all run under the
|
|
111
|
+
// one 10 s bound, which caps the cost of a process that has renewed for days.
|
|
112
|
+
async unmap(internalPort, _protocol) {
|
|
113
|
+
if (!this.gateway)
|
|
114
|
+
return;
|
|
115
|
+
const op = deadline(GATEWAY_OPERATION_TIMEOUT_MS);
|
|
73
116
|
try {
|
|
74
|
-
await this.
|
|
75
|
-
return true;
|
|
117
|
+
await this.gateway.unmap(internalPort, { signal: op.signal });
|
|
76
118
|
}
|
|
77
119
|
catch (err) {
|
|
78
|
-
log('
|
|
79
|
-
|
|
120
|
+
log('unmap(%d) failed: %s', internalPort, err.message);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
op.done();
|
|
80
124
|
}
|
|
81
125
|
}
|
|
82
126
|
async externalIp() {
|
|
127
|
+
if (!this.gateway)
|
|
128
|
+
return null;
|
|
129
|
+
const op = deadline(GATEWAY_OPERATION_TIMEOUT_MS);
|
|
83
130
|
try {
|
|
84
|
-
return await this.
|
|
131
|
+
return await this.gateway.externalIp({ signal: op.signal });
|
|
85
132
|
}
|
|
86
133
|
catch (err) {
|
|
87
134
|
log('externalIp() failed: %s', err.message);
|
|
88
135
|
return null;
|
|
89
136
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
await this.client.unmap({ externalPort, protocol });
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
log('unmap(%d) failed: %s', externalPort, err.message);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
async stop() {
|
|
100
|
-
try {
|
|
101
|
-
await this.client.stop();
|
|
137
|
+
finally {
|
|
138
|
+
op.done();
|
|
102
139
|
}
|
|
103
|
-
catch (err) {
|
|
104
|
-
log('stop() failed: %s', err.message);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Long-running orchestrator over a PortMapper. Owns the refresh timer and
|
|
110
|
-
* surfaces the latest state for the NatService snapshot.
|
|
111
|
-
*/
|
|
112
|
-
export class PortMapperService {
|
|
113
|
-
constructor(opts) {
|
|
114
|
-
this.refreshHandle = null;
|
|
115
|
-
this.started = false;
|
|
116
|
-
this.mapper = opts.mapper;
|
|
117
|
-
this.externalPort = opts.externalPort;
|
|
118
|
-
this.internalPort = opts.internalPort;
|
|
119
|
-
this.protocol = opts.protocol ?? 'tcp';
|
|
120
|
-
this.ttlMs = opts.ttlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
121
|
-
this.refreshMs = opts.refreshMs ?? DEFAULT_REFRESH_MS;
|
|
122
|
-
this.nowFn = opts.now ?? (() => new Date());
|
|
123
|
-
this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
124
|
-
this.clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
|
|
125
|
-
this.state = {
|
|
126
|
-
mode: 'disabled',
|
|
127
|
-
externalPort: this.externalPort,
|
|
128
|
-
internalPort: this.internalPort,
|
|
129
|
-
routerExternalIp: null,
|
|
130
|
-
leaseExpiresAt: null,
|
|
131
|
-
lastError: null,
|
|
132
|
-
};
|
|
133
140
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (this.started)
|
|
140
|
-
return;
|
|
141
|
-
this.started = true;
|
|
142
|
-
await this.installMapping();
|
|
143
|
-
this.scheduleRefresh();
|
|
144
|
-
}
|
|
145
|
-
/** Release the mapping and clear timers. Idempotent. */
|
|
141
|
+
/**
|
|
142
|
+
* Forget the gateway. Deliberately not `gateway.stop()`: that deletes every
|
|
143
|
+
* mapping this process made, and hosted children keep running across a host
|
|
144
|
+
* restart, so their mappings must outlive this process.
|
|
145
|
+
*/
|
|
146
146
|
async stop() {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
147
|
+
this.gateway = null;
|
|
148
|
+
this.lanAddress = null;
|
|
149
|
+
}
|
|
150
|
+
requireGateway() {
|
|
151
|
+
if (!this.gateway || !this.lanAddress) {
|
|
152
|
+
throw new NatError('router_unreachable', 'no UPnP gateway discovered');
|
|
153
|
+
}
|
|
154
|
+
return { gateway: this.gateway, lanAddress: this.lanAddress };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* First IPv4 gateway to answer the SSDP search, or null when none did within
|
|
158
|
+
* the timeout.
|
|
159
|
+
*
|
|
160
|
+
* NOTE: the library searches for `InternetGatewayDevice:2` only (its
|
|
161
|
+
* `upnp/discovery.js`), and a router that implements only version 1 does not
|
|
162
|
+
* answer a version-2 search, so such a router reads as "not found". There is
|
|
163
|
+
* no library option for it; if it shows up on real hosts, the fix is a fork or
|
|
164
|
+
* another library.
|
|
165
|
+
*/
|
|
166
|
+
async findIpv4Gateway() {
|
|
167
|
+
const search = deadline(GATEWAY_DISCOVERY_TIMEOUT_MS);
|
|
154
168
|
try {
|
|
155
|
-
await this.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
await this.mapper.stop();
|
|
169
|
+
for await (const gateway of this.client.findGateways({ signal: search.signal })) {
|
|
170
|
+
if (gateway.family === 'IPv4')
|
|
171
|
+
return gateway;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
162
174
|
}
|
|
163
175
|
catch (err) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
...this.state,
|
|
168
|
-
mode: 'disabled',
|
|
169
|
-
routerExternalIp: null,
|
|
170
|
-
leaseExpiresAt: null,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
/** Force an immediate verify+refresh cycle (used by testReachability). */
|
|
174
|
-
async refresh() {
|
|
175
|
-
if (!this.started)
|
|
176
|
-
return this.getState();
|
|
177
|
-
const ok = await this.mapper.verify(this.externalPort, this.protocol).catch(() => false);
|
|
178
|
-
if (!ok) {
|
|
179
|
-
log('verify() returned false; re-mapping');
|
|
180
|
-
await this.installMapping();
|
|
181
|
-
return this.getState();
|
|
176
|
+
if (search.signal.aborted)
|
|
177
|
+
return null;
|
|
178
|
+
throw new NatError('router_unreachable', `UPnP discovery failed: ${err.message}`);
|
|
182
179
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return this.getState();
|
|
186
|
-
}
|
|
187
|
-
scheduleRefresh() {
|
|
188
|
-
if (this.refreshHandle)
|
|
189
|
-
this.clearTimer(this.refreshHandle);
|
|
190
|
-
this.refreshHandle = this.setTimer(() => {
|
|
191
|
-
void this.refresh().catch((err) => log('refresh timer error: %s', err.message));
|
|
192
|
-
// Re-arm after the call to avoid drift.
|
|
193
|
-
this.scheduleRefresh();
|
|
194
|
-
}, this.refreshMs);
|
|
195
|
-
}
|
|
196
|
-
async installMapping() {
|
|
197
|
-
try {
|
|
198
|
-
const r = await this.mapper.map({
|
|
199
|
-
externalPort: this.externalPort,
|
|
200
|
-
internalPort: this.internalPort,
|
|
201
|
-
protocol: this.protocol,
|
|
202
|
-
ttlMs: this.ttlMs,
|
|
203
|
-
});
|
|
204
|
-
this.state = {
|
|
205
|
-
mode: r.mode,
|
|
206
|
-
externalPort: r.externalPort,
|
|
207
|
-
internalPort: r.internalPort,
|
|
208
|
-
routerExternalIp: r.externalIp,
|
|
209
|
-
leaseExpiresAt: r.leaseExpiresAt,
|
|
210
|
-
lastError: null,
|
|
211
|
-
};
|
|
212
|
-
log('mapped port %d (mode=%s, externalIp=%s)', r.externalPort, r.mode, r.externalIp ?? '?');
|
|
213
|
-
}
|
|
214
|
-
catch (err) {
|
|
215
|
-
const message = err.message;
|
|
216
|
-
this.state = {
|
|
217
|
-
...this.state,
|
|
218
|
-
mode: 'failed',
|
|
219
|
-
leaseExpiresAt: null,
|
|
220
|
-
lastError: message,
|
|
221
|
-
};
|
|
222
|
-
log('mapping failed: %s', message);
|
|
180
|
+
finally {
|
|
181
|
+
search.done();
|
|
223
182
|
}
|
|
224
183
|
}
|
|
225
184
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port-mapper.js","sourceRoot":"","sources":["../../src/nat/port-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"port-mapper.js","sourceRoot":"","sources":["../../src/nat/port-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAA6B,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,OAAO,EAA8B,MAAM,8BAA8B,CAAC;AAEnF,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,GAAG,GAAG,KAAK,CAAC,4BAA4B,CAAC,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEnD,wEAAwE;AACxE,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEjD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,4BAA4B,GAAG,KAAM,CAAC;AAEnD,sEAAsE;AACtE,MAAM,CAAC,MAAM,4BAA4B,GAAG,KAAM,CAAC;AAiDnD,0GAA0G;AAC1G,SAAS,QAAQ,CAAC,EAAU;IAC1B,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5F,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;AACxE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAkB,EAClB,aAAiE,iBAAiB,EAAE;IAEpF,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAChD,KAAK,MAAM,KAAK,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;YAClC,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI;gBAAE,SAAS;YACvE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAChD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;gBAAE,SAAS;YACxC,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;gBAChD,IAAI,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC;oBAAE,OAAO,KAAK,CAAC,OAAO,CAAC;YAC7D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,sCAAsC,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;YACjG,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,cAAc;IAKzB,YAAY,OAAiC,EAAE;QAHvC,YAAO,GAAmB,IAAI,CAAC;QAC/B,eAAU,GAAkB,IAAI,CAAC;QAGvC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;YACpB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,mBAAmB;YACpD,WAAW,EAAE,KAAK;SACnB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,QAAQ,CAChB,oBAAoB,EACpB,0BAA0B,OAAO,CAAC,IAAI,gDAAgD,CACvF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,GAAG,CAAC,6CAA6C,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACzF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAmB;QAC3B,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACtD,MAAM,EAAE,GAAG,QAAQ,CAAC,4BAA4B,CAAC,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE;gBAC9D,YAAY,EAAE,GAAG,CAAC,YAAY;gBAC9B,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,GAAG,EAAE,GAAG,CAAC,KAAK;gBACd,WAAW,EAAE,KAAK;gBAClB,MAAM,EAAE,EAAE,CAAC,MAAM;aAClB,CAAC,CAAC;YACH,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,cAAc,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAClG,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,QAAQ,CAChB,gBAAgB,EAChB,uCAAuC,GAAG,CAAC,YAAY,KAAM,GAAa,CAAC,OAAO,EAAE,CACrF,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,IAAI,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,mFAAmF;IACnF,qFAAqF;IACrF,qFAAqF;IACrF,iFAAiF;IACjF,qFAAqF;IACrF,mFAAmF;IACnF,mFAAmF;IACnF,8EAA8E;IAC9E,KAAK,CAAC,KAAK,CAAC,YAAoB,EAAE,SAAwB;QACxD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,EAAE,GAAG,QAAQ,CAAC,4BAA4B,CAAC,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,sBAAsB,EAAE,YAAY,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;QACpE,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,IAAI,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC/B,MAAM,EAAE,GAAG,QAAQ,CAAC,4BAA4B,CAAC,CAAC;QAClD,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,yBAAyB,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;YACvD,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,IAAI,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;IAEO,cAAc;QACpB,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACtC,MAAM,IAAI,QAAQ,CAAC,oBAAoB,EAAE,4BAA4B,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IAChE,CAAC;IAED;;;;;;;;;OASG;IACK,KAAK,CAAC,eAAe;QAC3B,MAAM,MAAM,GAAG,QAAQ,CAAC,4BAA4B,CAAC,CAAC;QACtD,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,MAAM,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;gBAChF,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;oBAAE,OAAO,OAAO,CAAC;YAChD,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YACvC,MAAM,IAAI,QAAQ,CAAC,oBAAoB,EAAE,0BAA2B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/F,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,19 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Reachability verdicts: one per hosted node, and the host-level roll-up the
|
|
3
|
+
* dashboard badge shows. Heuristics over the mapping table, not a dial-back —
|
|
4
|
+
* libp2p AutoNAT is a later ticket.
|
|
5
|
+
*/
|
|
6
|
+
import type { DirectReachability, NodeReachability, NodeVerdict, PortRoute } from './types.js';
|
|
7
|
+
export interface NodeVerdictInput {
|
|
8
|
+
tcp: PortRoute;
|
|
9
|
+
/** Null for a handle persisted by an older build without a WebSocket port. */
|
|
10
|
+
ws: PortRoute | null;
|
|
11
|
+
/** Whether a public host part exists: a DDNS hostname, or a public IPv4. */
|
|
12
|
+
hostKnown: boolean;
|
|
4
13
|
cgnatDetected: boolean;
|
|
14
|
+
upnpEnabled: boolean;
|
|
15
|
+
gatewayFound: boolean;
|
|
16
|
+
/** The address a user forwards to; null when no gateway was found. */
|
|
17
|
+
lanAddress: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface NodeVerdictResult {
|
|
20
|
+
verdict: NodeVerdict;
|
|
21
|
+
reason: string | null;
|
|
5
22
|
}
|
|
6
23
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* dial-back probe (see the follow-up section of `6.3-cadre-host-nat`).
|
|
24
|
+
* `unreachable` when either port has no usable route or the host part is
|
|
25
|
+
* unknown; otherwise `manual` when either port is forwarded by hand, else
|
|
26
|
+
* `mapped`. A UPnP route under CGNAT is not usable: the router's mapping is on
|
|
27
|
+
* a carrier-private address.
|
|
28
|
+
*/
|
|
29
|
+
export declare function evaluateNodeReachability(input: NodeVerdictInput): NodeVerdictResult;
|
|
30
|
+
/**
|
|
31
|
+
* Host roll-up: `cgnat` when CGNAT is detected and no node is reachable
|
|
32
|
+
* through a manual route; `unknown` with no running nodes; `reachable` when
|
|
33
|
+
* every running node is `mapped`/`manual`; else `unreachable`.
|
|
18
34
|
*/
|
|
19
|
-
export declare function
|
|
35
|
+
export declare function evaluateHostReachability(nodes: ReadonlyArray<Pick<NodeReachability, 'running' | 'verdict'>>, cgnatDetected: boolean): DirectReachability;
|
package/dist/nat/reachability.js
CHANGED
|
@@ -1,29 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* - 'cgnat' when CGNAT was detected (highest signal — UPnP succeeded but our
|
|
6
|
-
* external IP isn't actually reachable from the open internet).
|
|
7
|
-
* - 'unreachable' when port mapping failed.
|
|
8
|
-
* - 'reachable' when an auto mapping succeeded.
|
|
9
|
-
* - 'unknown' otherwise (manual config, disabled UPnP, etc).
|
|
10
|
-
*
|
|
11
|
-
* A future ticket will replace this with libp2p AutoNAT or a sereus-owned
|
|
12
|
-
* dial-back probe (see the follow-up section of `6.3-cadre-host-nat`).
|
|
2
|
+
* Reachability verdicts: one per hosted node, and the host-level roll-up the
|
|
3
|
+
* dashboard badge shows. Heuristics over the mapping table, not a dial-back —
|
|
4
|
+
* libp2p AutoNAT is a later ticket.
|
|
13
5
|
*/
|
|
14
|
-
|
|
6
|
+
/**
|
|
7
|
+
* `unreachable` when either port has no usable route or the host part is
|
|
8
|
+
* unknown; otherwise `manual` when either port is forwarded by hand, else
|
|
9
|
+
* `mapped`. A UPnP route under CGNAT is not usable: the router's mapping is on
|
|
10
|
+
* a carrier-private address.
|
|
11
|
+
*/
|
|
12
|
+
export function evaluateNodeReachability(input) {
|
|
13
|
+
const ports = portRoutes(input);
|
|
14
|
+
const failing = ports.filter(({ route }) => !usable(route, input.cgnatDetected));
|
|
15
|
+
if (!input.hostKnown) {
|
|
16
|
+
return { verdict: 'unreachable', reason: HOST_UNKNOWN_REASON };
|
|
17
|
+
}
|
|
18
|
+
if (failing.length > 0) {
|
|
19
|
+
return { verdict: 'unreachable', reason: describeFailures(failing, input) };
|
|
20
|
+
}
|
|
21
|
+
const manual = ports.some(({ route }) => route.source === 'manual');
|
|
22
|
+
return { verdict: manual ? 'manual' : 'mapped', reason: null };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Host roll-up: `cgnat` when CGNAT is detected and no node is reachable
|
|
26
|
+
* through a manual route; `unknown` with no running nodes; `reachable` when
|
|
27
|
+
* every running node is `mapped`/`manual`; else `unreachable`.
|
|
28
|
+
*/
|
|
29
|
+
export function evaluateHostReachability(nodes, cgnatDetected) {
|
|
30
|
+
const running = nodes.filter((n) => n.running);
|
|
31
|
+
if (cgnatDetected && !running.some((n) => n.verdict === 'manual'))
|
|
32
|
+
return 'cgnat';
|
|
33
|
+
if (running.length === 0)
|
|
34
|
+
return 'unknown';
|
|
35
|
+
return running.every((n) => n.verdict !== 'unreachable') ? 'reachable' : 'unreachable';
|
|
36
|
+
}
|
|
37
|
+
const HOST_UNKNOWN_REASON = "This host's public address is unknown: no dynamic DNS hostname is set and no public IPv4 address was detected. " +
|
|
38
|
+
'Set a DDNS hostname, or check that this machine can reach the internet.';
|
|
39
|
+
function portRoutes(input) {
|
|
40
|
+
const out = [{ kind: 'tcp', route: input.tcp }];
|
|
41
|
+
if (input.ws)
|
|
42
|
+
out.push({ kind: 'ws', route: input.ws });
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function usable(route, cgnatDetected) {
|
|
46
|
+
if (route.externalPort === null || route.source === null)
|
|
47
|
+
return false;
|
|
48
|
+
return !(cgnatDetected && route.source === 'upnp');
|
|
49
|
+
}
|
|
50
|
+
/** Behind CGNAT no router-side remedy helps, so CGNAT outranks whatever else failed the port. */
|
|
51
|
+
function causeOf(route, input) {
|
|
15
52
|
if (input.cgnatDetected)
|
|
16
53
|
return 'cgnat';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
54
|
+
if (route.error)
|
|
55
|
+
return 'refused';
|
|
56
|
+
if (!input.upnpEnabled)
|
|
57
|
+
return 'upnp_off';
|
|
58
|
+
if (!input.gatewayFound)
|
|
59
|
+
return 'no_gateway';
|
|
60
|
+
return 'pending';
|
|
61
|
+
}
|
|
62
|
+
/** One sentence per distinct cause, each naming every port that failed for it. */
|
|
63
|
+
function describeFailures(failing, input) {
|
|
64
|
+
const byCause = new Map();
|
|
65
|
+
for (const entry of failing) {
|
|
66
|
+
const cause = causeOf(entry.route, input);
|
|
67
|
+
byCause.set(cause, [...(byCause.get(cause) ?? []), entry]);
|
|
68
|
+
}
|
|
69
|
+
return [...byCause.entries()].map(([cause, entries]) => describeCause(cause, entries, input)).join(' ');
|
|
70
|
+
}
|
|
71
|
+
function describeCause(cause, entries, input) {
|
|
72
|
+
const names = entries.map(({ kind, route }) => `${kindLabel(kind)} port ${route.internalPort}`);
|
|
73
|
+
const lan = input.lanAddress ?? "this machine's LAN address";
|
|
74
|
+
const forwardHint = `Forward ${entries.length === 1 ? 'port' : 'ports'} ${listPorts(entries)} to ${lan} on your router, then tell cadre-host the external ${entries.length === 1 ? 'port' : 'ports'}.`;
|
|
75
|
+
switch (cause) {
|
|
76
|
+
case 'cgnat':
|
|
77
|
+
return `This host is behind carrier-grade NAT, so the ${joinNames(names)} cannot be reached from outside through your router, and neither UPnP nor a port forward on your router will help; a relay is needed.`;
|
|
78
|
+
case 'refused': {
|
|
79
|
+
const errors = entries.map(({ route }) => route.error).filter((e) => !!e);
|
|
80
|
+
return `Router refused the ${joinNames(names)} mapping (${errors.join('; ')}). ${forwardHint}`;
|
|
81
|
+
}
|
|
82
|
+
case 'upnp_off':
|
|
83
|
+
return `UPnP is off, so the ${joinNames(names)} ${entries.length === 1 ? 'is' : 'are'} not mapped. Turn UPnP on, or: ${lowerFirst(forwardHint)}`;
|
|
84
|
+
case 'no_gateway':
|
|
85
|
+
return `No UPnP router answered, so the ${joinNames(names)} ${entries.length === 1 ? 'is' : 'are'} not mapped. Enable UPnP on your router, or: ${lowerFirst(forwardHint)}`;
|
|
86
|
+
case 'pending':
|
|
87
|
+
return `The ${joinNames(names)} ${entries.length === 1 ? 'is' : 'are'} not mapped yet; the router is being asked.`;
|
|
27
88
|
}
|
|
28
89
|
}
|
|
90
|
+
function kindLabel(kind) {
|
|
91
|
+
return kind === 'tcp' ? 'TCP' : 'WebSocket';
|
|
92
|
+
}
|
|
93
|
+
function listPorts(entries) {
|
|
94
|
+
return joinNames(entries.map(({ route }) => String(route.internalPort)));
|
|
95
|
+
}
|
|
96
|
+
function joinNames(names) {
|
|
97
|
+
if (names.length <= 1)
|
|
98
|
+
return names[0] ?? '';
|
|
99
|
+
return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
100
|
+
}
|
|
101
|
+
function lowerFirst(s) {
|
|
102
|
+
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
103
|
+
}
|
|
29
104
|
//# sourceMappingURL=reachability.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reachability.js","sourceRoot":"","sources":["../../src/nat/reachability.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reachability.js","sourceRoot":"","sources":["../../src/nat/reachability.ts"],"names":[],"mappings":"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"}
|