@celilo/e2e 0.11.3 → 0.12.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/config/resolver/unbound.conf +4 -0
- package/config/routing/public-resolver-routes.sh +33 -0
- package/config/routing/resolver-internal-routes.sh +5 -0
- package/docker/Dockerfile.ip-echo +20 -0
- package/docker/Dockerfile.resolver +5 -1
- package/package.json +2 -2
- package/simulators/greenwave/state.ts +22 -3
- package/simulators/ip-echo/server.ts +76 -0
- package/src/docker-compose-generator.ts +43 -4
- package/src/network-builder.ts +11 -0
- package/src/simulator-ips.ts +21 -0
- package/src/types.ts +9 -0
|
@@ -38,6 +38,10 @@ server:
|
|
|
38
38
|
# releases, so the signal module's deploy-time download resolves inside
|
|
39
39
|
# the sealed network. See SIMULATOR_IPS.SIGNAL_RELEASE.
|
|
40
40
|
local-data: "signal-release.lab. IN A 100.64.0.62"
|
|
41
|
+
# IP echo simulator — the rig's api.ipify.org. celilo's `public_dns` check
|
|
42
|
+
# asks it the one thing the fleet cannot ask itself: what address it is
|
|
43
|
+
# currently reachable at. See SIMULATOR_IPS.IP_ECHO.
|
|
44
|
+
local-data: "ip-echo.lab. IN A 100.64.0.65"
|
|
41
45
|
# External website prober simulator — without this override, isitup.org
|
|
42
46
|
# falls through the "forward-zone: ." below and resolves to the REAL
|
|
43
47
|
# Namecheap-parked domain on the public internet.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# The OFF-FLEET public resolver — the rig's stand-in for 1.1.1.1.
|
|
5
|
+
#
|
|
6
|
+
# It exists because the fleet's own resolver cannot answer the question the
|
|
7
|
+
# `public_dns` check asks. `system init` points celilo at comcast-resolver
|
|
8
|
+
# (the ISP's), and a deployed fleet at the internal split-horizon resolver;
|
|
9
|
+
# both answer with whatever is right for a client INSIDE. celilo refuses to use
|
|
10
|
+
# either as evidence about the public internet, so the simulation has to
|
|
11
|
+
# contain a second, genuinely independent public resolver — exactly the
|
|
12
|
+
# real-world distinction between your ISP's resolver and 1.1.1.1.
|
|
13
|
+
#
|
|
14
|
+
# Unlike comcast-resolver this sits ON internet-external (the public internet
|
|
15
|
+
# network) alongside the authoritative servers, so its stub-zones reach
|
|
16
|
+
# namecheap-dns on the same wire with no route needed.
|
|
17
|
+
#
|
|
18
|
+
# Replying to the CUSTOMER is a different matter, and getting this wrong is why
|
|
19
|
+
# the first run of the public_dns e2e stage saw nothing: a query from the
|
|
20
|
+
# fleet arrives with the customer's public source address, and the answer goes
|
|
21
|
+
# out via Docker's bridge gateway, which has no path across networks. The reply
|
|
22
|
+
# is dropped and the query times out — which celilo correctly records as
|
|
23
|
+
# *undetermined* rather than a pass, so the check went quiet instead of firing.
|
|
24
|
+
# Every host on internet-external exits by the ISP edge; this is that route.
|
|
25
|
+
# See config/routing/public-sim-entrypoint.sh, and the recurrence gate in
|
|
26
|
+
# src/public-sim-routes.test.ts that catches a simulator shipped without it.
|
|
27
|
+
ip route del default 2>/dev/null || true
|
|
28
|
+
ip route add default via 100.64.0.1 2>/dev/null || true
|
|
29
|
+
|
|
30
|
+
/usr/sbin/unbound -d &
|
|
31
|
+
|
|
32
|
+
echo "public-resolver ready on 100.64.0.64 - the rig's 1.1.1.1 (SIMULATOR_IPS.PUBLIC_RESOLVER)"
|
|
33
|
+
sleep infinity
|
|
@@ -57,6 +57,11 @@ $(printf "${SPLIT_HORIZON_BLOCK}")
|
|
|
57
57
|
# MinIO S3 simulator — backup/restore target for the migration e2e.
|
|
58
58
|
# See SIMULATOR_IPS.MINIO.
|
|
59
59
|
local-data: "minio.lab. IN A 100.64.0.61"
|
|
60
|
+
# IP echo simulator — the rig's api.ipify.org. celilo's public_dns check
|
|
61
|
+
# asks it the one thing the fleet cannot ask itself: what address it is
|
|
62
|
+
# currently reachable at. See SIMULATOR_IPS.IP_ECHO.
|
|
63
|
+
# (Backtick-free on purpose — see the note above; this heredoc is unquoted.)
|
|
64
|
+
local-data: "ip-echo.lab. IN A 100.64.0.65"
|
|
60
65
|
# signal-cli daemon (real, unlinked) and its simulator counterpart. Both
|
|
61
66
|
# sit on the `internal` network — celilo-mgr reaches a notification
|
|
62
67
|
# transport locally; a transport needing public ingress could not tell
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
FROM oven/bun:latest
|
|
2
|
+
|
|
3
|
+
# iproute2 so the entrypoint can see its own addressing when debugging; the
|
|
4
|
+
# service itself needs no routing changes (it sits on internet-external with
|
|
5
|
+
# the other public simulators and answers requests that arrive there).
|
|
6
|
+
RUN apt-get update && apt-get install -y iproute2 curl \
|
|
7
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
8
|
+
|
|
9
|
+
COPY simulators/ip-echo/server.ts /simulator/server.ts
|
|
10
|
+
|
|
11
|
+
# Route the customer's public prefix via the ISP edge, like every other host
|
|
12
|
+
# on internet-external. Without it a reply to the customer's WAN address goes
|
|
13
|
+
# to Docker's bridge gateway, which has no path across networks, and the
|
|
14
|
+
# connection simply hangs. See config/routing/public-sim-entrypoint.sh.
|
|
15
|
+
COPY config/routing/public-sim-entrypoint.sh /usr/local/bin/public-sim-entrypoint.sh
|
|
16
|
+
|
|
17
|
+
WORKDIR /simulator
|
|
18
|
+
EXPOSE 80
|
|
19
|
+
ENTRYPOINT ["/bin/sh", "/usr/local/bin/public-sim-entrypoint.sh"]
|
|
20
|
+
CMD ["bun", "run", "server.ts"]
|
|
@@ -14,7 +14,11 @@ RUN chown -R root:unbound /etc/unbound
|
|
|
14
14
|
COPY config/resolver/unbound.conf /etc/unbound/unbound.conf
|
|
15
15
|
COPY config/resolver/root.hints /etc/unbound/root.hints
|
|
16
16
|
COPY config/routing/resolver-routes.sh /startup.sh
|
|
17
|
-
|
|
17
|
+
# The off-fleet public resolver runs from the SAME image with a different
|
|
18
|
+
# startup (it sits on internet-external, so it needs no route through fw-ext).
|
|
19
|
+
# One image, two `command:` values — see docker-compose-generator.ts.
|
|
20
|
+
COPY config/routing/public-resolver-routes.sh /public-resolver-startup.sh
|
|
21
|
+
RUN chmod +x /startup.sh /public-resolver-startup.sh
|
|
18
22
|
|
|
19
23
|
EXPOSE 53/tcp 53/udp
|
|
20
24
|
CMD ["/startup.sh"]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celilo/e2e",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "E2E test infrastructure for Celilo-deployed applications. Provides a simulated internet with DNS hierarchy, ACME server, firewalls, and target machines in Docker.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@celilo/cli-display": "^0.1.10",
|
|
41
|
-
"@celilo/event-bus": "^0.
|
|
41
|
+
"@celilo/event-bus": "^0.4.0",
|
|
42
42
|
"yaml": "^2.8.0",
|
|
43
43
|
"zod": "^3.24.1"
|
|
44
44
|
},
|
|
@@ -9,6 +9,25 @@ const PUBLIC_IP = process.env.ROUTER_PUBLIC_IP || '100.100.0.100';
|
|
|
9
9
|
// The network interface facing the ISP (isp-external network)
|
|
10
10
|
const EXTERNAL_INTERFACE = process.env.EXTERNAL_INTERFACE || 'eth1';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Vendor extension prefix for TR-181 parameters.
|
|
14
|
+
*
|
|
15
|
+
* This simulator stands in for two real devices that speak an identical CGI
|
|
16
|
+
* protocol and differ only here: the GreenWave C4000XG (`X_GWS_Via`, plus the
|
|
17
|
+
* Lantiq-chipset `X_LANTIQ_COM_INTERFACE`) and the Axon Networks Q1000K, which
|
|
18
|
+
* folds both into `X_AXON_`. Defaults to the GreenWave spelling so the existing
|
|
19
|
+
* greenwave suite is byte-for-byte unaffected.
|
|
20
|
+
*/
|
|
21
|
+
const VENDOR_PREFIX = process.env.ROUTER_VENDOR_PREFIX || 'X_GWS_';
|
|
22
|
+
const PARAM_VIA = `${VENDOR_PREFIX}Via`;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The C4000XG named the interface selector after the chipset vendor rather than
|
|
26
|
+
* the device vendor, so it does NOT follow the prefix. Axon firmware does.
|
|
27
|
+
*/
|
|
28
|
+
const PARAM_INTERFACE =
|
|
29
|
+
VENDOR_PREFIX === 'X_GWS_' ? 'X_LANTIQ_COM_INTERFACE' : `${VENDOR_PREFIX}INTERFACE`;
|
|
30
|
+
|
|
12
31
|
const sessions = new Map<string, Session>();
|
|
13
32
|
const portMappings: PortMapping[] = [];
|
|
14
33
|
let nextIndex = 1;
|
|
@@ -72,7 +91,7 @@ export function addPortMapping(params: Record<string, string>): CgiObject[] {
|
|
|
72
91
|
enable: params.Enable || '1',
|
|
73
92
|
description: params.Description || '',
|
|
74
93
|
protocol: params.Protocol || 'TCP',
|
|
75
|
-
via: params
|
|
94
|
+
via: params[PARAM_VIA] || 'UI',
|
|
76
95
|
remoteHost: params.RemoteHost || '',
|
|
77
96
|
};
|
|
78
97
|
|
|
@@ -200,9 +219,9 @@ function mappingToParams(pm: PortMapping): CgiParam[] {
|
|
|
200
219
|
{ ParamName: 'Enable', ParamValue: pm.enable },
|
|
201
220
|
{ ParamName: 'Description', ParamValue: pm.description },
|
|
202
221
|
{ ParamName: 'Protocol', ParamValue: pm.protocol },
|
|
203
|
-
{ ParamName:
|
|
222
|
+
{ ParamName: PARAM_VIA, ParamValue: pm.via },
|
|
204
223
|
{ ParamName: 'RemoteHost', ParamValue: pm.remoteHost },
|
|
205
|
-
{ ParamName:
|
|
224
|
+
{ ParamName: PARAM_INTERFACE, ParamValue: 'wan' },
|
|
206
225
|
{ ParamName: 'AllInterfaces', ParamValue: '0' },
|
|
207
226
|
];
|
|
208
227
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP echo simulator — the rig's stand-in for api.ipify.org.
|
|
3
|
+
*
|
|
4
|
+
* Reports the source address a request appears to come from, and nothing else.
|
|
5
|
+
* celilo's `public_dns` check uses it for the ONE thing it cannot ask itself:
|
|
6
|
+
* what address the fleet is currently reachable at.
|
|
7
|
+
*
|
|
8
|
+
* Why an independent service rather than the registrar's own answer: the DDNS
|
|
9
|
+
* response carries the address the registrar says it published, so comparing
|
|
10
|
+
* public DNS against it is self-agreement. Worse than useless, in fact —
|
|
11
|
+
* Namecheap returns `ErrCount 0` with the requested address echoed back for
|
|
12
|
+
* `www` updates it does not apply, so the response can be actively false. The
|
|
13
|
+
* expectation has to come from somewhere that has no stake in the answer.
|
|
14
|
+
*
|
|
15
|
+
* This sits on `internet-external`, so a request from the customer fleet
|
|
16
|
+
* arrives having been SNAT'd by the customer firewall — the source address it
|
|
17
|
+
* sees IS the firewall's external address, which is the address the internet
|
|
18
|
+
* must dial. That is the same mechanism DDNS source-IP detection relies on,
|
|
19
|
+
* observed independently.
|
|
20
|
+
*
|
|
21
|
+
* Plain HTTP on purpose. Real ipify is HTTPS, but TLS here would mean an ACME
|
|
22
|
+
* dance (Caddy + Pebble, as the isitup sim does) to test nothing this check
|
|
23
|
+
* cares about: the property under test is "an off-fleet observer reports our
|
|
24
|
+
* egress", not certificate validation. The path from the fleet to here still
|
|
25
|
+
* crosses both firewalls exactly as a real request would.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// Marks this file a module rather than a script. Without it the simulators
|
|
29
|
+
// share one global scope at typecheck time, and `PORT`/`server` collide with
|
|
30
|
+
// the identically-named top-level bindings in simulators/isitup/server.ts.
|
|
31
|
+
export {};
|
|
32
|
+
|
|
33
|
+
interface RequestIpProvider {
|
|
34
|
+
requestIP(req: Request): { address: string } | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const PORT = Number(process.env.ECHO_PORT) || 80;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Strip the IPv6-mapped-IPv4 prefix Bun returns for IPv4 connections over a
|
|
41
|
+
* dual-stack socket (`::ffff:100.100.0.100` → `100.100.0.100`). Same fix as
|
|
42
|
+
* the namecheap-ddns simulator: the mapped form is not a valid A-record value,
|
|
43
|
+
* and here it would simply never compare equal to what DNS serves.
|
|
44
|
+
*/
|
|
45
|
+
function normalizeIp(address: string): string {
|
|
46
|
+
const mapped = address.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
|
|
47
|
+
return mapped ? mapped[1] : address;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sourceIp(req: Request, server: RequestIpProvider): string | null {
|
|
51
|
+
const forwarded = req.headers.get('x-forwarded-for');
|
|
52
|
+
if (forwarded) return normalizeIp(forwarded.split(',')[0].trim());
|
|
53
|
+
const remote = server.requestIP(req);
|
|
54
|
+
return remote?.address ? normalizeIp(remote.address) : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const server = Bun.serve({
|
|
58
|
+
port: PORT,
|
|
59
|
+
fetch(req, server) {
|
|
60
|
+
const url = new URL(req.url);
|
|
61
|
+
|
|
62
|
+
if (url.pathname === '/health') return Response.json({ ok: true });
|
|
63
|
+
|
|
64
|
+
const ip = sourceIp(req, server);
|
|
65
|
+
if (!ip) {
|
|
66
|
+
// NEVER a sentinel. A prior version of the DDNS simulator returned
|
|
67
|
+
// `0.0.0.0` when it could not determine the source, and that value went
|
|
68
|
+
// straight into a public zone file. An unanswerable probe must read as
|
|
69
|
+
// an error, which celilo counts as *undetermined* — never as a pass.
|
|
70
|
+
return new Response('source address could not be determined', { status: 500 });
|
|
71
|
+
}
|
|
72
|
+
return new Response(ip, { headers: { 'Content-Type': 'text/plain' } });
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
console.log(`[ip-echo] listening on port ${server.port}`);
|
|
@@ -146,7 +146,7 @@ const PROTECTED_SUBNETS = (['dmz', 'app', 'secure', 'secure-mgmt'] as const)
|
|
|
146
146
|
.map((zone) => ZONE_SUBNETS[zone])
|
|
147
147
|
.join(' ');
|
|
148
148
|
|
|
149
|
-
function defaultTopologyServices(): Record<string, unknown> {
|
|
149
|
+
function defaultTopologyServices(config: NetworkConfig): Record<string, unknown> {
|
|
150
150
|
return {
|
|
151
151
|
'fw-main': baseService({
|
|
152
152
|
build: { context: '.', dockerfile: 'docker/Dockerfile.firewall' },
|
|
@@ -178,6 +178,9 @@ function defaultTopologyServices(): Record<string, unknown> {
|
|
|
178
178
|
devices: ['/dev/net/tun'],
|
|
179
179
|
sysctls: ROUTER_SYSCTLS,
|
|
180
180
|
volumes: ['ssh-keys:/ssh-keys:ro'],
|
|
181
|
+
// Which real ISP router this sim stands in for. The protocol is identical
|
|
182
|
+
// across the two; only the TR-181 vendor extension prefix differs.
|
|
183
|
+
environment: { ROUTER_VENDOR_PREFIX: config.routerVendorPrefix ?? 'X_GWS_' },
|
|
181
184
|
}),
|
|
182
185
|
};
|
|
183
186
|
}
|
|
@@ -211,7 +214,10 @@ function directInternetTopologyServices(): Record<string, unknown> {
|
|
|
211
214
|
};
|
|
212
215
|
}
|
|
213
216
|
|
|
214
|
-
const TOPOLOGY_SERVICES: Record<
|
|
217
|
+
const TOPOLOGY_SERVICES: Record<
|
|
218
|
+
TopologyPreset,
|
|
219
|
+
(config: NetworkConfig) => Record<string, unknown>
|
|
220
|
+
> = {
|
|
215
221
|
default: defaultTopologyServices,
|
|
216
222
|
'direct-internet': directInternetTopologyServices,
|
|
217
223
|
};
|
|
@@ -315,6 +321,39 @@ export function generateSharedInfraYaml(): string {
|
|
|
315
321
|
volumes: getRegistryVolumes(),
|
|
316
322
|
});
|
|
317
323
|
|
|
324
|
+
// OFF-FLEET recursive resolver — the rig's 1.1.1.1, and the vantage point
|
|
325
|
+
// celilo's `public_dns` check resolves from.
|
|
326
|
+
//
|
|
327
|
+
// A peer of comcast-resolver, not a replacement: the fleet is configured to
|
|
328
|
+
// use THAT one (`system init` writes dns.primary=100.100.0.1), and celilo
|
|
329
|
+
// refuses to accept a resolver it already asks as evidence about the public
|
|
330
|
+
// internet — a check that used the fleet's own resolver would pass whatever
|
|
331
|
+
// the internet sees, which is the celilo#626 blindness one layer up. So the
|
|
332
|
+
// simulation needs two independent public resolvers, exactly as the real
|
|
333
|
+
// world has your ISP's and Cloudflare's.
|
|
334
|
+
//
|
|
335
|
+
// Same image as comcast-resolver (identical unbound.conf → identical
|
|
336
|
+
// authoritative answers) with a different startup: this one sits ON
|
|
337
|
+
// internet-external with the authoritative servers, so it needs no route
|
|
338
|
+
// through fw-ext to reach them.
|
|
339
|
+
services['public-resolver'] = baseService({
|
|
340
|
+
build: { context: '.', dockerfile: 'docker/Dockerfile.resolver' },
|
|
341
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.PUBLIC_RESOLVER } },
|
|
342
|
+
cap_add: ['NET_ADMIN'],
|
|
343
|
+
command: '/public-resolver-startup.sh',
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// IP echo service — mimics api.ipify.org. Reports the source address a
|
|
347
|
+
// request appears to come from, which for the customer fleet is the
|
|
348
|
+
// firewall's external address after SNAT. This is where the `public_dns`
|
|
349
|
+
// check's EXPECTATION comes from; comparing public DNS against the
|
|
350
|
+
// registrar's own response would be self-agreement.
|
|
351
|
+
services['ip-echo'] = baseService({
|
|
352
|
+
build: { context: '.', dockerfile: 'docker/Dockerfile.ip-echo' },
|
|
353
|
+
networks: { 'internet-external': { ipv4_address: SIMULATOR_IPS.IP_ECHO } },
|
|
354
|
+
cap_add: ['NET_ADMIN'],
|
|
355
|
+
});
|
|
356
|
+
|
|
318
357
|
// External website prober simulator — mimics isitup.org's /api.json endpoint.
|
|
319
358
|
// Sits on internet-external so probe requests exercise the real public-facing
|
|
320
359
|
// path (target domain → DNS → ISP → firewall → Caddy). Uses comcast-resolver
|
|
@@ -532,7 +571,7 @@ export function generateTestComposeYaml(config: NetworkConfig, celiloRoot?: stri
|
|
|
532
571
|
|
|
533
572
|
// --- Topology-specific infrastructure (south of fw-ext) ---
|
|
534
573
|
const topologyFn = TOPOLOGY_SERVICES[config.topology] || TOPOLOGY_SERVICES.default;
|
|
535
|
-
Object.assign(services, topologyFn());
|
|
574
|
+
Object.assign(services, topologyFn(config));
|
|
536
575
|
|
|
537
576
|
// Give fw-main a leg on the control-plane network when celilo-mgr lives there,
|
|
538
577
|
// so the management box is routed like any other segmented network rather than
|
|
@@ -715,7 +754,7 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
715
754
|
});
|
|
716
755
|
|
|
717
756
|
const topologyFn = TOPOLOGY_SERVICES[config.topology] || TOPOLOGY_SERVICES.default;
|
|
718
|
-
Object.assign(services, topologyFn());
|
|
757
|
+
Object.assign(services, topologyFn(config));
|
|
719
758
|
|
|
720
759
|
const legacyCaddyIp =
|
|
721
760
|
config.dmzMachines.find((m) => m.name === 'caddy')?.ip ??
|
package/src/network-builder.ts
CHANGED
|
@@ -59,6 +59,17 @@ export class NetworkBuilder {
|
|
|
59
59
|
return this;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Make the `fw-isp` simulator stand in for an Axon Networks Q1000K rather
|
|
64
|
+
* than the default GreenWave C4000XG. The two speak an identical CGI
|
|
65
|
+
* protocol; only the TR-181 vendor extension prefix differs, so this swaps
|
|
66
|
+
* `X_GWS_Via`/`X_LANTIQ_COM_INTERFACE` for `X_AXON_Via`/`X_AXON_INTERFACE`.
|
|
67
|
+
*/
|
|
68
|
+
axonRouter(): this {
|
|
69
|
+
this.config.routerVendorPrefix = 'X_AXON_';
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
/**
|
|
63
74
|
* Place the celilo management container in a given zone. Defaults to
|
|
64
75
|
* `internal`; `secure-mgmt` gives celilo-mgr its own control-plane network
|
package/src/simulator-ips.ts
CHANGED
|
@@ -44,6 +44,27 @@ export const SIMULATOR_IPS = {
|
|
|
44
44
|
CPANEL_HOST: '100.64.0.63',
|
|
45
45
|
/** signal-cli release host — serves the tarball the signal module downloads at deploy time. */
|
|
46
46
|
SIGNAL_RELEASE: '100.64.0.62',
|
|
47
|
+
/**
|
|
48
|
+
* OFF-FLEET recursive resolver — the rig's stand-in for 1.1.1.1, and a peer
|
|
49
|
+
* of comcast-resolver rather than a replacement for it.
|
|
50
|
+
*
|
|
51
|
+
* The distinction is the whole point of celilo's `public_dns` check: the
|
|
52
|
+
* fleet's own resolver (its ISP's, or its internal split-horizon one)
|
|
53
|
+
* answers with whatever is correct for a client INSIDE, which is not
|
|
54
|
+
* evidence about what the internet sees. celilo REFUSES to use a resolver it
|
|
55
|
+
* is itself configured to use, so verifying public reachability requires a
|
|
56
|
+
* second, independent public resolver — and until this existed the topology
|
|
57
|
+
* had exactly one.
|
|
58
|
+
*/
|
|
59
|
+
PUBLIC_RESOLVER: '100.64.0.64',
|
|
60
|
+
/**
|
|
61
|
+
* IP echo service — the rig's stand-in for api.ipify.org. Reports the source
|
|
62
|
+
* address a request appears to come from, which for the customer fleet is
|
|
63
|
+
* the firewall's external address after SNAT. The `public_dns` check's
|
|
64
|
+
* expectation comes from here rather than from the registrar's own response,
|
|
65
|
+
* which is self-agreement (and, for a Namecheap `www` update, false).
|
|
66
|
+
*/
|
|
67
|
+
IP_ECHO: '100.64.0.65',
|
|
47
68
|
/** Pebble ACME server (replaces production Let's Encrypt). */
|
|
48
69
|
PEBBLE: '100.64.0.100',
|
|
49
70
|
} as const;
|
package/src/types.ts
CHANGED
|
@@ -55,6 +55,15 @@ export interface NetworkConfig {
|
|
|
55
55
|
domain: string;
|
|
56
56
|
ddnsPassword: string;
|
|
57
57
|
verifyRouting: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Which real ISP router the `fw-isp` simulator stands in for, expressed as
|
|
60
|
+
* its TR-181 vendor extension prefix. `X_GWS_` (the default) is the GreenWave
|
|
61
|
+
* C4000XG that `modules/greenwave` drives; `X_AXON_` is the Axon Networks
|
|
62
|
+
* Q1000K that `modules/axon` drives. The CGI protocol is identical across the
|
|
63
|
+
* two — this prefix IS the entire difference, which is why one simulator
|
|
64
|
+
* serves both rather than a near-duplicate second one.
|
|
65
|
+
*/
|
|
66
|
+
routerVendorPrefix?: string;
|
|
58
67
|
/** Extra volume mounts for the management container (host:container format) */
|
|
59
68
|
managementVolumes: string[];
|
|
60
69
|
/** Include a DHCP client container on the internal network */
|