@serfab/cadre-host 1.14.0 → 1.15.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 +19 -4
- package/dist/bin/host.js +32 -20
- package/dist/bin/host.js.map +1 -1
- package/dist/bin/nat-output.d.ts +5 -0
- package/dist/bin/nat-output.js +12 -1
- package/dist/bin/nat-output.js.map +1 -1
- package/dist/bin/ui-access.d.ts +22 -0
- package/dist/bin/ui-access.js +44 -0
- package/dist/bin/ui-access.js.map +1 -0
- package/dist/hosted/claim-details.d.ts +25 -3
- package/dist/hosted/claim-details.js +31 -37
- package/dist/hosted/claim-details.js.map +1 -1
- package/dist/hosted/hosted-node-service.d.ts +7 -0
- package/dist/hosted/hosted-node-service.js +16 -4
- package/dist/hosted/hosted-node-service.js.map +1 -1
- package/dist/hosted/hosted-node-watcher.d.ts +16 -4
- package/dist/hosted/hosted-node-watcher.js +45 -9
- package/dist/hosted/hosted-node-watcher.js.map +1 -1
- package/dist/hosted/node-status.d.ts +19 -3
- package/dist/hosted/node-status.js +21 -5
- package/dist/hosted/node-status.js.map +1 -1
- package/dist/hosted/types.d.ts +12 -5
- package/dist/hosted/types.js.map +1 -1
- package/dist/installer/config.d.ts +23 -0
- package/dist/installer/config.js +46 -0
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/index.d.ts +2 -0
- package/dist/installer/index.js +7 -16
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/wizard.d.ts +12 -2
- package/dist/installer/wizard.js +15 -7
- package/dist/installer/wizard.js.map +1 -1
- package/dist/nat/index.d.ts +2 -1
- package/dist/nat/index.js +1 -0
- package/dist/nat/index.js.map +1 -1
- package/dist/nat/nat-service.d.ts +22 -5
- package/dist/nat/nat-service.js +75 -26
- package/dist/nat/nat-service.js.map +1 -1
- package/dist/nat/nat-store.js +11 -0
- package/dist/nat/nat-store.js.map +1 -1
- package/dist/nat/network-mode.d.ts +19 -0
- package/dist/nat/network-mode.js +32 -0
- package/dist/nat/network-mode.js.map +1 -0
- package/dist/nat/reachability.d.ts +5 -4
- package/dist/nat/reachability.js +19 -6
- package/dist/nat/reachability.js.map +1 -1
- package/dist/nat/types.d.ts +26 -6
- package/dist/nat/types.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +3 -3
- package/dist/orchestrator/host-process-orchestrator.js +6 -15
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/package-version.d.ts +1 -0
- package/dist/package-version.js +4 -0
- package/dist/package-version.js.map +1 -0
- package/dist/server/routes/settings.d.ts +1 -0
- package/dist/server/routes/settings.js +15 -1
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/status.js +2 -32
- package/dist/server/routes/status.js.map +1 -1
- package/dist/ui/assets/index-BHd8CWZA.css +1 -0
- package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
- package/dist/ui/assets/{index-4FWuU8ye.js.map → index-Ccr2GjPT.js.map} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +4 -5
- package/src/__tests__/cli.smoke.test.ts +1 -1
- package/src/__tests__/orchestrator-claim-secret.test.ts +1 -2
- package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
- package/src/__tests__/orchestrator-handle-contract.ts +1 -1
- package/src/__tests__/orchestrator-node-identity.test.ts +0 -5
- package/src/__tests__/orchestrator-push.test.ts +3 -3
- package/src/__tests__/orchestrator.test.ts +3 -5
- package/src/__tests__/ui-access.test.ts +30 -0
- package/src/bin/host.ts +30 -20
- package/src/bin/nat-output.ts +12 -1
- package/src/bin/ui-access.ts +50 -0
- package/src/hosted/__tests__/fake-orchestrator.test.ts +0 -1
- package/src/hosted/__tests__/fake-orchestrator.ts +263 -265
- package/src/hosted/__tests__/hosted-node-service.test.ts +708 -609
- package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -474
- package/src/hosted/claim-details.ts +44 -37
- package/src/hosted/hosted-node-service.ts +23 -4
- package/src/hosted/hosted-node-watcher.ts +288 -250
- package/src/hosted/node-status.ts +31 -5
- package/src/hosted/types.ts +160 -153
- package/src/installer/__tests__/installer.smoke.test.ts +184 -182
- package/src/installer/__tests__/wizard.test.ts +76 -56
- package/src/installer/config.ts +66 -0
- package/src/installer/index.ts +252 -259
- package/src/installer/wizard.ts +101 -87
- package/src/nat/__tests__/nat-service.test.ts +608 -540
- package/src/nat/__tests__/nat-store.test.ts +130 -127
- package/src/nat/__tests__/network-mode.test.ts +38 -0
- package/src/nat/index.ts +91 -87
- package/src/nat/nat-service.ts +79 -26
- package/src/nat/nat-store.ts +249 -237
- package/src/nat/network-mode.ts +36 -0
- package/src/nat/reachability.ts +155 -143
- package/src/nat/types.ts +232 -209
- package/src/orchestrator/host-process-orchestrator.ts +1089 -1098
- package/src/package-version.ts +4 -0
- package/src/server/__tests__/fakes.ts +41 -40
- package/src/server/__tests__/hosted-nodes-route.test.ts +161 -160
- package/src/server/__tests__/settings-route.test.ts +31 -0
- package/src/server/routes/settings.ts +128 -115
- package/src/server/routes/status.ts +72 -102
- package/dist/ui/assets/index-4FWuU8ye.js +0 -19
- package/dist/ui/assets/index-Di9Q4d01.css +0 -1
|
@@ -35,6 +35,27 @@ export interface PushSettings {
|
|
|
35
35
|
/** Per-strand burst-coalescing window (ms) forwarded into the node's push block. */
|
|
36
36
|
debounceMs?: number;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Which addresses a hosted node's QR code carries (cadre-core's `selectNodeClaimAddresses`).
|
|
40
|
+
* Absent ⇒ `{ lan: 'auto' }`. Read on every claim-code request, so an edit applies to the
|
|
41
|
+
* next code shown.
|
|
42
|
+
*/
|
|
43
|
+
export interface ClaimAddressSettings {
|
|
44
|
+
/**
|
|
45
|
+
* The LAN address a phone at home dials: `'auto'` (the address on the router's subnet, else
|
|
46
|
+
* the machine's primary one; none in public-IP mode), `'none'` (public addresses only), or an
|
|
47
|
+
* IP address. Other private addresses (Docker bridges, VPNs) are left out either way.
|
|
48
|
+
*/
|
|
49
|
+
lan?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Exact addresses to put in every code instead, for an operator who knows better than the
|
|
52
|
+
* rule, such as one name behind a proxy. Each names no destination peer (a relay address ends
|
|
53
|
+
* at its `/p2p-circuit` hop); every node's code appends its own peer id.
|
|
54
|
+
*/
|
|
55
|
+
addrs?: string[];
|
|
56
|
+
}
|
|
57
|
+
/** Why a `claimAddresses` value is unacceptable, naming the field; undefined when it is fine. */
|
|
58
|
+
export declare function claimAddressSettingsProblem(value: unknown): string | undefined;
|
|
38
59
|
export interface HostConfigFile {
|
|
39
60
|
version: 3;
|
|
40
61
|
/** A stable per-install id, for log correlation. */
|
|
@@ -57,6 +78,8 @@ export interface HostConfigFile {
|
|
|
57
78
|
* they are read from the secret store at node-spawn time (`src/push/`).
|
|
58
79
|
*/
|
|
59
80
|
push?: PushSettings;
|
|
81
|
+
/** Which addresses claim codes carry; absent ⇒ automatic. */
|
|
82
|
+
claimAddresses?: ClaimAddressSettings;
|
|
60
83
|
}
|
|
61
84
|
export declare function writeHostConfig(filePath: string, cfg: HostConfigFile): void;
|
|
62
85
|
/** Apply a partial patch to the persisted config (read-modify-write). */
|
package/dist/installer/config.js
CHANGED
|
@@ -11,7 +11,50 @@
|
|
|
11
11
|
* being silently misread.
|
|
12
12
|
*/
|
|
13
13
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { isIP } from 'node:net';
|
|
14
15
|
import { dirname } from 'node:path';
|
|
16
|
+
import { destinationPeerIdOf } from '@serfab/cadre-core';
|
|
17
|
+
/** Why a `claimAddresses` value is unacceptable, naming the field; undefined when it is fine. */
|
|
18
|
+
export function claimAddressSettingsProblem(value) {
|
|
19
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
20
|
+
return '"claimAddresses" must be an object';
|
|
21
|
+
const o = value;
|
|
22
|
+
for (const key of Object.keys(o)) {
|
|
23
|
+
if (key !== 'lan' && key !== 'addrs')
|
|
24
|
+
return `Unknown setting "claimAddresses.${key}"`;
|
|
25
|
+
}
|
|
26
|
+
if (o.lan !== undefined) {
|
|
27
|
+
if (typeof o.lan !== 'string' || (o.lan !== 'auto' && o.lan !== 'none' && isIP(o.lan) === 0)) {
|
|
28
|
+
return '"claimAddresses.lan" must be "auto", "none" or an IP address';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (o.addrs !== undefined) {
|
|
32
|
+
if (!Array.isArray(o.addrs) || !o.addrs.every((a) => typeof a === 'string')) {
|
|
33
|
+
return '"claimAddresses.addrs" must be a list of multiaddr strings';
|
|
34
|
+
}
|
|
35
|
+
for (const addr of o.addrs) {
|
|
36
|
+
const problem = exactClaimAddressProblem(addr);
|
|
37
|
+
if (problem)
|
|
38
|
+
return `"claimAddresses.addrs" entry "${addr}" ${problem}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Why one `claimAddresses.addrs` entry is unacceptable. It must not name its destination peer:
|
|
45
|
+
* the list is host-wide and each hosted node's code appends its own peer id, so a named one
|
|
46
|
+
* would be some other node's address in every code but that node's.
|
|
47
|
+
*/
|
|
48
|
+
function exactClaimAddressProblem(addr) {
|
|
49
|
+
let destination;
|
|
50
|
+
try {
|
|
51
|
+
destination = destinationPeerIdOf(addr.trim());
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return 'is not a valid multiaddr';
|
|
55
|
+
}
|
|
56
|
+
return destination === null ? undefined : 'must not end in /p2p/<peer id>: each node\'s own id is appended';
|
|
57
|
+
}
|
|
15
58
|
const CURRENT_VERSION = 3;
|
|
16
59
|
export function writeHostConfig(filePath, cfg) {
|
|
17
60
|
if (cfg.version !== CURRENT_VERSION) {
|
|
@@ -51,6 +94,9 @@ export function readHostConfig(filePath) {
|
|
|
51
94
|
if (!isHostConfigShape(obj)) {
|
|
52
95
|
throw new Error(`host.config.json at ${filePath} is missing required fields`);
|
|
53
96
|
}
|
|
97
|
+
const claimAddressesProblem = obj.claimAddresses === undefined ? undefined : claimAddressSettingsProblem(obj.claimAddresses);
|
|
98
|
+
if (claimAddressesProblem)
|
|
99
|
+
throw new Error(`host.config.json at ${filePath}: ${claimAddressesProblem}`);
|
|
54
100
|
return obj;
|
|
55
101
|
}
|
|
56
102
|
function isHostConfigShape(v) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/installer/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/installer/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,6 +40,8 @@ export interface InstallOptions {
|
|
|
40
40
|
serviceHost?: ServiceHost;
|
|
41
41
|
/** Test-only: stub the interactive wizard. Production callers leave unset. */
|
|
42
42
|
wizard?: (defaults: WizardDefaults) => Promise<WizardAnswers>;
|
|
43
|
+
/** Test-only: stub the interface read that decides whether to ask about UPnP. */
|
|
44
|
+
publicInterfaceAddress?: () => string | null;
|
|
43
45
|
}
|
|
44
46
|
export interface InstallResult {
|
|
45
47
|
dataDir: string;
|
package/dist/installer/index.js
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* subsystem modules (wizard.ts, config.ts, service-host/{systemd,launchd,nssm}.ts,
|
|
9
9
|
* browser.ts).
|
|
10
10
|
*/
|
|
11
|
-
import { existsSync, mkdirSync,
|
|
11
|
+
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
12
12
|
import { randomBytes } from 'node:crypto';
|
|
13
13
|
import debug from 'debug';
|
|
14
|
-
import { NatStore } from '../nat/index.js';
|
|
14
|
+
import { NatStore, publicInterfaceAddress } from '../nat/index.js';
|
|
15
|
+
import { packageVersion } from '../package-version.js';
|
|
15
16
|
import { openBrowser } from './browser.js';
|
|
16
17
|
import { writeHostConfig } from './config.js';
|
|
17
18
|
import { configPath, defaultHostJs, defaultServiceDir, logsDir, } from './paths.js';
|
|
@@ -23,7 +24,7 @@ const log = debug('cadre:host:installer');
|
|
|
23
24
|
export class Installer {
|
|
24
25
|
constructor(opts = {}) {
|
|
25
26
|
this.platform = opts.platform ?? detectPlatform();
|
|
26
|
-
this.installerVersion = opts.installerVersion ??
|
|
27
|
+
this.installerVersion = opts.installerVersion ?? packageVersion;
|
|
27
28
|
}
|
|
28
29
|
async install(opts) {
|
|
29
30
|
if (opts.system) {
|
|
@@ -112,7 +113,6 @@ export class Installer {
|
|
|
112
113
|
dataDir: opts.dataDir ?? defaults.dataDir,
|
|
113
114
|
uiPort: opts.uiPort ?? defaults.uiPort,
|
|
114
115
|
upnpEnabled: opts.noUpnp ? false : defaults.upnpEnabled,
|
|
115
|
-
configureDdns: false,
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
async resolveAnswers(opts) {
|
|
@@ -129,6 +129,9 @@ export class Installer {
|
|
|
129
129
|
dataDir: opts.dataDir ?? platformDefaults.dataDir,
|
|
130
130
|
uiPort: opts.uiPort ?? platformDefaults.uiPort,
|
|
131
131
|
upnpEnabled: opts.noUpnp ? false : platformDefaults.upnpEnabled,
|
|
132
|
+
// A flag answers its question: the wizard asks only for what the command line left open.
|
|
133
|
+
given: { dataDir: opts.dataDir !== undefined, uiPort: opts.uiPort !== undefined, upnpEnabled: opts.noUpnp === true },
|
|
134
|
+
publicInterfaceIp: (opts.publicInterfaceAddress ?? publicInterfaceAddress)(),
|
|
132
135
|
};
|
|
133
136
|
const wizard = opts.wizard ?? runWizard;
|
|
134
137
|
return await wizard(defaults);
|
|
@@ -143,18 +146,6 @@ function seedNatSettings(dataDir, upnpEnabled) {
|
|
|
143
146
|
function randomInstallId() {
|
|
144
147
|
return randomBytes(16).toString('hex');
|
|
145
148
|
}
|
|
146
|
-
function readPackageVersion() {
|
|
147
|
-
// Best-effort: read the package.json next to dist/ at runtime.
|
|
148
|
-
try {
|
|
149
|
-
const here = new URL('../../package.json', import.meta.url);
|
|
150
|
-
const raw = readFileSync(here, 'utf8');
|
|
151
|
-
const parsed = JSON.parse(raw);
|
|
152
|
-
return parsed.version ?? '0.0.0-unknown';
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return '0.0.0-unknown';
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
149
|
// Exposed for tests that drive the wizard with a custom prompt fn.
|
|
159
150
|
export { runWizard };
|
|
160
151
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/installer/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/installer/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,12 +10,22 @@ export interface WizardDefaults {
|
|
|
10
10
|
dataDir: string;
|
|
11
11
|
uiPort: number;
|
|
12
12
|
upnpEnabled: boolean;
|
|
13
|
+
/** Values a command-line flag already supplied: the wizard does not ask for them again. */
|
|
14
|
+
given?: {
|
|
15
|
+
dataDir?: boolean;
|
|
16
|
+
uiPort?: boolean;
|
|
17
|
+
upnpEnabled?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The public IPv4 an interface holds (`publicInterfaceAddress`), when one does: the machine is
|
|
21
|
+
* reached directly (a VPS), so there is no router to ask about UPnP.
|
|
22
|
+
*/
|
|
23
|
+
publicInterfaceIp?: string | null;
|
|
13
24
|
}
|
|
14
25
|
export interface WizardAnswers {
|
|
15
26
|
dataDir: string;
|
|
16
27
|
uiPort: number;
|
|
17
28
|
upnpEnabled: boolean;
|
|
18
|
-
configureDdns: boolean;
|
|
19
29
|
}
|
|
20
30
|
export declare const DEFAULT_UI_PORT = 8765;
|
|
21
31
|
export declare function defaultsForPlatform(platform: SupportedPlatform): WizardDefaults;
|
|
@@ -23,6 +33,6 @@ export declare function defaultsForPlatform(platform: SupportedPlatform): Wizard
|
|
|
23
33
|
* Drive the wizard with a function that returns the next user answer for
|
|
24
34
|
* a prompt. Used both by the readline-backed `runWizard` and by tests.
|
|
25
35
|
*/
|
|
26
|
-
export declare function runWizardWith(defaults: WizardDefaults, ask: (label: string, fallback: string) => Promise<string
|
|
36
|
+
export declare function runWizardWith(defaults: WizardDefaults, ask: (label: string, fallback: string) => Promise<string>, say?: (line: string) => void): Promise<WizardAnswers>;
|
|
27
37
|
/** Production entrypoint — drives the wizard over stdin/stdout. */
|
|
28
38
|
export declare function runWizard(defaults: WizardDefaults): Promise<WizardAnswers>;
|
package/dist/installer/wizard.js
CHANGED
|
@@ -19,18 +19,26 @@ export function defaultsForPlatform(platform) {
|
|
|
19
19
|
* Drive the wizard with a function that returns the next user answer for
|
|
20
20
|
* a prompt. Used both by the readline-backed `runWizard` and by tests.
|
|
21
21
|
*/
|
|
22
|
-
export async function runWizardWith(defaults, ask) {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
export async function runWizardWith(defaults, ask, say = () => { }) {
|
|
23
|
+
const given = defaults.given ?? {};
|
|
24
|
+
const dataDir = given.dataDir ? defaults.dataDir : (await ask('Data directory', defaults.dataDir)).trim() || defaults.dataDir;
|
|
25
|
+
const uiPort = given.uiPort
|
|
26
|
+
? defaults.uiPort
|
|
27
|
+
: parsePort(await ask('UI port (localhost only)', String(defaults.uiPort)), defaults.uiPort);
|
|
28
|
+
let upnpEnabled = defaults.upnpEnabled;
|
|
29
|
+
if (defaults.publicInterfaceIp) {
|
|
30
|
+
say(`This machine has a public IP address (${defaults.publicInterfaceIp}), so it is reached directly: no router, no UPnP.`);
|
|
31
|
+
}
|
|
32
|
+
else if (!given.upnpEnabled) {
|
|
33
|
+
upnpEnabled = parseBool(await ask('This machine is behind a router. Ask it to map ports automatically (UPnP)? [Y/n]', defaults.upnpEnabled ? 'Y' : 'n'), defaults.upnpEnabled);
|
|
34
|
+
}
|
|
35
|
+
return { dataDir, uiPort, upnpEnabled };
|
|
28
36
|
}
|
|
29
37
|
/** Production entrypoint — drives the wizard over stdin/stdout. */
|
|
30
38
|
export async function runWizard(defaults) {
|
|
31
39
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
32
40
|
try {
|
|
33
|
-
return await runWizardWith(defaults, async (label, fallback) => prompt(rl, label, fallback));
|
|
41
|
+
return await runWizardWith(defaults, async (label, fallback) => prompt(rl, label, fallback), (line) => console.log(line));
|
|
34
42
|
}
|
|
35
43
|
finally {
|
|
36
44
|
rl.close();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/installer/wizard.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAuC,MAAM,eAAe,CAAC;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/installer/wizard.ts"],"names":[],"mappings":"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"}
|
package/dist/nat/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { NAT_ADDRESS_RESTART_MIN_INTERVAL_MS } from './address-watch.js';
|
|
|
10
10
|
export type { NodeAddressesStaleListener } from './address-watch.js';
|
|
11
11
|
export { NatStore } from './nat-store.js';
|
|
12
12
|
export { NatError, } from './types.js';
|
|
13
|
-
export type { NatStatusSnapshot, NatGatewayStatus, NatDdnsStatus, NatSettingsFile, NatDdnsSettings, NatHandlers, NatErrorCode, DirectReachability, NodeReachability, NodeVerdict, PortKind, PortRoute, PortRouteSource, ManualForward, ManualForwardPatch, DdnsProviderInfo, DdnsConfigField, } from './types.js';
|
|
13
|
+
export type { NatStatusSnapshot, NatGatewayStatus, NatDdnsStatus, NatNetworkStatus, NetworkMode, NetworkSetting, NatSettingsFile, NatDdnsSettings, NatHandlers, NatErrorCode, DirectReachability, NodeReachability, NodeVerdict, PortKind, PortRoute, PortRouteSource, ManualForward, ManualForwardPatch, DdnsProviderInfo, DdnsConfigField, } from './types.js';
|
|
14
14
|
export { ExternalIpDetector, isPlausibleIp, } from './external-ip.js';
|
|
15
15
|
export type { ExternalIpResult, RouterIpProbe, ExternalIpDetectorOptions, } from './external-ip.js';
|
|
16
16
|
export { UpnpPortMapper, pickLanAddress, DEFAULT_LEASE_TTL_MS, DEFAULT_REFRESH_MS, GATEWAY_DISCOVERY_TIMEOUT_MS, } from './port-mapper.js';
|
|
@@ -18,6 +18,7 @@ export type { PortMapper, GatewayInfo, PortMapRequest, PortMapResult, } from './
|
|
|
18
18
|
export { evaluateNodeReachability, evaluateHostReachability } from './reachability.js';
|
|
19
19
|
export type { NodeVerdictInput, NodeVerdictResult } from './reachability.js';
|
|
20
20
|
export { buildPublicAddresses, isPublicIpv4 } from './address-resolver.js';
|
|
21
|
+
export { publicInterfaceAddress, effectiveNetworkMode } from './network-mode.js';
|
|
21
22
|
export type { PublicAddressInput } from './address-resolver.js';
|
|
22
23
|
export { BUILTIN_PROVIDERS, getProvider, listProviders, duckDnsProvider, DdnsUpdater, } from './ddns/index.js';
|
|
23
24
|
export type { DdnsProvider, DdnsProviderDeps, DdnsUpdaterOptions, DdnsUpdateOutcome, } from './ddns/index.js';
|
package/dist/nat/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export { ExternalIpDetector, isPlausibleIp, } from './external-ip.js';
|
|
|
12
12
|
export { UpnpPortMapper, pickLanAddress, DEFAULT_LEASE_TTL_MS, DEFAULT_REFRESH_MS, GATEWAY_DISCOVERY_TIMEOUT_MS, } from './port-mapper.js';
|
|
13
13
|
export { evaluateNodeReachability, evaluateHostReachability } from './reachability.js';
|
|
14
14
|
export { buildPublicAddresses, isPublicIpv4 } from './address-resolver.js';
|
|
15
|
+
export { publicInterfaceAddress, effectiveNetworkMode } from './network-mode.js';
|
|
15
16
|
export { BUILTIN_PROVIDERS, getProvider, listProviders, duckDnsProvider, DdnsUpdater, } from './ddns/index.js';
|
|
16
17
|
export { KeytarSecretsStore, FileSecretsStore, createSecretsStore, ddnsAccount, KEYTAR_SERVICE, } from './secrets/index.js';
|
|
17
18
|
//# sourceMappingURL=index.js.map
|
package/dist/nat/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/nat/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,mCAAmC,EAAE,MAAM,oBAAoB,CAAC;AAEzE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EACL,QAAQ,GACT,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/nat/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,mCAAmC,EAAE,MAAM,oBAAoB,CAAC;AAEzE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EACL,QAAQ,GACT,MAAM,YAAY,CAAC;AAuBpB,OAAO,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EACL,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAO1B,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEjF,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EACf,WAAW,GACZ,MAAM,iBAAiB,CAAC;AAOzB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,cAAc,GACf,MAAM,oBAAoB,CAAC"}
|
|
@@ -48,6 +48,8 @@ export interface NatServiceOptions {
|
|
|
48
48
|
store?: NatStore;
|
|
49
49
|
/** Lease TTL asked of the router. Default 1 h. */
|
|
50
50
|
leaseTtlMs?: number;
|
|
51
|
+
/** Test-only: stub the interface read behind `network: auto`. Default: `publicInterfaceAddress()`. */
|
|
52
|
+
publicInterfaceAddress?: () => string | null;
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* NatService — one mapping table for every node the host runs, keyed by node
|
|
@@ -92,6 +94,9 @@ export declare class NatService {
|
|
|
92
94
|
private ddnsUpdater;
|
|
93
95
|
private currentSettings;
|
|
94
96
|
private gateway;
|
|
97
|
+
/** The public IPv4 an interface holds (`publicInterfaceAddress`), re-read at start and on every probe. */
|
|
98
|
+
private publicInterfaceIp;
|
|
99
|
+
private readonly readInterfaces;
|
|
95
100
|
private latestIp;
|
|
96
101
|
private lastTestedAt;
|
|
97
102
|
private readonly table;
|
|
@@ -166,10 +171,11 @@ export declare class NatService {
|
|
|
166
171
|
*/
|
|
167
172
|
renewMappings(): Promise<void>;
|
|
168
173
|
/**
|
|
169
|
-
* The 5-minute probe pass:
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
174
|
+
* The 5-minute probe pass: re-read the interfaces and re-route every node when
|
|
175
|
+
* the mode `auto` lands on changes; while UPnP is in use and no router has
|
|
176
|
+
* answered, search again and map the running nodes as soon as one does — a
|
|
177
|
+
* host that boots before its network, or a router that comes up later, is
|
|
178
|
+
* found here rather than at the next restart; then re-detect the external IP.
|
|
173
179
|
*/
|
|
174
180
|
redetect(): Promise<void>;
|
|
175
181
|
/** Re-discover the router if needed, re-map everything, re-detect the IP, then return a fresh snapshot. */
|
|
@@ -244,8 +250,19 @@ export declare class NatService {
|
|
|
244
250
|
private ipDetector;
|
|
245
251
|
/** The detected external IP for display and DDNS: the public probe's, else the router's. */
|
|
246
252
|
private detectedIp;
|
|
247
|
-
/** The IP used in `/ip4/` addresses: only a public IPv4 counts. */
|
|
253
|
+
/** The IP used in `/ip4/` addresses: only a public IPv4 counts. Public mode prefers the interface's own. */
|
|
248
254
|
private publicIpv4;
|
|
255
|
+
/**
|
|
256
|
+
* Re-read the interfaces (a VPS gains an address, a laptop moves networks); true when that
|
|
257
|
+
* changed the mode in effect, so the caller re-routes every node in the same pass.
|
|
258
|
+
*/
|
|
259
|
+
private refreshNetworkMode;
|
|
260
|
+
/** The mode in effect: the `network` setting, or what `auto` reads from the interfaces. */
|
|
261
|
+
private networkMode;
|
|
262
|
+
/** UPnP is asked for nothing in public mode: there is no router to ask. */
|
|
263
|
+
private usesUpnp;
|
|
264
|
+
/** An interface holding the public address rules out carrier-grade NAT, whatever the probes say. */
|
|
265
|
+
private cgnatDetected;
|
|
249
266
|
private describeNode;
|
|
250
267
|
private hostKnown;
|
|
251
268
|
private addressesFor;
|
package/dist/nat/nat-service.js
CHANGED
|
@@ -7,6 +7,7 @@ import { evaluateHostReachability, evaluateNodeReachability } from './reachabili
|
|
|
7
7
|
import { DdnsUpdater } from './ddns/updater.js';
|
|
8
8
|
import { getProvider, listProviders } from './ddns/index.js';
|
|
9
9
|
import { buildPublicAddresses, isPublicIpv4 } from './address-resolver.js';
|
|
10
|
+
import { effectiveNetworkMode, publicInterfaceAddress } from './network-mode.js';
|
|
10
11
|
import { createSecretsStore, ddnsAccount } from './secrets/index.js';
|
|
11
12
|
import { AddressWatch } from './address-watch.js';
|
|
12
13
|
const log = debug('cadre:host:nat-service');
|
|
@@ -27,6 +28,10 @@ const PORT_KINDS = ['tcp', 'ws'];
|
|
|
27
28
|
function noRoute(internalPort) {
|
|
28
29
|
return { internalPort, externalPort: null, source: null, leaseExpiresAt: null, error: null };
|
|
29
30
|
}
|
|
31
|
+
/** Public mode: the port is reached at its own number, with nothing to map. */
|
|
32
|
+
function directRoute(internalPort) {
|
|
33
|
+
return { internalPort, externalPort: internalPort, source: 'direct', leaseExpiresAt: null, error: null };
|
|
34
|
+
}
|
|
30
35
|
function manualRoute(internalPort, externalPort) {
|
|
31
36
|
return { internalPort, externalPort, source: 'manual', leaseExpiresAt: null, error: null };
|
|
32
37
|
}
|
|
@@ -74,6 +79,8 @@ export class NatService {
|
|
|
74
79
|
this.detector = null;
|
|
75
80
|
this.ddnsUpdater = null;
|
|
76
81
|
this.gateway = { found: false, lanAddress: null, routerExternalIp: null, lastError: null };
|
|
82
|
+
/** The public IPv4 an interface holds (`publicInterfaceAddress`), re-read at start and on every probe. */
|
|
83
|
+
this.publicInterfaceIp = null;
|
|
77
84
|
this.latestIp = null;
|
|
78
85
|
this.lastTestedAt = null;
|
|
79
86
|
this.table = new Map();
|
|
@@ -94,6 +101,7 @@ export class NatService {
|
|
|
94
101
|
this.secretsStore = opts.secretsStore ?? null;
|
|
95
102
|
this.injectedDetector = opts.externalIpDetector ?? null;
|
|
96
103
|
this.secretsRootDir = opts.rootDir;
|
|
104
|
+
this.readInterfaces = opts.publicInterfaceAddress ?? (() => publicInterfaceAddress());
|
|
97
105
|
this.currentSettings = this.store.load();
|
|
98
106
|
this.addressWatch = new AddressWatch(() => this.nowFn().getTime());
|
|
99
107
|
}
|
|
@@ -110,7 +118,9 @@ export class NatService {
|
|
|
110
118
|
if (!this.secretsStore) {
|
|
111
119
|
this.secretsStore = await createSecretsStore(this.secretsRootDir);
|
|
112
120
|
}
|
|
113
|
-
|
|
121
|
+
this.publicInterfaceIp = this.readInterfaces();
|
|
122
|
+
log('network mode %s (setting %s, public interface address %s)', this.networkMode(), this.currentSettings.network, this.publicInterfaceIp ?? 'none');
|
|
123
|
+
if (this.usesUpnp())
|
|
114
124
|
await this.discoverGateway();
|
|
115
125
|
await this.detectIp();
|
|
116
126
|
this.ddnsUpdater = new DdnsUpdater({
|
|
@@ -168,11 +178,12 @@ export class NatService {
|
|
|
168
178
|
// --- reads ---
|
|
169
179
|
/** Cheap read of the current snapshot. */
|
|
170
180
|
getStatus() {
|
|
171
|
-
const cgnatDetected = this.
|
|
181
|
+
const cgnatDetected = this.cgnatDetected();
|
|
172
182
|
const nodes = [...this.table.values()]
|
|
173
183
|
.sort((a, b) => a.nodeId.localeCompare(b.nodeId))
|
|
174
184
|
.map((entry) => this.describeNode(entry, cgnatDetected));
|
|
175
185
|
return {
|
|
186
|
+
network: { setting: this.currentSettings.network, mode: this.networkMode(), publicInterfaceIp: this.publicInterfaceIp },
|
|
176
187
|
upnpEnabled: this.currentSettings.upnpEnabled,
|
|
177
188
|
gateway: { ...this.gateway, routerExternalIp: this.latestIp?.routerIp ?? null },
|
|
178
189
|
externalIp: this.detectedIp(),
|
|
@@ -247,19 +258,21 @@ export class NatService {
|
|
|
247
258
|
return this.enqueue(() => this.renewOnce());
|
|
248
259
|
}
|
|
249
260
|
/**
|
|
250
|
-
* The 5-minute probe pass:
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
261
|
+
* The 5-minute probe pass: re-read the interfaces and re-route every node when
|
|
262
|
+
* the mode `auto` lands on changes; while UPnP is in use and no router has
|
|
263
|
+
* answered, search again and map the running nodes as soon as one does — a
|
|
264
|
+
* host that boots before its network, or a router that comes up later, is
|
|
265
|
+
* found here rather than at the next restart; then re-detect the external IP.
|
|
254
266
|
*/
|
|
255
267
|
async redetect() {
|
|
256
|
-
|
|
257
|
-
|
|
268
|
+
await this.enqueue(async () => {
|
|
269
|
+
const modeChanged = this.refreshNetworkMode();
|
|
270
|
+
const gatewayWasFound = this.gateway.found;
|
|
271
|
+
if (this.usesUpnp() && !gatewayWasFound)
|
|
258
272
|
await this.discoverGateway();
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
273
|
+
if (modeChanged || (!gatewayWasFound && this.gateway.found))
|
|
274
|
+
await this.reconcileOnce({ mapMissing: true });
|
|
275
|
+
});
|
|
263
276
|
await this.detectIp();
|
|
264
277
|
this.publish();
|
|
265
278
|
}
|
|
@@ -267,7 +280,8 @@ export class NatService {
|
|
|
267
280
|
/** Re-discover the router if needed, re-map everything, re-detect the IP, then return a fresh snapshot. */
|
|
268
281
|
async testReachability() {
|
|
269
282
|
await this.enqueue(async () => {
|
|
270
|
-
|
|
283
|
+
this.refreshNetworkMode();
|
|
284
|
+
if (this.usesUpnp() && !this.gateway.found)
|
|
271
285
|
await this.discoverGateway();
|
|
272
286
|
await this.reconcileOnce({ mapMissing: false });
|
|
273
287
|
await this.renewOnce();
|
|
@@ -283,7 +297,8 @@ export class NatService {
|
|
|
283
297
|
/** Replace settings; persists to disk and reconfigures the runtime. */
|
|
284
298
|
async putSettings(patch) {
|
|
285
299
|
const next = this.store.update(patch);
|
|
286
|
-
const
|
|
300
|
+
const wasUsingUpnp = this.usesUpnp();
|
|
301
|
+
const modeBefore = this.networkMode();
|
|
287
302
|
const ddnsChanged = next.ddns.providerId !== this.currentSettings.ddns.providerId ||
|
|
288
303
|
next.ddns.hostname !== this.currentSettings.ddns.hostname ||
|
|
289
304
|
next.ddns.externallyManaged !== this.currentSettings.ddns.externallyManaged ||
|
|
@@ -293,12 +308,12 @@ export class NatService {
|
|
|
293
308
|
if (ddnsChanged && this.ddnsUpdater) {
|
|
294
309
|
await this.ddnsUpdater.updateSettings(next.ddns);
|
|
295
310
|
}
|
|
296
|
-
if (
|
|
297
|
-
//
|
|
298
|
-
// pass, so the pass can map.
|
|
299
|
-
// `upnp` route and keeps the manual ones.
|
|
311
|
+
if (this.usesUpnp() !== wasUsingUpnp || this.networkMode() !== modeBefore) {
|
|
312
|
+
// UPnP coming into use (turned on, or leaving public mode) with no gateway known yet:
|
|
313
|
+
// look for one before the pass, so the pass can map. Going out of use: the pass
|
|
314
|
+
// releases every `upnp` route and keeps the manual ones; public mode adds `direct` ones.
|
|
300
315
|
await this.enqueue(async () => {
|
|
301
|
-
if (
|
|
316
|
+
if (this.usesUpnp() && !this.gateway.found)
|
|
302
317
|
await this.discoverGateway();
|
|
303
318
|
await this.reconcileOnce({ mapMissing: true });
|
|
304
319
|
});
|
|
@@ -464,9 +479,15 @@ export class NatService {
|
|
|
464
479
|
entry.routes[kind] = manualRoute(route.internalPort, forward);
|
|
465
480
|
continue;
|
|
466
481
|
}
|
|
467
|
-
if (route.source === 'manual') {
|
|
482
|
+
if (route.source === 'manual' || route.source === 'direct') {
|
|
468
483
|
entry.routes[kind] = noRoute(route.internalPort);
|
|
469
484
|
}
|
|
485
|
+
if (this.networkMode() === 'public') {
|
|
486
|
+
if (route.source === 'upnp')
|
|
487
|
+
await this.unmapRoute(entry, kind);
|
|
488
|
+
entry.routes[kind] = directRoute(route.internalPort);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
470
491
|
if (!this.currentSettings.upnpEnabled) {
|
|
471
492
|
if (entry.routes[kind].source === 'upnp')
|
|
472
493
|
await this.unmapRoute(entry, kind);
|
|
@@ -485,7 +506,7 @@ export class NatService {
|
|
|
485
506
|
async renewOnce() {
|
|
486
507
|
if (!this.started)
|
|
487
508
|
return;
|
|
488
|
-
if (!this.
|
|
509
|
+
if (!this.usesUpnp() || !this.gateway.found)
|
|
489
510
|
return;
|
|
490
511
|
for (const entry of this.table.values()) {
|
|
491
512
|
if (!this.started)
|
|
@@ -493,7 +514,7 @@ export class NatService {
|
|
|
493
514
|
if (!entry.running)
|
|
494
515
|
continue;
|
|
495
516
|
for (const kind of this.kindsOf(entry)) {
|
|
496
|
-
if (entry.routes[kind].source
|
|
517
|
+
if (entry.routes[kind].source !== 'upnp' && entry.routes[kind].source !== null)
|
|
497
518
|
continue;
|
|
498
519
|
await this.mapRoute(entry, kind);
|
|
499
520
|
}
|
|
@@ -614,11 +635,37 @@ export class NatService {
|
|
|
614
635
|
detectedIp() {
|
|
615
636
|
return this.latestIp?.publicIp ?? this.latestIp?.routerIp ?? null;
|
|
616
637
|
}
|
|
617
|
-
/** The IP used in `/ip4/` addresses: only a public IPv4 counts. */
|
|
638
|
+
/** The IP used in `/ip4/` addresses: only a public IPv4 counts. Public mode prefers the interface's own. */
|
|
618
639
|
publicIpv4() {
|
|
640
|
+
if (this.networkMode() === 'public' && this.publicInterfaceIp)
|
|
641
|
+
return this.publicInterfaceIp;
|
|
619
642
|
const ip = this.detectedIp();
|
|
620
643
|
return ip && isPublicIpv4(ip) ? ip : null;
|
|
621
644
|
}
|
|
645
|
+
/**
|
|
646
|
+
* Re-read the interfaces (a VPS gains an address, a laptop moves networks); true when that
|
|
647
|
+
* changed the mode in effect, so the caller re-routes every node in the same pass.
|
|
648
|
+
*/
|
|
649
|
+
refreshNetworkMode() {
|
|
650
|
+
const modeBefore = this.networkMode();
|
|
651
|
+
this.publicInterfaceIp = this.readInterfaces();
|
|
652
|
+
const changed = this.networkMode() !== modeBefore;
|
|
653
|
+
if (changed)
|
|
654
|
+
log('network mode changed to %s (public interface address %s)', this.networkMode(), this.publicInterfaceIp ?? 'none');
|
|
655
|
+
return changed;
|
|
656
|
+
}
|
|
657
|
+
/** The mode in effect: the `network` setting, or what `auto` reads from the interfaces. */
|
|
658
|
+
networkMode() {
|
|
659
|
+
return effectiveNetworkMode(this.currentSettings.network, this.publicInterfaceIp);
|
|
660
|
+
}
|
|
661
|
+
/** UPnP is asked for nothing in public mode: there is no router to ask. */
|
|
662
|
+
usesUpnp() {
|
|
663
|
+
return this.currentSettings.upnpEnabled && this.networkMode() === 'lan';
|
|
664
|
+
}
|
|
665
|
+
/** An interface holding the public address rules out carrier-grade NAT, whatever the probes say. */
|
|
666
|
+
cgnatDetected() {
|
|
667
|
+
return this.networkMode() === 'public' ? false : this.latestIp?.cgnat ?? false;
|
|
668
|
+
}
|
|
622
669
|
// --- status assembly ---
|
|
623
670
|
describeNode(entry, cgnatDetected) {
|
|
624
671
|
const tcp = this.effectiveRoute(entry.routes.tcp);
|
|
@@ -628,7 +675,7 @@ export class NatService {
|
|
|
628
675
|
ws,
|
|
629
676
|
hostKnown: this.hostKnown(),
|
|
630
677
|
cgnatDetected,
|
|
631
|
-
upnpEnabled: this.
|
|
678
|
+
upnpEnabled: this.usesUpnp(),
|
|
632
679
|
gatewayFound: this.gateway.found,
|
|
633
680
|
lanAddress: this.gateway.lanAddress,
|
|
634
681
|
});
|
|
@@ -649,7 +696,7 @@ export class NatService {
|
|
|
649
696
|
return buildPublicAddresses({
|
|
650
697
|
ddnsHostname: this.currentSettings.ddns.hostname,
|
|
651
698
|
externalIp: this.publicIpv4(),
|
|
652
|
-
cgnatDetected: this.
|
|
699
|
+
cgnatDetected: this.cgnatDetected(),
|
|
653
700
|
tcp,
|
|
654
701
|
ws,
|
|
655
702
|
});
|
|
@@ -658,13 +705,15 @@ export class NatService {
|
|
|
658
705
|
const forward = this.currentSettings.forwards[nodeId]?.[kind];
|
|
659
706
|
if (forward !== undefined)
|
|
660
707
|
return manualRoute(internalPort, forward);
|
|
708
|
+
if (this.networkMode() === 'public')
|
|
709
|
+
return directRoute(internalPort);
|
|
661
710
|
const existing = entry?.routes[kind];
|
|
662
711
|
if (existing && existing.internalPort === internalPort) {
|
|
663
712
|
const route = this.effectiveRoute(existing);
|
|
664
713
|
if (route.source !== null || route.error !== null)
|
|
665
714
|
return route;
|
|
666
715
|
}
|
|
667
|
-
if (this.
|
|
716
|
+
if (this.usesUpnp() && this.gateway.found) {
|
|
668
717
|
return { internalPort, externalPort: internalPort, source: 'upnp', leaseExpiresAt: null, error: null };
|
|
669
718
|
}
|
|
670
719
|
return noRoute(internalPort);
|