@impz/testing 6.6.1 → 6.7.1
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/dist/index.js +641 -529
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -18923,12 +18923,12 @@ var BACKUP_TREE = {
|
|
|
18923
18923
|
buildImageDir: (digest) => join("images", toDigestHex(digest))
|
|
18924
18924
|
};
|
|
18925
18925
|
|
|
18926
|
-
// packages/daemon/
|
|
18926
|
+
// packages/daemon/src/test-utils/start-test-impd.ts
|
|
18927
18927
|
import { copyFileSync as copyFileSync5, mkdirSync as mkdirSync31, mkdtempSync, writeFileSync as writeFileSync16 } from "fs";
|
|
18928
18928
|
import { rm as rm2 } from "fs/promises";
|
|
18929
18929
|
import { tmpdir } from "os";
|
|
18930
18930
|
|
|
18931
|
-
// packages/test-utils/
|
|
18931
|
+
// packages/shared/src/test-utils/claim-free-ports.ts
|
|
18932
18932
|
import { onTestFinished } from "bun:test";
|
|
18933
18933
|
import { randomInt } from "crypto";
|
|
18934
18934
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -18991,449 +18991,13 @@ function readBusyPorts() {
|
|
|
18991
18991
|
return busy;
|
|
18992
18992
|
}
|
|
18993
18993
|
|
|
18994
|
-
// packages/daemon/
|
|
18994
|
+
// packages/daemon/src/test-utils/start-test-impd.ts
|
|
18995
18995
|
import { createORPCClient as createORPCClient2 } from "@orpc/client";
|
|
18996
18996
|
import { RPCLink } from "@orpc/client/fetch";
|
|
18997
18997
|
import invariant7 from "tiny-invariant";
|
|
18998
18998
|
|
|
18999
18999
|
// packages/daemon/src/config.ts
|
|
19000
19000
|
import { join as join4 } from "path";
|
|
19001
|
-
import * as z53 from "zod";
|
|
19002
|
-
// packages/shared/src/format-bytes.ts
|
|
19003
|
-
var UNITS = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
19004
|
-
function formatBytes(bytes) {
|
|
19005
|
-
let value = bytes;
|
|
19006
|
-
let unit = 0;
|
|
19007
|
-
while (value >= 1024 && unit < UNITS.length - 1) {
|
|
19008
|
-
value /= 1024;
|
|
19009
|
-
unit += 1;
|
|
19010
|
-
}
|
|
19011
|
-
const digits = unit === 0 || value >= 100 ? 0 : 1;
|
|
19012
|
-
return `${value.toFixed(digits)} ${UNITS[unit] ?? "B"}`;
|
|
19013
|
-
}
|
|
19014
|
-
// packages/shared/src/merge-bytes.ts
|
|
19015
|
-
function mergeBytes(chunks) {
|
|
19016
|
-
const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
|
|
19017
|
-
let offset = 0;
|
|
19018
|
-
for (const chunk of chunks) {
|
|
19019
|
-
merged.set(chunk, offset);
|
|
19020
|
-
offset += chunk.byteLength;
|
|
19021
|
-
}
|
|
19022
|
-
return merged;
|
|
19023
|
-
}
|
|
19024
|
-
// packages/shared/src/to-plain-octets.ts
|
|
19025
|
-
var DOTTED_ENTRY = /^(?<octets>\d{1,3}(?:\.\d{1,3}){3})(?<prefix>\/\d{1,2})?$/u;
|
|
19026
|
-
function toPlainOctets(entry) {
|
|
19027
|
-
const groups = DOTTED_ENTRY.exec(entry)?.groups;
|
|
19028
|
-
if (groups === undefined) {
|
|
19029
|
-
return entry;
|
|
19030
|
-
}
|
|
19031
|
-
const octets = (groups["octets"] ?? "").split(".").map((octet) => String(Number(octet)));
|
|
19032
|
-
return `${octets.join(".")}${groups["prefix"] ?? ""}`;
|
|
19033
|
-
}
|
|
19034
|
-
// packages/daemon/src/host-net/net/addressing.ts
|
|
19035
|
-
import { IPv4 as IPv43 } from "ipaddr.js";
|
|
19036
|
-
|
|
19037
|
-
// packages/daemon/src/host-net/net/addressing6.ts
|
|
19038
|
-
import { IPv6 as IPv62 } from "ipaddr.js";
|
|
19039
|
-
|
|
19040
|
-
// packages/daemon/src/host-net/net/parse-address.ts
|
|
19041
|
-
import { IPv4, IPv6 } from "ipaddr.js";
|
|
19042
|
-
function parseAddress(text) {
|
|
19043
|
-
if (IPv4.isValidFourPartDecimal(text)) {
|
|
19044
|
-
return IPv4.parse(text);
|
|
19045
|
-
}
|
|
19046
|
-
if (text.includes("%")) {
|
|
19047
|
-
return null;
|
|
19048
|
-
}
|
|
19049
|
-
const hex = toHexGroups(text);
|
|
19050
|
-
return hex !== null && IPv6.isValid(hex) ? IPv6.parse(hex) : null;
|
|
19051
|
-
}
|
|
19052
|
-
function toHexGroups(text) {
|
|
19053
|
-
const split = text.lastIndexOf(":");
|
|
19054
|
-
const tail = text.slice(split + 1);
|
|
19055
|
-
if (!tail.includes(".")) {
|
|
19056
|
-
return text;
|
|
19057
|
-
}
|
|
19058
|
-
if (!IPv4.isValidFourPartDecimal(tail)) {
|
|
19059
|
-
return null;
|
|
19060
|
-
}
|
|
19061
|
-
const [first = 0, second = 0, third = 0, fourth = 0] = IPv4.parse(tail).octets;
|
|
19062
|
-
return `${text.slice(0, split + 1)}${toGroup(first, second)}:${toGroup(third, fourth)}`;
|
|
19063
|
-
}
|
|
19064
|
-
function toGroup(high, low) {
|
|
19065
|
-
return (high * 256 + low).toString(16);
|
|
19066
|
-
}
|
|
19067
|
-
|
|
19068
|
-
// packages/daemon/src/host-net/net/parse-cidr.ts
|
|
19069
|
-
import { IPv4 as IPv42 } from "ipaddr.js";
|
|
19070
|
-
function parseCidr(text) {
|
|
19071
|
-
const [addressText = "", prefix, ...rest] = text.split("/");
|
|
19072
|
-
const address = parseAddress(addressText);
|
|
19073
|
-
if (address === null || rest.length > 0 || prefix !== undefined && !/^\d{1,3}$/v.test(prefix)) {
|
|
19074
|
-
return null;
|
|
19075
|
-
}
|
|
19076
|
-
const maxBits = address instanceof IPv42 ? 32 : 128;
|
|
19077
|
-
const bits = prefix === undefined ? maxBits : Number(prefix);
|
|
19078
|
-
return bits > maxBits ? null : { address, bits };
|
|
19079
|
-
}
|
|
19080
|
-
|
|
19081
|
-
// packages/daemon/src/host-net/net/addressing6.ts
|
|
19082
|
-
var GROUPS = 8;
|
|
19083
|
-
var GROUP_BITS = 16n;
|
|
19084
|
-
var PREFIX_LENGTH = 64;
|
|
19085
|
-
var GATEWAY_IP6 = "fe80::1";
|
|
19086
|
-
function parseIpv6(text) {
|
|
19087
|
-
const address = parseAddress(text);
|
|
19088
|
-
return address instanceof IPv62 ? toBigint(address) : null;
|
|
19089
|
-
}
|
|
19090
|
-
function formatIpv6(address) {
|
|
19091
|
-
const groups = Array.from({ length: GROUPS }, (_, index) => Number(address >> GROUP_BITS * BigInt(GROUPS - 1 - index) & 0xffffn));
|
|
19092
|
-
return new IPv62(groups).toRFC5952String();
|
|
19093
|
-
}
|
|
19094
|
-
function formatCidr6(text) {
|
|
19095
|
-
const parsed = parseCidr(text);
|
|
19096
|
-
if (parsed === null || !(parsed.address instanceof IPv62)) {
|
|
19097
|
-
return null;
|
|
19098
|
-
}
|
|
19099
|
-
const hostBits = BigInt(128 - parsed.bits);
|
|
19100
|
-
const network = toBigint(parsed.address) >> hostBits << hostBits;
|
|
19101
|
-
return `${formatIpv6(network)}/${String(parsed.bits)}`;
|
|
19102
|
-
}
|
|
19103
|
-
function parsePrefix64(cidr) {
|
|
19104
|
-
const parsed = parseCidr(cidr);
|
|
19105
|
-
const network = parsed?.address instanceof IPv62 ? toBigint(parsed.address) : null;
|
|
19106
|
-
if (network === null || !cidr.endsWith(`/${String(PREFIX_LENGTH)}`) || network % 2n ** 64n !== 0n) {
|
|
19107
|
-
return null;
|
|
19108
|
-
}
|
|
19109
|
-
return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
|
|
19110
|
-
}
|
|
19111
|
-
function buildUlaPrefix(random) {
|
|
19112
|
-
if (random.length < 5) {
|
|
19113
|
-
throw new Error("a ULA global ID needs 5 random bytes");
|
|
19114
|
-
}
|
|
19115
|
-
const globalId = [...random.subarray(0, 5)].reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n);
|
|
19116
|
-
const network = (0xfdn << 40n) + globalId << 80n;
|
|
19117
|
-
return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
|
|
19118
|
-
}
|
|
19119
|
-
function findMappedIpv4(address) {
|
|
19120
|
-
const parsed = parseAddress(address);
|
|
19121
|
-
return parsed instanceof IPv62 && parsed.isIPv4MappedAddress() ? parsed.toIPv4Address().toString() : null;
|
|
19122
|
-
}
|
|
19123
|
-
function deriveGuestIp6(prefix, guestIpv4) {
|
|
19124
|
-
return formatIpv6(prefix.network + BigInt(guestIpv4));
|
|
19125
|
-
}
|
|
19126
|
-
function toBigint(address) {
|
|
19127
|
-
return address.parts.reduce((acc, group) => (acc << GROUP_BITS) + BigInt(group), 0n);
|
|
19128
|
-
}
|
|
19129
|
-
|
|
19130
|
-
// packages/daemon/src/host-net/net/addressing.ts
|
|
19131
|
-
var SLOT_SIZE = 4;
|
|
19132
|
-
var SLOT_PREFIX_LENGTH = 30;
|
|
19133
|
-
function parseSubnet(cidr) {
|
|
19134
|
-
const parsed = cidr.includes("/") ? parseCidr(cidr) : null;
|
|
19135
|
-
if (parsed === null || !(parsed.address instanceof IPv43)) {
|
|
19136
|
-
throw new Error(`subnet is not an IPv4 CIDR: ${cidr}`);
|
|
19137
|
-
}
|
|
19138
|
-
if (parsed.bits < 8 || parsed.bits > SLOT_PREFIX_LENGTH) {
|
|
19139
|
-
throw new Error(`subnet prefix must be between /8 and /30: ${cidr}`);
|
|
19140
|
-
}
|
|
19141
|
-
const network = toNumber(parsed.address);
|
|
19142
|
-
if (network % 2 ** (32 - parsed.bits) !== 0) {
|
|
19143
|
-
throw new Error(`subnet has host bits set: ${cidr}`);
|
|
19144
|
-
}
|
|
19145
|
-
return { network, prefixLength: parsed.bits };
|
|
19146
|
-
}
|
|
19147
|
-
var TAILNET_NETWORK = 100 * 2 ** 24 + 64 * 2 ** 16;
|
|
19148
|
-
var TAILNET_SIZE = 2 ** 22;
|
|
19149
|
-
function isTailnetOverlap(subnet) {
|
|
19150
|
-
const start = subnet.network;
|
|
19151
|
-
const end = start + 2 ** (32 - subnet.prefixLength);
|
|
19152
|
-
return start < TAILNET_NETWORK + TAILNET_SIZE && TAILNET_NETWORK < end;
|
|
19153
|
-
}
|
|
19154
|
-
function countSlots(subnet) {
|
|
19155
|
-
return 2 ** (32 - subnet.prefixLength) / SLOT_SIZE;
|
|
19156
|
-
}
|
|
19157
|
-
function deriveSlotAddress(slot, plan) {
|
|
19158
|
-
if (!Number.isInteger(slot) || slot < 0 || slot >= countSlots(plan.subnet)) {
|
|
19159
|
-
throw new RangeError(`slot ${String(slot)} is outside the subnet`);
|
|
19160
|
-
}
|
|
19161
|
-
const base = plan.subnet.network + slot * SLOT_SIZE;
|
|
19162
|
-
const guest = base + 2;
|
|
19163
|
-
return {
|
|
19164
|
-
slot,
|
|
19165
|
-
tap: `imp${String(slot)}`,
|
|
19166
|
-
hostIp: formatIpv4(base + 1),
|
|
19167
|
-
guestIp: formatIpv4(guest),
|
|
19168
|
-
prefixLength: SLOT_PREFIX_LENGTH,
|
|
19169
|
-
netmask: "255.255.255.252",
|
|
19170
|
-
guestMac: ["06", "00", ...splitOctets(guest).map((octet) => toHexByte(octet))].join(":"),
|
|
19171
|
-
hostMac: ["06", "01", ...splitOctets(base + 1).map((octet) => toHexByte(octet))].join(":"),
|
|
19172
|
-
guestIp6: plan.prefix6 === undefined || plan.prefix6 === null ? null : deriveGuestIp6(plan.prefix6, guest),
|
|
19173
|
-
tailnetPort: plan.portBase + slot
|
|
19174
|
-
};
|
|
19175
|
-
}
|
|
19176
|
-
function parseIpv4(text, allowMapped = false) {
|
|
19177
|
-
const address = parseAddress(text);
|
|
19178
|
-
if (address instanceof IPv43) {
|
|
19179
|
-
return toNumber(address);
|
|
19180
|
-
}
|
|
19181
|
-
return allowMapped && address?.isIPv4MappedAddress() === true ? toNumber(address.toIPv4Address()) : null;
|
|
19182
|
-
}
|
|
19183
|
-
function findPeerSlot(peer, local, subnet) {
|
|
19184
|
-
const peerIp = parseIpv4(peer, true);
|
|
19185
|
-
const localIp = parseIpv4(local, true);
|
|
19186
|
-
if (peerIp === null || localIp === null) {
|
|
19187
|
-
return null;
|
|
19188
|
-
}
|
|
19189
|
-
const offset = peerIp - subnet.network;
|
|
19190
|
-
if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
|
|
19191
|
-
return null;
|
|
19192
|
-
}
|
|
19193
|
-
return localIp === peerIp - 1 ? (offset - 2) / SLOT_SIZE : null;
|
|
19194
|
-
}
|
|
19195
|
-
function findGuestSlot(address, subnet) {
|
|
19196
|
-
const ip = parseIpv4(address, true);
|
|
19197
|
-
const offset = ip === null ? -1 : ip - subnet.network;
|
|
19198
|
-
if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
|
|
19199
|
-
return null;
|
|
19200
|
-
}
|
|
19201
|
-
return (offset - 2) / SLOT_SIZE;
|
|
19202
|
-
}
|
|
19203
|
-
function formatSubnet(subnet) {
|
|
19204
|
-
return `${formatIpv4(subnet.network)}/${String(subnet.prefixLength)}`;
|
|
19205
|
-
}
|
|
19206
|
-
function formatCidr4(text) {
|
|
19207
|
-
const parsed = parseCidr(toPlainOctets(text));
|
|
19208
|
-
if (parsed === null || !(parsed.address instanceof IPv43)) {
|
|
19209
|
-
return null;
|
|
19210
|
-
}
|
|
19211
|
-
const ip = toNumber(parsed.address);
|
|
19212
|
-
return formatSubnet({ network: ip - ip % 2 ** (32 - parsed.bits), prefixLength: parsed.bits });
|
|
19213
|
-
}
|
|
19214
|
-
function toNumber(address) {
|
|
19215
|
-
return address.octets.reduce((acc, octet) => acc * 256 + octet, 0);
|
|
19216
|
-
}
|
|
19217
|
-
function formatIpv4(address) {
|
|
19218
|
-
return splitOctets(address).join(".");
|
|
19219
|
-
}
|
|
19220
|
-
function splitOctets(address) {
|
|
19221
|
-
return [
|
|
19222
|
-
Math.floor(address / 2 ** 24) % 256,
|
|
19223
|
-
Math.floor(address / 2 ** 16) % 256,
|
|
19224
|
-
Math.floor(address / 2 ** 8) % 256,
|
|
19225
|
-
address % 256
|
|
19226
|
-
];
|
|
19227
|
-
}
|
|
19228
|
-
function toHexByte(octet) {
|
|
19229
|
-
return octet.toString(16).padStart(2, "0");
|
|
19230
|
-
}
|
|
19231
|
-
|
|
19232
|
-
// packages/daemon/src/host-net/net/ipv6-plan.ts
|
|
19233
|
-
import { randomBytes } from "crypto";
|
|
19234
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
19235
|
-
import { dirname, join as join2 } from "path";
|
|
19236
|
-
|
|
19237
|
-
// packages/daemon/src/platform/process/get-error-message.ts
|
|
19238
|
-
function getErrorMessage(error) {
|
|
19239
|
-
return error instanceof Error ? error.message : String(error);
|
|
19240
|
-
}
|
|
19241
|
-
|
|
19242
|
-
// packages/daemon/src/platform/process/get-impd-logger.ts
|
|
19243
|
-
import { getLogger } from "@logtape/logtape";
|
|
19244
|
-
function getImpdLogger(area) {
|
|
19245
|
-
return getLogger(["impd", area]);
|
|
19246
|
-
}
|
|
19247
|
-
|
|
19248
|
-
// packages/daemon/src/platform/process/run-command.ts
|
|
19249
|
-
async function runCommand(argv, options = {}) {
|
|
19250
|
-
const child = Bun.spawn([...argv], {
|
|
19251
|
-
stdin: "ignore",
|
|
19252
|
-
stdout: "pipe",
|
|
19253
|
-
stderr: "pipe",
|
|
19254
|
-
env: options.env === undefined ? process.env : { ...options.env },
|
|
19255
|
-
...options.signal !== undefined && { signal: options.signal }
|
|
19256
|
-
});
|
|
19257
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
19258
|
-
new Response(child.stdout).text(),
|
|
19259
|
-
new Response(child.stderr).text(),
|
|
19260
|
-
child.exited
|
|
19261
|
-
]);
|
|
19262
|
-
return { exitCode, stdout, stderr };
|
|
19263
|
-
}
|
|
19264
|
-
async function runChecked(argv, options = {}, run = runCommand) {
|
|
19265
|
-
const result = await run(argv, options);
|
|
19266
|
-
if (result.exitCode !== 0) {
|
|
19267
|
-
throw buildExitError(argv, result);
|
|
19268
|
-
}
|
|
19269
|
-
return result.stdout;
|
|
19270
|
-
}
|
|
19271
|
-
function buildExitError(argv, result) {
|
|
19272
|
-
const output = result.stderr.trim() || (result.stdout ?? "").trim();
|
|
19273
|
-
return new Error(`${argv.join(" ")} exited ${String(result.exitCode)}: ${output}`);
|
|
19274
|
-
}
|
|
19275
|
-
|
|
19276
|
-
// packages/daemon/src/host-net/net/nat66-ruleset.ts
|
|
19277
|
-
var NAT66_TABLE = "ip6 imp_nat66";
|
|
19278
|
-
function buildNat66Ruleset(prefix, uplink) {
|
|
19279
|
-
return [
|
|
19280
|
-
`table ${NAT66_TABLE} {}`,
|
|
19281
|
-
`delete table ${NAT66_TABLE}`,
|
|
19282
|
-
`table ${NAT66_TABLE} {`,
|
|
19283
|
-
" chain postrouting {",
|
|
19284
|
-
" type nat hook postrouting priority srcnat; policy accept;",
|
|
19285
|
-
` oifname "${uplink}" ip6 saddr ${prefix} masquerade`,
|
|
19286
|
-
" }",
|
|
19287
|
-
"}",
|
|
19288
|
-
""
|
|
19289
|
-
].join(`
|
|
19290
|
-
`);
|
|
19291
|
-
}
|
|
19292
|
-
function buildNat66Removal() {
|
|
19293
|
-
return `table ${NAT66_TABLE} {}
|
|
19294
|
-
delete table ${NAT66_TABLE}
|
|
19295
|
-
`;
|
|
19296
|
-
}
|
|
19297
|
-
|
|
19298
|
-
// packages/daemon/src/host-net/net/ipv6-plan.ts
|
|
19299
|
-
function parseIpv6Setting(text) {
|
|
19300
|
-
if (text === "auto" || text === "off") {
|
|
19301
|
-
return { kind: text };
|
|
19302
|
-
}
|
|
19303
|
-
const prefix = parsePrefix64(text);
|
|
19304
|
-
if (prefix === null) {
|
|
19305
|
-
throw new Error(`IMP_SUBNET6 must be auto, off or an IPv6 /64, got ${text}`);
|
|
19306
|
-
}
|
|
19307
|
-
return { kind: "routed", prefix };
|
|
19308
|
-
}
|
|
19309
|
-
async function resolveIpv6Plan(setting, deps) {
|
|
19310
|
-
const stopIpv6 = async (reason) => {
|
|
19311
|
-
await removeNat66(deps);
|
|
19312
|
-
getImpdLogger("ipv6").info("off ({reason})", { reason });
|
|
19313
|
-
return null;
|
|
19314
|
-
};
|
|
19315
|
-
if (setting.kind === "off") {
|
|
19316
|
-
return stopIpv6("IMP_SUBNET6=off");
|
|
19317
|
-
}
|
|
19318
|
-
const uplink = await deps.readDefaultRoute();
|
|
19319
|
-
if (setting.kind === "auto" && uplink === null) {
|
|
19320
|
-
return stopIpv6("IMP_SUBNET6=auto, and the container has no IPv6 default route");
|
|
19321
|
-
}
|
|
19322
|
-
const missing = await deps.checkHostRules();
|
|
19323
|
-
if (missing !== null) {
|
|
19324
|
-
return stopIpv6(`the host's IPv6 rules are not in place: ${missing}`);
|
|
19325
|
-
}
|
|
19326
|
-
if (setting.kind === "routed") {
|
|
19327
|
-
await removeNat66(deps);
|
|
19328
|
-
getImpdLogger("ipv6").info("{text}, routed{route}", {
|
|
19329
|
-
text: setting.prefix.text,
|
|
19330
|
-
route: uplink === null ? "; the container has no IPv6 default route" : ` via ${uplink}`
|
|
19331
|
-
});
|
|
19332
|
-
return { prefix: setting.prefix, nat66: false, uplink };
|
|
19333
|
-
}
|
|
19334
|
-
const prefix = deps.readUlaPrefix();
|
|
19335
|
-
try {
|
|
19336
|
-
await deps.runNft(buildNat66Ruleset(prefix.text, uplink ?? ""));
|
|
19337
|
-
} catch (error) {
|
|
19338
|
-
return stopIpv6(`NAT66: ${getErrorMessage(error)}`);
|
|
19339
|
-
}
|
|
19340
|
-
getImpdLogger("ipv6").info("{text}, NAT66 out of {uplink}", {
|
|
19341
|
-
text: prefix.text,
|
|
19342
|
-
uplink: uplink ?? ""
|
|
19343
|
-
});
|
|
19344
|
-
return { prefix, nat66: true, uplink };
|
|
19345
|
-
}
|
|
19346
|
-
async function removeNat66(deps) {
|
|
19347
|
-
try {
|
|
19348
|
-
await deps.runNft(buildNat66Removal());
|
|
19349
|
-
} catch {}
|
|
19350
|
-
}
|
|
19351
|
-
var HOST_RULES6 = [
|
|
19352
|
-
["filter", "-A INPUT -i imp+ -j DROP"],
|
|
19353
|
-
["filter", "-A FORWARD -i imp+ -o imp+ -j DROP"],
|
|
19354
|
-
["filter", "-A FORWARD -o imp+ -j DROP"],
|
|
19355
|
-
["filter", "-A FORWARD -i imp+ -j DROP"],
|
|
19356
|
-
["raw", "-A PREROUTING -i imp+ -m rpfilter --invert -j DROP"]
|
|
19357
|
-
];
|
|
19358
|
-
var HOST_SYSCTLS6 = [
|
|
19359
|
-
["default/accept_ra", "0"],
|
|
19360
|
-
["default/accept_redirects", "0"],
|
|
19361
|
-
["all/forwarding", "1"]
|
|
19362
|
-
];
|
|
19363
|
-
async function checkHostRules6(procSys = "/proc/sys/net/ipv6/conf", run = runCommand) {
|
|
19364
|
-
for (const table of ["filter", "raw"]) {
|
|
19365
|
-
const result = await run(["ip6tables", "-w", "-t", table, "-S"]);
|
|
19366
|
-
const rules = new Set(result.stdout.split(`
|
|
19367
|
-
`).map((line) => line.trim()));
|
|
19368
|
-
const missing = HOST_RULES6.find(([inTable, rule]) => inTable === table && !rules.has(rule));
|
|
19369
|
-
if (result.exitCode !== 0) {
|
|
19370
|
-
return `ip6tables -t ${table}: ${result.stderr.trim()}`;
|
|
19371
|
-
}
|
|
19372
|
-
if (missing !== undefined) {
|
|
19373
|
-
return `no ip6tables -t ${table} ${missing[1]}`;
|
|
19374
|
-
}
|
|
19375
|
-
}
|
|
19376
|
-
for (const [key, wanted] of HOST_SYSCTLS6) {
|
|
19377
|
-
const value = readSysctl(join2(procSys, key));
|
|
19378
|
-
if (value !== wanted) {
|
|
19379
|
-
return `net.ipv6.conf.${key.replace("/", ".")} is ${value ?? "missing"}, not ${wanted}`;
|
|
19380
|
-
}
|
|
19381
|
-
}
|
|
19382
|
-
return null;
|
|
19383
|
-
}
|
|
19384
|
-
function readSysctl(path) {
|
|
19385
|
-
try {
|
|
19386
|
-
return readFileSync2(path, "utf8").trim();
|
|
19387
|
-
} catch {
|
|
19388
|
-
return null;
|
|
19389
|
-
}
|
|
19390
|
-
}
|
|
19391
|
-
function readOrCreateUlaPrefix(path) {
|
|
19392
|
-
if (existsSync2(path)) {
|
|
19393
|
-
const prefix = parsePrefix64(readFileSync2(path, "utf8").trim());
|
|
19394
|
-
if (prefix !== null) {
|
|
19395
|
-
return prefix;
|
|
19396
|
-
}
|
|
19397
|
-
}
|
|
19398
|
-
const prefix = buildUlaPrefix(randomBytes(5));
|
|
19399
|
-
mkdirSync2(dirname(path), { recursive: true });
|
|
19400
|
-
writeFileSync(path, `${prefix.text}
|
|
19401
|
-
`);
|
|
19402
|
-
return prefix;
|
|
19403
|
-
}
|
|
19404
|
-
|
|
19405
|
-
// packages/daemon/src/host-net/net/range-matcher.ts
|
|
19406
|
-
import { IPv4 as IPv44 } from "ipaddr.js";
|
|
19407
|
-
function buildRangeMatcher(ranges) {
|
|
19408
|
-
const ranges4 = [];
|
|
19409
|
-
const ranges6 = [];
|
|
19410
|
-
for (const range of ranges) {
|
|
19411
|
-
const cidr = parseCidr(range);
|
|
19412
|
-
if (cidr === null) {
|
|
19413
|
-
throw new Error(`not an address range: ${range}`);
|
|
19414
|
-
}
|
|
19415
|
-
if (cidr.address instanceof IPv44) {
|
|
19416
|
-
ranges4.push([cidr.address, cidr.bits]);
|
|
19417
|
-
} else {
|
|
19418
|
-
ranges6.push([cidr.address, cidr.bits]);
|
|
19419
|
-
}
|
|
19420
|
-
}
|
|
19421
|
-
const isIn4 = (ip) => ranges4.some(([network, bits]) => ip.match(network, bits));
|
|
19422
|
-
const isIn6 = (ip) => ranges6.some(([network, bits]) => ip.match(network, bits));
|
|
19423
|
-
return (address) => {
|
|
19424
|
-
const ip = parseAddress(address);
|
|
19425
|
-
if (ip === null) {
|
|
19426
|
-
return false;
|
|
19427
|
-
}
|
|
19428
|
-
if (ip instanceof IPv44) {
|
|
19429
|
-
return isIn4(ip);
|
|
19430
|
-
}
|
|
19431
|
-
return isIn6(ip) || ip.isIPv4MappedAddress() && isIn4(ip.toIPv4Address());
|
|
19432
|
-
};
|
|
19433
|
-
}
|
|
19434
|
-
|
|
19435
|
-
// packages/daemon/src/identity/auth/parse-tailnet-rules.ts
|
|
19436
|
-
import * as z48 from "zod";
|
|
19437
19001
|
// packages/api/src/schemas/lease-schema.ts
|
|
19438
19002
|
import * as z2 from "zod";
|
|
19439
19003
|
|
|
@@ -21266,6 +20830,443 @@ function hasScope(granted, needed) {
|
|
|
21266
20830
|
const neededRank = ScopeSchema.options.indexOf(needed);
|
|
21267
20831
|
return grantedRank !== -1 && neededRank !== -1 && grantedRank >= neededRank;
|
|
21268
20832
|
}
|
|
20833
|
+
// packages/daemon/src/config.ts
|
|
20834
|
+
import * as z53 from "zod";
|
|
20835
|
+
// packages/shared/src/format-bytes.ts
|
|
20836
|
+
var UNITS = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
20837
|
+
function formatBytes(bytes) {
|
|
20838
|
+
let value = bytes;
|
|
20839
|
+
let unit = 0;
|
|
20840
|
+
while (value >= 1024 && unit < UNITS.length - 1) {
|
|
20841
|
+
value /= 1024;
|
|
20842
|
+
unit += 1;
|
|
20843
|
+
}
|
|
20844
|
+
const digits = unit === 0 || value >= 100 ? 0 : 1;
|
|
20845
|
+
return `${value.toFixed(digits)} ${UNITS[unit] ?? "B"}`;
|
|
20846
|
+
}
|
|
20847
|
+
// packages/shared/src/get-error-message.ts
|
|
20848
|
+
function getErrorMessage(error) {
|
|
20849
|
+
return error instanceof Error ? error.message : String(error);
|
|
20850
|
+
}
|
|
20851
|
+
// packages/shared/src/merge-bytes.ts
|
|
20852
|
+
function mergeBytes(chunks) {
|
|
20853
|
+
const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
|
|
20854
|
+
let offset = 0;
|
|
20855
|
+
for (const chunk of chunks) {
|
|
20856
|
+
merged.set(chunk, offset);
|
|
20857
|
+
offset += chunk.byteLength;
|
|
20858
|
+
}
|
|
20859
|
+
return merged;
|
|
20860
|
+
}
|
|
20861
|
+
// packages/shared/src/to-plain-octets.ts
|
|
20862
|
+
var DOTTED_ENTRY = /^(?<octets>\d{1,3}(?:\.\d{1,3}){3})(?<prefix>\/\d{1,2})?$/u;
|
|
20863
|
+
function toPlainOctets(entry) {
|
|
20864
|
+
const groups = DOTTED_ENTRY.exec(entry)?.groups;
|
|
20865
|
+
if (groups === undefined) {
|
|
20866
|
+
return entry;
|
|
20867
|
+
}
|
|
20868
|
+
const octets = (groups["octets"] ?? "").split(".").map((octet) => String(Number(octet)));
|
|
20869
|
+
return `${octets.join(".")}${groups["prefix"] ?? ""}`;
|
|
20870
|
+
}
|
|
20871
|
+
// packages/daemon/src/host-net/net/addressing.ts
|
|
20872
|
+
import { IPv4 as IPv43 } from "ipaddr.js";
|
|
20873
|
+
|
|
20874
|
+
// packages/daemon/src/host-net/net/addressing6.ts
|
|
20875
|
+
import { IPv6 as IPv62 } from "ipaddr.js";
|
|
20876
|
+
|
|
20877
|
+
// packages/daemon/src/host-net/net/parse-address.ts
|
|
20878
|
+
import { IPv4, IPv6 } from "ipaddr.js";
|
|
20879
|
+
function parseAddress(text) {
|
|
20880
|
+
if (IPv4.isValidFourPartDecimal(text)) {
|
|
20881
|
+
return IPv4.parse(text);
|
|
20882
|
+
}
|
|
20883
|
+
if (text.includes("%")) {
|
|
20884
|
+
return null;
|
|
20885
|
+
}
|
|
20886
|
+
const hex = toHexGroups(text);
|
|
20887
|
+
return hex !== null && IPv6.isValid(hex) ? IPv6.parse(hex) : null;
|
|
20888
|
+
}
|
|
20889
|
+
function toHexGroups(text) {
|
|
20890
|
+
const split = text.lastIndexOf(":");
|
|
20891
|
+
const tail = text.slice(split + 1);
|
|
20892
|
+
if (!tail.includes(".")) {
|
|
20893
|
+
return text;
|
|
20894
|
+
}
|
|
20895
|
+
if (!IPv4.isValidFourPartDecimal(tail)) {
|
|
20896
|
+
return null;
|
|
20897
|
+
}
|
|
20898
|
+
const [first = 0, second = 0, third = 0, fourth = 0] = IPv4.parse(tail).octets;
|
|
20899
|
+
return `${text.slice(0, split + 1)}${toGroup(first, second)}:${toGroup(third, fourth)}`;
|
|
20900
|
+
}
|
|
20901
|
+
function toGroup(high, low) {
|
|
20902
|
+
return (high * 256 + low).toString(16);
|
|
20903
|
+
}
|
|
20904
|
+
|
|
20905
|
+
// packages/daemon/src/host-net/net/parse-cidr.ts
|
|
20906
|
+
import { IPv4 as IPv42 } from "ipaddr.js";
|
|
20907
|
+
function parseCidr(text) {
|
|
20908
|
+
const [addressText = "", prefix, ...rest] = text.split("/");
|
|
20909
|
+
const address = parseAddress(addressText);
|
|
20910
|
+
if (address === null || rest.length > 0 || prefix !== undefined && !/^\d{1,3}$/v.test(prefix)) {
|
|
20911
|
+
return null;
|
|
20912
|
+
}
|
|
20913
|
+
const maxBits = address instanceof IPv42 ? 32 : 128;
|
|
20914
|
+
const bits = prefix === undefined ? maxBits : Number(prefix);
|
|
20915
|
+
return bits > maxBits ? null : { address, bits };
|
|
20916
|
+
}
|
|
20917
|
+
|
|
20918
|
+
// packages/daemon/src/host-net/net/addressing6.ts
|
|
20919
|
+
var GROUPS = 8;
|
|
20920
|
+
var GROUP_BITS = 16n;
|
|
20921
|
+
var PREFIX_LENGTH = 64;
|
|
20922
|
+
var GATEWAY_IP6 = "fe80::1";
|
|
20923
|
+
function parseIpv6(text) {
|
|
20924
|
+
const address = parseAddress(text);
|
|
20925
|
+
return address instanceof IPv62 ? toBigint(address) : null;
|
|
20926
|
+
}
|
|
20927
|
+
function formatIpv6(address) {
|
|
20928
|
+
const groups = Array.from({ length: GROUPS }, (_, index) => Number(address >> GROUP_BITS * BigInt(GROUPS - 1 - index) & 0xffffn));
|
|
20929
|
+
return new IPv62(groups).toRFC5952String();
|
|
20930
|
+
}
|
|
20931
|
+
function formatCidr6(text) {
|
|
20932
|
+
const parsed = parseCidr(text);
|
|
20933
|
+
if (parsed === null || !(parsed.address instanceof IPv62)) {
|
|
20934
|
+
return null;
|
|
20935
|
+
}
|
|
20936
|
+
const hostBits = BigInt(128 - parsed.bits);
|
|
20937
|
+
const network = toBigint(parsed.address) >> hostBits << hostBits;
|
|
20938
|
+
return `${formatIpv6(network)}/${String(parsed.bits)}`;
|
|
20939
|
+
}
|
|
20940
|
+
function parsePrefix64(cidr) {
|
|
20941
|
+
const parsed = parseCidr(cidr);
|
|
20942
|
+
const network = parsed?.address instanceof IPv62 ? toBigint(parsed.address) : null;
|
|
20943
|
+
if (network === null || !cidr.endsWith(`/${String(PREFIX_LENGTH)}`) || network % 2n ** 64n !== 0n) {
|
|
20944
|
+
return null;
|
|
20945
|
+
}
|
|
20946
|
+
return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
|
|
20947
|
+
}
|
|
20948
|
+
function buildUlaPrefix(random) {
|
|
20949
|
+
if (random.length < 5) {
|
|
20950
|
+
throw new Error("a ULA global ID needs 5 random bytes");
|
|
20951
|
+
}
|
|
20952
|
+
const globalId = [...random.subarray(0, 5)].reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n);
|
|
20953
|
+
const network = (0xfdn << 40n) + globalId << 80n;
|
|
20954
|
+
return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
|
|
20955
|
+
}
|
|
20956
|
+
function findMappedIpv4(address) {
|
|
20957
|
+
const parsed = parseAddress(address);
|
|
20958
|
+
return parsed instanceof IPv62 && parsed.isIPv4MappedAddress() ? parsed.toIPv4Address().toString() : null;
|
|
20959
|
+
}
|
|
20960
|
+
function deriveGuestIp6(prefix, guestIpv4) {
|
|
20961
|
+
return formatIpv6(prefix.network + BigInt(guestIpv4));
|
|
20962
|
+
}
|
|
20963
|
+
function toBigint(address) {
|
|
20964
|
+
return address.parts.reduce((acc, group) => (acc << GROUP_BITS) + BigInt(group), 0n);
|
|
20965
|
+
}
|
|
20966
|
+
|
|
20967
|
+
// packages/daemon/src/host-net/net/addressing.ts
|
|
20968
|
+
var SLOT_SIZE = 4;
|
|
20969
|
+
var SLOT_PREFIX_LENGTH = 30;
|
|
20970
|
+
function parseSubnet(cidr) {
|
|
20971
|
+
const parsed = cidr.includes("/") ? parseCidr(cidr) : null;
|
|
20972
|
+
if (parsed === null || !(parsed.address instanceof IPv43)) {
|
|
20973
|
+
throw new Error(`subnet is not an IPv4 CIDR: ${cidr}`);
|
|
20974
|
+
}
|
|
20975
|
+
if (parsed.bits < 8 || parsed.bits > SLOT_PREFIX_LENGTH) {
|
|
20976
|
+
throw new Error(`subnet prefix must be between /8 and /30: ${cidr}`);
|
|
20977
|
+
}
|
|
20978
|
+
const network = toNumber(parsed.address);
|
|
20979
|
+
if (network % 2 ** (32 - parsed.bits) !== 0) {
|
|
20980
|
+
throw new Error(`subnet has host bits set: ${cidr}`);
|
|
20981
|
+
}
|
|
20982
|
+
return { network, prefixLength: parsed.bits };
|
|
20983
|
+
}
|
|
20984
|
+
var TAILNET_NETWORK = 100 * 2 ** 24 + 64 * 2 ** 16;
|
|
20985
|
+
var TAILNET_SIZE = 2 ** 22;
|
|
20986
|
+
function isTailnetOverlap(subnet) {
|
|
20987
|
+
const start = subnet.network;
|
|
20988
|
+
const end = start + 2 ** (32 - subnet.prefixLength);
|
|
20989
|
+
return start < TAILNET_NETWORK + TAILNET_SIZE && TAILNET_NETWORK < end;
|
|
20990
|
+
}
|
|
20991
|
+
function countSlots(subnet) {
|
|
20992
|
+
return 2 ** (32 - subnet.prefixLength) / SLOT_SIZE;
|
|
20993
|
+
}
|
|
20994
|
+
function deriveSlotAddress(slot, plan) {
|
|
20995
|
+
if (!Number.isInteger(slot) || slot < 0 || slot >= countSlots(plan.subnet)) {
|
|
20996
|
+
throw new RangeError(`slot ${String(slot)} is outside the subnet`);
|
|
20997
|
+
}
|
|
20998
|
+
const base = plan.subnet.network + slot * SLOT_SIZE;
|
|
20999
|
+
const guest = base + 2;
|
|
21000
|
+
return {
|
|
21001
|
+
slot,
|
|
21002
|
+
tap: `imp${String(slot)}`,
|
|
21003
|
+
hostIp: formatIpv4(base + 1),
|
|
21004
|
+
guestIp: formatIpv4(guest),
|
|
21005
|
+
prefixLength: SLOT_PREFIX_LENGTH,
|
|
21006
|
+
netmask: "255.255.255.252",
|
|
21007
|
+
guestMac: ["06", "00", ...splitOctets(guest).map((octet) => toHexByte(octet))].join(":"),
|
|
21008
|
+
hostMac: ["06", "01", ...splitOctets(base + 1).map((octet) => toHexByte(octet))].join(":"),
|
|
21009
|
+
guestIp6: plan.prefix6 === undefined || plan.prefix6 === null ? null : deriveGuestIp6(plan.prefix6, guest),
|
|
21010
|
+
tailnetPort: plan.portBase + slot
|
|
21011
|
+
};
|
|
21012
|
+
}
|
|
21013
|
+
function parseIpv4(text, allowMapped = false) {
|
|
21014
|
+
const address = parseAddress(text);
|
|
21015
|
+
if (address instanceof IPv43) {
|
|
21016
|
+
return toNumber(address);
|
|
21017
|
+
}
|
|
21018
|
+
return allowMapped && address?.isIPv4MappedAddress() === true ? toNumber(address.toIPv4Address()) : null;
|
|
21019
|
+
}
|
|
21020
|
+
function findPeerSlot(peer, local, subnet) {
|
|
21021
|
+
const peerIp = parseIpv4(peer, true);
|
|
21022
|
+
const localIp = parseIpv4(local, true);
|
|
21023
|
+
if (peerIp === null || localIp === null) {
|
|
21024
|
+
return null;
|
|
21025
|
+
}
|
|
21026
|
+
const offset = peerIp - subnet.network;
|
|
21027
|
+
if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
|
|
21028
|
+
return null;
|
|
21029
|
+
}
|
|
21030
|
+
return localIp === peerIp - 1 ? (offset - 2) / SLOT_SIZE : null;
|
|
21031
|
+
}
|
|
21032
|
+
function findGuestSlot(address, subnet) {
|
|
21033
|
+
const ip = parseIpv4(address, true);
|
|
21034
|
+
const offset = ip === null ? -1 : ip - subnet.network;
|
|
21035
|
+
if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
|
|
21036
|
+
return null;
|
|
21037
|
+
}
|
|
21038
|
+
return (offset - 2) / SLOT_SIZE;
|
|
21039
|
+
}
|
|
21040
|
+
function formatSubnet(subnet) {
|
|
21041
|
+
return `${formatIpv4(subnet.network)}/${String(subnet.prefixLength)}`;
|
|
21042
|
+
}
|
|
21043
|
+
function formatCidr4(text) {
|
|
21044
|
+
const parsed = parseCidr(toPlainOctets(text));
|
|
21045
|
+
if (parsed === null || !(parsed.address instanceof IPv43)) {
|
|
21046
|
+
return null;
|
|
21047
|
+
}
|
|
21048
|
+
const ip = toNumber(parsed.address);
|
|
21049
|
+
return formatSubnet({ network: ip - ip % 2 ** (32 - parsed.bits), prefixLength: parsed.bits });
|
|
21050
|
+
}
|
|
21051
|
+
function toNumber(address) {
|
|
21052
|
+
return address.octets.reduce((acc, octet) => acc * 256 + octet, 0);
|
|
21053
|
+
}
|
|
21054
|
+
function formatIpv4(address) {
|
|
21055
|
+
return splitOctets(address).join(".");
|
|
21056
|
+
}
|
|
21057
|
+
function splitOctets(address) {
|
|
21058
|
+
return [
|
|
21059
|
+
Math.floor(address / 2 ** 24) % 256,
|
|
21060
|
+
Math.floor(address / 2 ** 16) % 256,
|
|
21061
|
+
Math.floor(address / 2 ** 8) % 256,
|
|
21062
|
+
address % 256
|
|
21063
|
+
];
|
|
21064
|
+
}
|
|
21065
|
+
function toHexByte(octet) {
|
|
21066
|
+
return octet.toString(16).padStart(2, "0");
|
|
21067
|
+
}
|
|
21068
|
+
|
|
21069
|
+
// packages/daemon/src/host-net/net/ipv6-plan.ts
|
|
21070
|
+
import { randomBytes } from "crypto";
|
|
21071
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
21072
|
+
import { dirname, join as join2 } from "path";
|
|
21073
|
+
|
|
21074
|
+
// packages/daemon/src/platform/process/get-impd-logger.ts
|
|
21075
|
+
import { getLogger } from "@logtape/logtape";
|
|
21076
|
+
function getImpdLogger(area) {
|
|
21077
|
+
return getLogger(["impd", area]);
|
|
21078
|
+
}
|
|
21079
|
+
|
|
21080
|
+
// packages/daemon/src/platform/process/run-command.ts
|
|
21081
|
+
async function runCommand(argv, options = {}) {
|
|
21082
|
+
const child = Bun.spawn([...argv], {
|
|
21083
|
+
stdin: "ignore",
|
|
21084
|
+
stdout: "pipe",
|
|
21085
|
+
stderr: "pipe",
|
|
21086
|
+
env: options.env === undefined ? process.env : { ...options.env },
|
|
21087
|
+
...options.signal !== undefined && { signal: options.signal }
|
|
21088
|
+
});
|
|
21089
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
21090
|
+
new Response(child.stdout).text(),
|
|
21091
|
+
new Response(child.stderr).text(),
|
|
21092
|
+
child.exited
|
|
21093
|
+
]);
|
|
21094
|
+
return { exitCode, stdout, stderr };
|
|
21095
|
+
}
|
|
21096
|
+
async function runChecked(argv, options = {}, run = runCommand) {
|
|
21097
|
+
const result = await run(argv, options);
|
|
21098
|
+
if (result.exitCode !== 0) {
|
|
21099
|
+
throw buildExitError(argv, result);
|
|
21100
|
+
}
|
|
21101
|
+
return result.stdout;
|
|
21102
|
+
}
|
|
21103
|
+
function buildExitError(argv, result) {
|
|
21104
|
+
const output = result.stderr.trim() || (result.stdout ?? "").trim();
|
|
21105
|
+
return new Error(`${argv.join(" ")} exited ${String(result.exitCode)}: ${output}`);
|
|
21106
|
+
}
|
|
21107
|
+
|
|
21108
|
+
// packages/daemon/src/host-net/net/nat66-ruleset.ts
|
|
21109
|
+
var NAT66_TABLE = "ip6 imp_nat66";
|
|
21110
|
+
function buildNat66Ruleset(prefix, uplink) {
|
|
21111
|
+
return [
|
|
21112
|
+
`table ${NAT66_TABLE} {}`,
|
|
21113
|
+
`delete table ${NAT66_TABLE}`,
|
|
21114
|
+
`table ${NAT66_TABLE} {`,
|
|
21115
|
+
" chain postrouting {",
|
|
21116
|
+
" type nat hook postrouting priority srcnat; policy accept;",
|
|
21117
|
+
` oifname "${uplink}" ip6 saddr ${prefix} masquerade`,
|
|
21118
|
+
" }",
|
|
21119
|
+
"}",
|
|
21120
|
+
""
|
|
21121
|
+
].join(`
|
|
21122
|
+
`);
|
|
21123
|
+
}
|
|
21124
|
+
function buildNat66Removal() {
|
|
21125
|
+
return `table ${NAT66_TABLE} {}
|
|
21126
|
+
delete table ${NAT66_TABLE}
|
|
21127
|
+
`;
|
|
21128
|
+
}
|
|
21129
|
+
|
|
21130
|
+
// packages/daemon/src/host-net/net/ipv6-plan.ts
|
|
21131
|
+
function parseIpv6Setting(text) {
|
|
21132
|
+
if (text === "auto" || text === "off") {
|
|
21133
|
+
return { kind: text };
|
|
21134
|
+
}
|
|
21135
|
+
const prefix = parsePrefix64(text);
|
|
21136
|
+
if (prefix === null) {
|
|
21137
|
+
throw new Error(`IMP_SUBNET6 must be auto, off or an IPv6 /64, got ${text}`);
|
|
21138
|
+
}
|
|
21139
|
+
return { kind: "routed", prefix };
|
|
21140
|
+
}
|
|
21141
|
+
async function resolveIpv6Plan(setting, deps) {
|
|
21142
|
+
const stopIpv6 = async (reason) => {
|
|
21143
|
+
await removeNat66(deps);
|
|
21144
|
+
getImpdLogger("ipv6").info("off ({reason})", { reason });
|
|
21145
|
+
return null;
|
|
21146
|
+
};
|
|
21147
|
+
if (setting.kind === "off") {
|
|
21148
|
+
return stopIpv6("IMP_SUBNET6=off");
|
|
21149
|
+
}
|
|
21150
|
+
const uplink = await deps.readDefaultRoute();
|
|
21151
|
+
if (setting.kind === "auto" && uplink === null) {
|
|
21152
|
+
return stopIpv6("IMP_SUBNET6=auto, and the container has no IPv6 default route");
|
|
21153
|
+
}
|
|
21154
|
+
const missing = await deps.checkHostRules();
|
|
21155
|
+
if (missing !== null) {
|
|
21156
|
+
return stopIpv6(`the host's IPv6 rules are not in place: ${missing}`);
|
|
21157
|
+
}
|
|
21158
|
+
if (setting.kind === "routed") {
|
|
21159
|
+
await removeNat66(deps);
|
|
21160
|
+
getImpdLogger("ipv6").info("{text}, routed{route}", {
|
|
21161
|
+
text: setting.prefix.text,
|
|
21162
|
+
route: uplink === null ? "; the container has no IPv6 default route" : ` via ${uplink}`
|
|
21163
|
+
});
|
|
21164
|
+
return { prefix: setting.prefix, nat66: false, uplink };
|
|
21165
|
+
}
|
|
21166
|
+
const prefix = deps.readUlaPrefix();
|
|
21167
|
+
try {
|
|
21168
|
+
await deps.runNft(buildNat66Ruleset(prefix.text, uplink ?? ""));
|
|
21169
|
+
} catch (error) {
|
|
21170
|
+
return stopIpv6(`NAT66: ${getErrorMessage(error)}`);
|
|
21171
|
+
}
|
|
21172
|
+
getImpdLogger("ipv6").info("{text}, NAT66 out of {uplink}", {
|
|
21173
|
+
text: prefix.text,
|
|
21174
|
+
uplink: uplink ?? ""
|
|
21175
|
+
});
|
|
21176
|
+
return { prefix, nat66: true, uplink };
|
|
21177
|
+
}
|
|
21178
|
+
async function removeNat66(deps) {
|
|
21179
|
+
try {
|
|
21180
|
+
await deps.runNft(buildNat66Removal());
|
|
21181
|
+
} catch {}
|
|
21182
|
+
}
|
|
21183
|
+
var HOST_RULES6 = [
|
|
21184
|
+
["filter", "-A INPUT -i imp+ -j DROP"],
|
|
21185
|
+
["filter", "-A FORWARD -i imp+ -o imp+ -j DROP"],
|
|
21186
|
+
["filter", "-A FORWARD -o imp+ -j DROP"],
|
|
21187
|
+
["filter", "-A FORWARD -i imp+ -j DROP"],
|
|
21188
|
+
["raw", "-A PREROUTING -i imp+ -m rpfilter --invert -j DROP"]
|
|
21189
|
+
];
|
|
21190
|
+
var HOST_SYSCTLS6 = [
|
|
21191
|
+
["default/accept_ra", "0"],
|
|
21192
|
+
["default/accept_redirects", "0"],
|
|
21193
|
+
["all/forwarding", "1"]
|
|
21194
|
+
];
|
|
21195
|
+
async function checkHostRules6(procSys = "/proc/sys/net/ipv6/conf", run = runCommand) {
|
|
21196
|
+
for (const table of ["filter", "raw"]) {
|
|
21197
|
+
const result = await run(["ip6tables", "-w", "-t", table, "-S"]);
|
|
21198
|
+
const rules = new Set(result.stdout.split(`
|
|
21199
|
+
`).map((line) => line.trim()));
|
|
21200
|
+
const missing = HOST_RULES6.find(([inTable, rule]) => inTable === table && !rules.has(rule));
|
|
21201
|
+
if (result.exitCode !== 0) {
|
|
21202
|
+
return `ip6tables -t ${table}: ${result.stderr.trim()}`;
|
|
21203
|
+
}
|
|
21204
|
+
if (missing !== undefined) {
|
|
21205
|
+
return `no ip6tables -t ${table} ${missing[1]}`;
|
|
21206
|
+
}
|
|
21207
|
+
}
|
|
21208
|
+
for (const [key, wanted] of HOST_SYSCTLS6) {
|
|
21209
|
+
const value = readSysctl(join2(procSys, key));
|
|
21210
|
+
if (value !== wanted) {
|
|
21211
|
+
return `net.ipv6.conf.${key.replace("/", ".")} is ${value ?? "missing"}, not ${wanted}`;
|
|
21212
|
+
}
|
|
21213
|
+
}
|
|
21214
|
+
return null;
|
|
21215
|
+
}
|
|
21216
|
+
function readSysctl(path) {
|
|
21217
|
+
try {
|
|
21218
|
+
return readFileSync2(path, "utf8").trim();
|
|
21219
|
+
} catch {
|
|
21220
|
+
return null;
|
|
21221
|
+
}
|
|
21222
|
+
}
|
|
21223
|
+
function readOrCreateUlaPrefix(path) {
|
|
21224
|
+
if (existsSync2(path)) {
|
|
21225
|
+
const prefix = parsePrefix64(readFileSync2(path, "utf8").trim());
|
|
21226
|
+
if (prefix !== null) {
|
|
21227
|
+
return prefix;
|
|
21228
|
+
}
|
|
21229
|
+
}
|
|
21230
|
+
const prefix = buildUlaPrefix(randomBytes(5));
|
|
21231
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
21232
|
+
writeFileSync(path, `${prefix.text}
|
|
21233
|
+
`);
|
|
21234
|
+
return prefix;
|
|
21235
|
+
}
|
|
21236
|
+
|
|
21237
|
+
// packages/daemon/src/host-net/net/range-matcher.ts
|
|
21238
|
+
import { IPv4 as IPv44 } from "ipaddr.js";
|
|
21239
|
+
function buildRangeMatcher(ranges) {
|
|
21240
|
+
const ranges4 = [];
|
|
21241
|
+
const ranges6 = [];
|
|
21242
|
+
for (const range of ranges) {
|
|
21243
|
+
const cidr = parseCidr(range);
|
|
21244
|
+
if (cidr === null) {
|
|
21245
|
+
throw new Error(`not an address range: ${range}`);
|
|
21246
|
+
}
|
|
21247
|
+
if (cidr.address instanceof IPv44) {
|
|
21248
|
+
ranges4.push([cidr.address, cidr.bits]);
|
|
21249
|
+
} else {
|
|
21250
|
+
ranges6.push([cidr.address, cidr.bits]);
|
|
21251
|
+
}
|
|
21252
|
+
}
|
|
21253
|
+
const isIn4 = (ip) => ranges4.some(([network, bits]) => ip.match(network, bits));
|
|
21254
|
+
const isIn6 = (ip) => ranges6.some(([network, bits]) => ip.match(network, bits));
|
|
21255
|
+
return (address) => {
|
|
21256
|
+
const ip = parseAddress(address);
|
|
21257
|
+
if (ip === null) {
|
|
21258
|
+
return false;
|
|
21259
|
+
}
|
|
21260
|
+
if (ip instanceof IPv44) {
|
|
21261
|
+
return isIn4(ip);
|
|
21262
|
+
}
|
|
21263
|
+
return isIn6(ip) || ip.isIPv4MappedAddress() && isIn4(ip.toIPv4Address());
|
|
21264
|
+
};
|
|
21265
|
+
}
|
|
21266
|
+
|
|
21267
|
+
// packages/daemon/src/identity/auth/parse-tailnet-rules.ts
|
|
21268
|
+
import * as z48 from "zod";
|
|
21269
|
+
|
|
21269
21270
|
// packages/daemon/src/identity/auth/tailnet-identity.ts
|
|
21270
21271
|
import * as z47 from "zod";
|
|
21271
21272
|
|
|
@@ -21703,6 +21704,8 @@ var EnvSchema = z53.object({
|
|
|
21703
21704
|
IMP_EGRESS_DENY: z53.string().default(""),
|
|
21704
21705
|
IMP_HOST_ADDRESSES: z53.string().default(""),
|
|
21705
21706
|
IMP_RAM_BUDGET_MIB: CountSchema3.default(16384),
|
|
21707
|
+
IMP_PRIORITY_RAM_MIB: CountSchema3.optional(),
|
|
21708
|
+
IMP_PRIORITY_IMPS: z53.string().transform(splitList).pipe(z53.array(ImpPatternSchema)).optional(),
|
|
21706
21709
|
IMP_IDLE_TIMEOUT_S: CountSchema3.default(60),
|
|
21707
21710
|
IMP_IDLE_CPU_PERCENT: z53.coerce.number().nonnegative().default(10),
|
|
21708
21711
|
IMP_MEMORY_SHRINK_AFTER_S: CountSchema3.default(60),
|
|
@@ -21867,6 +21870,7 @@ function loadConfig(env) {
|
|
|
21867
21870
|
hostAddresses,
|
|
21868
21871
|
brokerTestUpstreams: parsed.IMP_BROKER_TEST_UPSTREAMS ?? null,
|
|
21869
21872
|
ramBudgetMib: parsed.IMP_RAM_BUDGET_MIB,
|
|
21873
|
+
priorityRam: parsePriorityRam(parsed),
|
|
21870
21874
|
idleTimeoutS: parsed.IMP_IDLE_TIMEOUT_S,
|
|
21871
21875
|
idleCpuPercent: parsed.IMP_IDLE_CPU_PERCENT,
|
|
21872
21876
|
memoryShrinkAfterMs: parsed.IMP_MEMORY_SHRINK_AFTER_S * 1000,
|
|
@@ -21923,6 +21927,20 @@ function loadConfig(env) {
|
|
|
21923
21927
|
warnings: listHttpsWarnings(parsed)
|
|
21924
21928
|
};
|
|
21925
21929
|
}
|
|
21930
|
+
function parsePriorityRam(parsed) {
|
|
21931
|
+
const mib = parsed.IMP_PRIORITY_RAM_MIB;
|
|
21932
|
+
const patterns = parsed.IMP_PRIORITY_IMPS;
|
|
21933
|
+
if (mib === undefined && patterns === undefined) {
|
|
21934
|
+
return null;
|
|
21935
|
+
}
|
|
21936
|
+
if (mib === undefined || patterns === undefined) {
|
|
21937
|
+
throw new Error("IMP_PRIORITY_RAM_MIB and IMP_PRIORITY_IMPS go together; set both or neither");
|
|
21938
|
+
}
|
|
21939
|
+
if (mib >= parsed.IMP_RAM_BUDGET_MIB) {
|
|
21940
|
+
throw new Error(`IMP_PRIORITY_RAM_MIB ${String(mib)} leaves no room for other imps in the ${String(parsed.IMP_RAM_BUDGET_MIB)} MiB IMP_RAM_BUDGET_MIB`);
|
|
21941
|
+
}
|
|
21942
|
+
return { mib, patterns };
|
|
21943
|
+
}
|
|
21926
21944
|
function parseTestRegistry(parsed) {
|
|
21927
21945
|
const registry = parsed.IMP_BUILD_TEST_REGISTRY;
|
|
21928
21946
|
const caPath = parsed.IMP_BUILD_TEST_REGISTRY_CA;
|
|
@@ -21972,7 +21990,7 @@ import { join as join48 } from "path";
|
|
|
21972
21990
|
// packages/daemon/package.json
|
|
21973
21991
|
var package_default = {
|
|
21974
21992
|
name: "@impz/daemon",
|
|
21975
|
-
version: "6.
|
|
21993
|
+
version: "6.7.1",
|
|
21976
21994
|
private: true,
|
|
21977
21995
|
description: "impd: the imp host daemon",
|
|
21978
21996
|
type: "module",
|
|
@@ -21980,13 +21998,9 @@ var package_default = {
|
|
|
21980
21998
|
".": "./src/index.ts",
|
|
21981
21999
|
"./read-text": "./src/platform/process/read-text.ts",
|
|
21982
22000
|
"./real-clock": "./src/platform/process/real-clock.ts",
|
|
21983
|
-
"./
|
|
21984
|
-
"./
|
|
21985
|
-
"./
|
|
21986
|
-
"./testing/pebble-images": "./testing/network/pebble-images.ts",
|
|
21987
|
-
"./testing/start-stub-agent": "./testing/vm/start-stub-agent.ts",
|
|
21988
|
-
"./testing/start-test-impd": "./testing/start-test-impd.ts",
|
|
21989
|
-
"./testing/test-token": "./testing/test-token.ts",
|
|
22001
|
+
"./test-utils": "./src/test-utils/index.ts",
|
|
22002
|
+
"./test-utils/data-layout": "./src/vm/storage/data-layout.ts",
|
|
22003
|
+
"./test-utils/*": "./src/test-utils/*.ts",
|
|
21990
22004
|
"./package.json": "./package.json"
|
|
21991
22005
|
},
|
|
21992
22006
|
dependencies: {
|
|
@@ -22029,7 +22043,8 @@ var package_default = {
|
|
|
22029
22043
|
},
|
|
22030
22044
|
devDependencies: {
|
|
22031
22045
|
"@faker-js/faker": "catalog:",
|
|
22032
|
-
"@impz/
|
|
22046
|
+
"@impz/cli": "workspace:*",
|
|
22047
|
+
"@impz/e2e-harness": "workspace:*",
|
|
22033
22048
|
"@modelcontextprotocol/client": "catalog:",
|
|
22034
22049
|
"@msw/data": "catalog:",
|
|
22035
22050
|
"@types/dns-packet": "catalog:",
|
|
@@ -25007,7 +25022,7 @@ class RamBudgetError extends ImpdError {
|
|
|
25007
25022
|
budgetMib: shortfall.budgetMib,
|
|
25008
25023
|
usedMib: shortfall.usedMib,
|
|
25009
25024
|
requestedMib: shortfall.requestedMib,
|
|
25010
|
-
neededMib: buildNeededMib(shortfall.usedMib, shortfall.requestedMib, shortfall.budgetMib),
|
|
25025
|
+
neededMib: buildNeededMib(shortfall.usedMib + (shortfall.kept?.mib ?? 0), shortfall.requestedMib, shortfall.budgetMib),
|
|
25011
25026
|
protected: [],
|
|
25012
25027
|
protectedHidden: shortfall.protected.length
|
|
25013
25028
|
}
|
|
@@ -25027,25 +25042,35 @@ function buildRamBudgetError(shortfall) {
|
|
|
25027
25042
|
const used = String(shortfall.usedMib);
|
|
25028
25043
|
const budget = String(shortfall.budgetMib);
|
|
25029
25044
|
const requested = String(shortfall.requestedMib);
|
|
25030
|
-
|
|
25045
|
+
const kept = formatKept(shortfall.kept);
|
|
25046
|
+
return new RamBudgetError(`not enough RAM: ${used} of ${budget} MiB in use${kept}, ${requested} MiB requested, and no idle imp left to sleep`, shortfall, "ram");
|
|
25031
25047
|
}
|
|
25032
25048
|
function buildOrdinaryBudgetError(shortfall) {
|
|
25033
25049
|
const used = String(shortfall.usedMib);
|
|
25034
25050
|
const budget = String(shortfall.budgetMib);
|
|
25035
25051
|
const requested = String(shortfall.requestedMib);
|
|
25036
|
-
|
|
25052
|
+
const kept = formatKept(shortfall.kept);
|
|
25053
|
+
return new RamBudgetError(`not enough RAM outside the huge page pool: ${used} of ${budget} MiB in use${kept}, ${requested} MiB requested on 4 KiB pages, and no idle imp left to sleep`, shortfall, "ordinary");
|
|
25037
25054
|
}
|
|
25038
25055
|
function buildHugePoolError(shortfall) {
|
|
25039
25056
|
const committed = String(shortfall.committedMib);
|
|
25040
25057
|
const total = String(shortfall.totalMib);
|
|
25041
25058
|
const requested = String(shortfall.requestedMib);
|
|
25042
|
-
|
|
25059
|
+
const kept = formatKept(shortfall.kept);
|
|
25060
|
+
return new RamBudgetError(`not enough 2 MiB pages: ${committed} of the host's ${total} MiB pool in use or promised to awake imps${kept}, ${requested} MiB requested`, {
|
|
25043
25061
|
budgetMib: shortfall.totalMib,
|
|
25044
25062
|
usedMib: shortfall.committedMib,
|
|
25045
25063
|
requestedMib: shortfall.requestedMib,
|
|
25046
|
-
protected: []
|
|
25064
|
+
protected: [],
|
|
25065
|
+
...shortfall.kept !== undefined && { kept: shortfall.kept }
|
|
25047
25066
|
}, "pool");
|
|
25048
25067
|
}
|
|
25068
|
+
function formatKept(kept) {
|
|
25069
|
+
if (kept === undefined || kept.mib === 0) {
|
|
25070
|
+
return "";
|
|
25071
|
+
}
|
|
25072
|
+
return ` and ${String(kept.mib)} MiB kept for priority imps (${kept.patterns.join(", ")})`;
|
|
25073
|
+
}
|
|
25049
25074
|
function buildNeededMib(usedMib, requestedMib, budgetMib) {
|
|
25050
25075
|
return Math.max(0, usedMib + requestedMib - budgetMib);
|
|
25051
25076
|
}
|
|
@@ -26317,7 +26342,11 @@ async function sendAgentRequest(vsockPath, request, timeoutMs = 5000) {
|
|
|
26317
26342
|
const connection = await openAgentConnection(vsockPath, timeoutMs);
|
|
26318
26343
|
try {
|
|
26319
26344
|
sendRequestFrame(connection, request);
|
|
26320
|
-
const frame = await readFrameWithin(connection,
|
|
26345
|
+
const frame = await readFrameWithin(connection, {
|
|
26346
|
+
call: `${request.op} response`,
|
|
26347
|
+
vsockPath,
|
|
26348
|
+
timeoutMs
|
|
26349
|
+
});
|
|
26321
26350
|
if (frame?.type !== FRAME_TYPES.response) {
|
|
26322
26351
|
throw new Error(`agent ${request.op}: no response`);
|
|
26323
26352
|
}
|
|
@@ -26379,8 +26408,8 @@ async function sendThaw(vsockPath) {
|
|
|
26379
26408
|
const response = await sendAgentRequest(vsockPath, { op: "thaw" });
|
|
26380
26409
|
decodeAgentMessage(OKSchema, response);
|
|
26381
26410
|
}
|
|
26382
|
-
function readFrameWithin(connection,
|
|
26383
|
-
return requireWithin(connection.next(), timeoutMs, () => new Error(`agent
|
|
26411
|
+
function readFrameWithin(connection, wait) {
|
|
26412
|
+
return requireWithin(connection.next(), wait.timeoutMs, () => new Error(`agent at ${wait.vsockPath} sent no ${wait.call} within ${String(wait.timeoutMs)} ms`));
|
|
26384
26413
|
}
|
|
26385
26414
|
|
|
26386
26415
|
// packages/daemon/src/imps/imps/consistent-disk.ts
|
|
@@ -33480,7 +33509,11 @@ async function openStream(vsockPath, request, startTimeoutMs) {
|
|
|
33480
33509
|
let output;
|
|
33481
33510
|
try {
|
|
33482
33511
|
sendRequestFrame(connection, request);
|
|
33483
|
-
const first = await readFrameWithin(connection,
|
|
33512
|
+
const first = await readFrameWithin(connection, {
|
|
33513
|
+
call: `${request.op} start`,
|
|
33514
|
+
vsockPath,
|
|
33515
|
+
timeoutMs: startTimeoutMs
|
|
33516
|
+
});
|
|
33484
33517
|
if (first === null) {
|
|
33485
33518
|
throw new Error("agent closed the exec connection before the process started");
|
|
33486
33519
|
}
|
|
@@ -37693,6 +37726,61 @@ function buildBudgets(budgetMib, pool) {
|
|
|
37693
37726
|
};
|
|
37694
37727
|
}
|
|
37695
37728
|
|
|
37729
|
+
// packages/daemon/src/imp-ops/governor/build-usage-totals.ts
|
|
37730
|
+
function buildUsageTotals(input) {
|
|
37731
|
+
const reserved = new Map(input.reservations.map((reservation) => [reservation.id, reservation]));
|
|
37732
|
+
const byImp = new Map;
|
|
37733
|
+
let usedMib = input.lockedMib;
|
|
37734
|
+
let effectiveMib = input.lockedMib + input.headroomMib;
|
|
37735
|
+
let ordinaryUsedMib = input.lockedMib;
|
|
37736
|
+
let ordinaryEffectiveMib = input.lockedMib + input.headroomMib;
|
|
37737
|
+
const priority = { ramMib: 0, ordinaryMib: 0, poolMib: 0 };
|
|
37738
|
+
for (const [index, imp] of input.awake.entries()) {
|
|
37739
|
+
const reading = input.readings[index] ?? null;
|
|
37740
|
+
const measured = reading?.ramMib ?? 0;
|
|
37741
|
+
const effective = Math.max(measured, reserved.get(imp.id)?.mib ?? 0);
|
|
37742
|
+
const ordinary = imp.hugePagesMib > 0 ? Math.max(0, measured - (reading?.hugetlbMib ?? 0)) : effective;
|
|
37743
|
+
byImp.set(imp.id, effective);
|
|
37744
|
+
usedMib += measured;
|
|
37745
|
+
effectiveMib += effective;
|
|
37746
|
+
ordinaryUsedMib += imp.hugePagesMib > 0 ? ordinary : measured;
|
|
37747
|
+
ordinaryEffectiveMib += ordinary;
|
|
37748
|
+
if (imp.isPriority) {
|
|
37749
|
+
priority.ramMib += effective;
|
|
37750
|
+
priority.ordinaryMib += ordinary;
|
|
37751
|
+
priority.poolMib += imp.hugePagesMib;
|
|
37752
|
+
}
|
|
37753
|
+
}
|
|
37754
|
+
for (const reservation of input.reservations) {
|
|
37755
|
+
if (!byImp.has(reservation.id)) {
|
|
37756
|
+
const ordinary = reservation.isOnPool ? 0 : reservation.mib;
|
|
37757
|
+
effectiveMib += reservation.mib;
|
|
37758
|
+
ordinaryEffectiveMib += ordinary;
|
|
37759
|
+
if (reservation.isPriority) {
|
|
37760
|
+
priority.ramMib += reservation.mib;
|
|
37761
|
+
priority.ordinaryMib += ordinary;
|
|
37762
|
+
}
|
|
37763
|
+
}
|
|
37764
|
+
}
|
|
37765
|
+
for (const pending of input.pendingPool) {
|
|
37766
|
+
if (pending.isPriority && !byImp.has(pending.id)) {
|
|
37767
|
+
priority.poolMib += pending.mib;
|
|
37768
|
+
}
|
|
37769
|
+
}
|
|
37770
|
+
return {
|
|
37771
|
+
byImp,
|
|
37772
|
+
usedMib,
|
|
37773
|
+
effectiveMib,
|
|
37774
|
+
ordinaryUsedMib,
|
|
37775
|
+
ordinaryEffectiveMib,
|
|
37776
|
+
kept: {
|
|
37777
|
+
ramMib: Math.max(0, input.priorityMib - priority.ramMib),
|
|
37778
|
+
ordinaryMib: Math.max(0, input.priorityMib - priority.ordinaryMib),
|
|
37779
|
+
poolMib: Math.max(0, input.priorityMib - priority.poolMib)
|
|
37780
|
+
}
|
|
37781
|
+
};
|
|
37782
|
+
}
|
|
37783
|
+
|
|
37696
37784
|
// packages/daemon/src/imp-ops/governor/build-huge-pool-admission.ts
|
|
37697
37785
|
function buildHugePoolAdmission(input) {
|
|
37698
37786
|
const growthMib = input.awake.reduce((sum, guest) => sum + Math.max(0, guest.hugePagesMib - guest.heldMib), 0);
|
|
@@ -37700,21 +37788,22 @@ function buildHugePoolAdmission(input) {
|
|
|
37700
37788
|
const totalMib = input.pool?.totalMib ?? 0;
|
|
37701
37789
|
const freeMib = input.pool?.freeMib ?? 0;
|
|
37702
37790
|
const promisedMib = growthMib + pendingMib;
|
|
37703
|
-
|
|
37791
|
+
const wantedMib = promisedMib + input.keptMib + input.requestedMib;
|
|
37792
|
+
if (wantedMib <= freeMib) {
|
|
37704
37793
|
return { admitted: true };
|
|
37705
37794
|
}
|
|
37706
37795
|
return {
|
|
37707
37796
|
admitted: false,
|
|
37708
37797
|
totalMib,
|
|
37709
37798
|
committedMib: totalMib - freeMib + promisedMib,
|
|
37710
|
-
neededMib:
|
|
37799
|
+
neededMib: wantedMib - freeMib
|
|
37711
37800
|
};
|
|
37712
37801
|
}
|
|
37713
37802
|
|
|
37714
37803
|
// packages/daemon/src/imp-ops/governor/make-check-huge-pool.ts
|
|
37715
37804
|
function makeCheckHugePool(deps) {
|
|
37716
37805
|
const pendingHugePages = deps.pendingHugePages;
|
|
37717
|
-
return async (request, hugePagesMib) => {
|
|
37806
|
+
return async (request, hugePagesMib, kept) => {
|
|
37718
37807
|
const listed = await deps.listAwake();
|
|
37719
37808
|
const awake = listed.filter((imp) => imp.id !== request.id);
|
|
37720
37809
|
for (const imp of awake) {
|
|
@@ -37730,7 +37819,8 @@ function makeCheckHugePool(deps) {
|
|
|
37730
37819
|
heldMib: held[index] ?? 0
|
|
37731
37820
|
})),
|
|
37732
37821
|
pendingMib: [...pendingHugePages].filter(([id]) => id !== request.id).map(([, mib]) => mib),
|
|
37733
|
-
requestedMib: hugePagesMib
|
|
37822
|
+
requestedMib: hugePagesMib,
|
|
37823
|
+
keptMib: kept?.mib ?? 0
|
|
37734
37824
|
});
|
|
37735
37825
|
if (poolAdmission.admitted) {
|
|
37736
37826
|
return;
|
|
@@ -37746,7 +37836,8 @@ function makeCheckHugePool(deps) {
|
|
|
37746
37836
|
throw buildHugePoolError({
|
|
37747
37837
|
totalMib: poolAdmission.totalMib,
|
|
37748
37838
|
committedMib: poolAdmission.committedMib,
|
|
37749
|
-
requestedMib: hugePagesMib
|
|
37839
|
+
requestedMib: hugePagesMib,
|
|
37840
|
+
...kept !== null && { kept }
|
|
37750
37841
|
});
|
|
37751
37842
|
};
|
|
37752
37843
|
}
|
|
@@ -37771,6 +37862,17 @@ function pickShortfall(used, budgets, requestedMib) {
|
|
|
37771
37862
|
} : ram;
|
|
37772
37863
|
}
|
|
37773
37864
|
|
|
37865
|
+
// packages/daemon/src/imp-ops/governor/pick-effective-shortfall.ts
|
|
37866
|
+
function pickEffectiveShortfall(usage, budgets, requestedMib, isPriority) {
|
|
37867
|
+
const kept = isPriority ? { ramMib: 0, ordinaryMib: 0 } : usage.kept;
|
|
37868
|
+
const shortfall = pickShortfall({
|
|
37869
|
+
ramMib: usage.effectiveMib + kept.ramMib,
|
|
37870
|
+
ordinaryMib: usage.ordinaryEffectiveMib + kept.ordinaryMib
|
|
37871
|
+
}, budgets, requestedMib);
|
|
37872
|
+
const keptMib = shortfall.budget === "ordinary" ? kept.ordinaryMib : kept.ramMib;
|
|
37873
|
+
return { ...shortfall, usedMib: shortfall.usedMib - keptMib, keptMib };
|
|
37874
|
+
}
|
|
37875
|
+
|
|
37774
37876
|
// packages/daemon/src/imp-ops/governor/pick-sleep-victims.ts
|
|
37775
37877
|
function pickSleepVictims(candidates, needMib) {
|
|
37776
37878
|
if (needMib <= 0) {
|
|
@@ -37798,8 +37900,12 @@ function createRamGovernor(deps) {
|
|
|
37798
37900
|
const admission = createPrioritySemaphore(1);
|
|
37799
37901
|
const reservations = new Map;
|
|
37800
37902
|
const now = deps.now ?? Date.now;
|
|
37903
|
+
const priority = deps.priority ?? null;
|
|
37904
|
+
const isPriority = (name) => priority !== null && name !== null && isImpAllowed(priority.patterns, name);
|
|
37905
|
+
const buildKept = (mib) => priority === null || mib === 0 ? null : { mib, patterns: priority.patterns };
|
|
37801
37906
|
let stuckOver = false;
|
|
37802
37907
|
const pendingHugePages = new Map;
|
|
37908
|
+
const admittedPriority = new Set;
|
|
37803
37909
|
const emitDecision = (decision) => {
|
|
37804
37910
|
deps.events?.publish({
|
|
37805
37911
|
v: EVENT_VERSION,
|
|
@@ -37830,6 +37936,11 @@ function createRamGovernor(deps) {
|
|
|
37830
37936
|
if ((request.hugePagesMib ?? 0) > 0) {
|
|
37831
37937
|
pendingHugePages.set(request.id, request.hugePagesMib ?? 0);
|
|
37832
37938
|
}
|
|
37939
|
+
if (isPriority(request.name)) {
|
|
37940
|
+
admittedPriority.add(request.id);
|
|
37941
|
+
} else {
|
|
37942
|
+
admittedPriority.delete(request.id);
|
|
37943
|
+
}
|
|
37833
37944
|
emitAdmission(request, "admitted", usedMib);
|
|
37834
37945
|
};
|
|
37835
37946
|
const readReservation = (id) => {
|
|
@@ -37860,45 +37971,27 @@ function createRamGovernor(deps) {
|
|
|
37860
37971
|
const readEffectiveUsage = async (excludeId, readVm = deps.readVmRam) => {
|
|
37861
37972
|
const listed = await deps.listAwake();
|
|
37862
37973
|
const awake = listed.filter((imp) => imp.id !== excludeId);
|
|
37863
|
-
const byImp = new Map;
|
|
37864
37974
|
const lockedMib = deps.readLockedMib?.() ?? 0;
|
|
37865
|
-
let usedMib = lockedMib;
|
|
37866
|
-
let effectiveMib = lockedMib;
|
|
37867
|
-
let ordinaryUsedMib = lockedMib;
|
|
37868
|
-
let ordinaryEffectiveMib = lockedMib;
|
|
37869
37975
|
const readings = await Promise.all(awake.map((imp) => readVm(imp.pid, imp.apiSocket)));
|
|
37870
|
-
|
|
37871
|
-
|
|
37872
|
-
|
|
37873
|
-
|
|
37874
|
-
|
|
37875
|
-
|
|
37876
|
-
|
|
37877
|
-
effectiveMib += effective;
|
|
37878
|
-
ordinaryUsedMib += imp.hugePagesMib > 0 ? ordinary : measured;
|
|
37879
|
-
ordinaryEffectiveMib += ordinary;
|
|
37880
|
-
}
|
|
37881
|
-
for (const id of reservations.keys()) {
|
|
37882
|
-
if (id !== excludeId && !byImp.has(id)) {
|
|
37883
|
-
effectiveMib += readReservation(id);
|
|
37884
|
-
if (!pendingHugePages.has(id)) {
|
|
37885
|
-
ordinaryEffectiveMib += readReservation(id);
|
|
37886
|
-
}
|
|
37887
|
-
}
|
|
37888
|
-
}
|
|
37976
|
+
const live = [...reservations.keys()].filter((id) => id !== excludeId).map((id) => ({
|
|
37977
|
+
id,
|
|
37978
|
+
mib: readReservation(id),
|
|
37979
|
+
isPriority: admittedPriority.has(id),
|
|
37980
|
+
isOnPool: pendingHugePages.has(id)
|
|
37981
|
+
}));
|
|
37982
|
+
const pendingPool = [...pendingHugePages].filter(([id]) => id !== excludeId).map(([id, mib]) => ({ id, mib, isPriority: admittedPriority.has(id) }));
|
|
37889
37983
|
const headroom = deps.readHeadroomMib?.(awake.map((imp) => imp.pid)) ?? Promise.resolve(0);
|
|
37890
37984
|
const headroomMib = await headroom;
|
|
37891
|
-
|
|
37892
|
-
|
|
37893
|
-
|
|
37894
|
-
|
|
37895
|
-
|
|
37896
|
-
|
|
37897
|
-
|
|
37898
|
-
|
|
37899
|
-
|
|
37900
|
-
|
|
37901
|
-
};
|
|
37985
|
+
const totals = buildUsageTotals({
|
|
37986
|
+
awake: awake.map((imp) => ({ ...imp, isPriority: isPriority(imp.name) })),
|
|
37987
|
+
readings,
|
|
37988
|
+
reservations: live,
|
|
37989
|
+
pendingPool,
|
|
37990
|
+
lockedMib,
|
|
37991
|
+
headroomMib,
|
|
37992
|
+
priorityMib: priority?.mib ?? 0
|
|
37993
|
+
});
|
|
37994
|
+
return { awake, headroomMib, ...totals };
|
|
37902
37995
|
};
|
|
37903
37996
|
const readFreeRoom = async (excludeId, findShortfall) => {
|
|
37904
37997
|
const usage = await readEffectiveUsage(excludeId);
|
|
@@ -37926,7 +38019,7 @@ function createRamGovernor(deps) {
|
|
|
37926
38019
|
const shortfall = options.findShortfall(usage);
|
|
37927
38020
|
const missingMib = shortfall.missingMib;
|
|
37928
38021
|
const isOrdinary = shortfall.budget === "ordinary";
|
|
37929
|
-
const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0);
|
|
38022
|
+
const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0).filter((imp) => !options.sparesPriority || !isPriority(imp.name));
|
|
37930
38023
|
if (missingMib <= 0) {
|
|
37931
38024
|
return {
|
|
37932
38025
|
fits: true,
|
|
@@ -38011,20 +38104,23 @@ function createRamGovernor(deps) {
|
|
|
38011
38104
|
requestedMib: request.memoryMib
|
|
38012
38105
|
});
|
|
38013
38106
|
}
|
|
38107
|
+
const bound = await readEffectiveUsage(request.id, deps.readResidentRam);
|
|
38108
|
+
const isPriorityRequest = isPriority(request.name);
|
|
38014
38109
|
if (hugePagesMib > 0) {
|
|
38015
|
-
|
|
38110
|
+
const kept = isPriorityRequest ? null : buildKept(bound.kept.poolMib);
|
|
38111
|
+
await checkHugePool(request, hugePagesMib, kept);
|
|
38016
38112
|
}
|
|
38017
|
-
const
|
|
38018
|
-
if (
|
|
38113
|
+
const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib, isPriorityRequest);
|
|
38114
|
+
if (findReserveShortfall(bound).missingMib <= 0) {
|
|
38019
38115
|
recordAdmission(request, bound.effectiveMib);
|
|
38020
38116
|
return;
|
|
38021
38117
|
}
|
|
38022
|
-
const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib);
|
|
38023
38118
|
const room = request.maySleepImps === false ? await readFreeRoom(request.id, findReserveShortfall) : await makeRoom({
|
|
38024
38119
|
excludeId: request.id,
|
|
38025
38120
|
reason: `to make room for ${request.name ?? request.id}`,
|
|
38026
38121
|
whenShort: "giveUp",
|
|
38027
|
-
findShortfall: findReserveShortfall
|
|
38122
|
+
findShortfall: findReserveShortfall,
|
|
38123
|
+
sparesPriority: !isPriorityRequest
|
|
38028
38124
|
});
|
|
38029
38125
|
const diskFull = room.diskFull ? deps.readDiskFullError?.() ?? null : null;
|
|
38030
38126
|
if (!room.fits && diskFull !== null) {
|
|
@@ -38034,8 +38130,9 @@ function createRamGovernor(deps) {
|
|
|
38034
38130
|
const usage = await readEffectiveUsage(request.id);
|
|
38035
38131
|
const shortfall = findReserveShortfall(usage);
|
|
38036
38132
|
const isOrdinary = shortfall.budget === "ordinary";
|
|
38133
|
+
const kept = buildKept(shortfall.keptMib);
|
|
38037
38134
|
emitAdmission(request, "refused", shortfall.usedMib, {
|
|
38038
|
-
neededMib:
|
|
38135
|
+
neededMib: Math.max(0, shortfall.missingMib),
|
|
38039
38136
|
protectedCount: room.protected.length,
|
|
38040
38137
|
...isOrdinary && { trigger: ORDINARY_TRIGGER }
|
|
38041
38138
|
});
|
|
@@ -38043,7 +38140,8 @@ function createRamGovernor(deps) {
|
|
|
38043
38140
|
budgetMib: shortfall.budgetMib,
|
|
38044
38141
|
usedMib: shortfall.usedMib,
|
|
38045
38142
|
requestedMib: request.reserveMib,
|
|
38046
|
-
protected: room.protected
|
|
38143
|
+
protected: room.protected,
|
|
38144
|
+
...kept !== null && { kept }
|
|
38047
38145
|
};
|
|
38048
38146
|
throw isOrdinary ? buildOrdinaryBudgetError(refusal) : buildRamBudgetError(refusal);
|
|
38049
38147
|
}
|
|
@@ -38052,6 +38150,7 @@ function createRamGovernor(deps) {
|
|
|
38052
38150
|
release: (id) => {
|
|
38053
38151
|
reservations.delete(id);
|
|
38054
38152
|
pendingHugePages.delete(id);
|
|
38153
|
+
admittedPriority.delete(id);
|
|
38055
38154
|
},
|
|
38056
38155
|
admitGrow: (request) => admission.run("background", async () => {
|
|
38057
38156
|
const listed = await deps.listAwake();
|
|
@@ -38059,15 +38158,17 @@ function createRamGovernor(deps) {
|
|
|
38059
38158
|
return false;
|
|
38060
38159
|
}
|
|
38061
38160
|
const budgets = readBudgets();
|
|
38161
|
+
const findGrowShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.mib, isPriority(request.name));
|
|
38062
38162
|
const room = await makeRoom({
|
|
38063
38163
|
excludeId: null,
|
|
38064
38164
|
reason: `to grow ${request.name}`,
|
|
38065
38165
|
whenShort: "giveUp",
|
|
38066
|
-
findShortfall:
|
|
38067
|
-
protectedId: request.id
|
|
38166
|
+
findShortfall: findGrowShortfall,
|
|
38167
|
+
protectedId: request.id,
|
|
38168
|
+
sparesPriority: !isPriority(request.name)
|
|
38068
38169
|
});
|
|
38069
38170
|
const usage = await readEffectiveUsage(null);
|
|
38070
|
-
const shortfall =
|
|
38171
|
+
const shortfall = findGrowShortfall(usage);
|
|
38071
38172
|
const fits = room.fits && usage.awake.some((imp) => imp.id === request.id);
|
|
38072
38173
|
emitDecision({
|
|
38073
38174
|
decision: fits ? "admitted" : "refused",
|
|
@@ -38116,7 +38217,8 @@ function createRamGovernor(deps) {
|
|
|
38116
38217
|
excludeId: null,
|
|
38117
38218
|
reason: ENFORCE_TRIGGER,
|
|
38118
38219
|
whenShort: "sleepAll",
|
|
38119
|
-
findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0)
|
|
38220
|
+
findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0),
|
|
38221
|
+
sparesPriority: false
|
|
38120
38222
|
});
|
|
38121
38223
|
const over = `over budget by ${String(room.missingMib)} MiB`;
|
|
38122
38224
|
if (room.fits) {
|
|
@@ -38134,9 +38236,6 @@ function createRamGovernor(deps) {
|
|
|
38134
38236
|
})
|
|
38135
38237
|
};
|
|
38136
38238
|
}
|
|
38137
|
-
function pickEffectiveShortfall(usage, budgets, requestedMib) {
|
|
38138
|
-
return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
|
|
38139
|
-
}
|
|
38140
38239
|
function toTaskPriority(request) {
|
|
38141
38240
|
return request.isBackground === true ? "background" : "foreground";
|
|
38142
38241
|
}
|
|
@@ -41333,7 +41432,11 @@ async function openRelayStream(vsockPath, request, feature, answerTimeoutMs) {
|
|
|
41333
41432
|
const connection = await openAgentConnection(vsockPath);
|
|
41334
41433
|
try {
|
|
41335
41434
|
sendRequestFrame(connection, request);
|
|
41336
|
-
const answer = await readFrameWithin(connection,
|
|
41435
|
+
const answer = await readFrameWithin(connection, {
|
|
41436
|
+
call: `${request.op} answer`,
|
|
41437
|
+
vsockPath,
|
|
41438
|
+
timeoutMs: answerTimeoutMs
|
|
41439
|
+
});
|
|
41337
41440
|
if (answer?.type !== FRAME_TYPES.response) {
|
|
41338
41441
|
throw new Error(`agent closed the ${request.op} connection before it answered`);
|
|
41339
41442
|
}
|
|
@@ -41379,12 +41482,16 @@ function buildListenRequest(spec) {
|
|
|
41379
41482
|
}
|
|
41380
41483
|
return { op: "listen", network: "tcp", address: `127.0.0.1:${String(spec.port)}` };
|
|
41381
41484
|
}
|
|
41382
|
-
async function openListener(vsockPath, spec) {
|
|
41485
|
+
async function openListener(vsockPath, spec, answerTimeoutMs = ANSWER_TIMEOUT_MS) {
|
|
41383
41486
|
const feature = spec.network === "ssh-agent" ? "agent-forwarding" : "reverse-forward";
|
|
41384
41487
|
const connection = await openAgentConnection(vsockPath);
|
|
41385
41488
|
try {
|
|
41386
41489
|
sendRequestFrame(connection, buildListenRequest(spec));
|
|
41387
|
-
const answer = await readFrameWithin(connection,
|
|
41490
|
+
const answer = await readFrameWithin(connection, {
|
|
41491
|
+
call: "listener answer",
|
|
41492
|
+
vsockPath,
|
|
41493
|
+
timeoutMs: answerTimeoutMs
|
|
41494
|
+
});
|
|
41388
41495
|
if (answer?.type !== FRAME_TYPES.response) {
|
|
41389
41496
|
throw new Error("agent closed the listen connection before it answered");
|
|
41390
41497
|
}
|
|
@@ -42286,7 +42393,7 @@ async function sendServicesRestart(vsockPath, service) {
|
|
|
42286
42393
|
const response = await sendAgentRequest(vsockPath, { op: "services.restart", service }, STOP_TIMEOUT_MS).catch(handleUnknownOp("services"));
|
|
42287
42394
|
decodeAgentMessage(OKSchema, response);
|
|
42288
42395
|
}
|
|
42289
|
-
async function openServiceLogStream(vsockPath, request) {
|
|
42396
|
+
async function openServiceLogStream(vsockPath, request, startTimeoutMs = LOGS_START_TIMEOUT_MS) {
|
|
42290
42397
|
const connection = await openAgentConnection(vsockPath);
|
|
42291
42398
|
try {
|
|
42292
42399
|
sendRequestFrame(connection, {
|
|
@@ -42296,7 +42403,11 @@ async function openServiceLogStream(vsockPath, request) {
|
|
|
42296
42403
|
follow: request.follow,
|
|
42297
42404
|
...request.cursor !== undefined && { cursor: request.cursor }
|
|
42298
42405
|
});
|
|
42299
|
-
const first = await readFrameWithin(connection,
|
|
42406
|
+
const first = await readFrameWithin(connection, {
|
|
42407
|
+
call: "services.logs start",
|
|
42408
|
+
vsockPath,
|
|
42409
|
+
timeoutMs: startTimeoutMs
|
|
42410
|
+
});
|
|
42300
42411
|
if (first === null) {
|
|
42301
42412
|
throw new Error("agent closed the log connection before it answered");
|
|
42302
42413
|
}
|
|
@@ -45622,6 +45733,7 @@ function createImpdImps(config, deps, parts) {
|
|
|
45622
45733
|
});
|
|
45623
45734
|
const governor = createRamGovernor({
|
|
45624
45735
|
budgetMib: config.ramBudgetMib,
|
|
45736
|
+
priority: config.priorityRam,
|
|
45625
45737
|
listAwake: async () => {
|
|
45626
45738
|
const listed = await listImps(deps.db);
|
|
45627
45739
|
return listed.flatMap((imp) => {
|
|
@@ -58516,12 +58628,12 @@ async function setupDefaultImage(config, images) {
|
|
|
58516
58628
|
}
|
|
58517
58629
|
}
|
|
58518
58630
|
|
|
58519
|
-
// packages/daemon/
|
|
58631
|
+
// packages/daemon/src/test-utils/network/read-no-tailscale.ts
|
|
58520
58632
|
function readNoTailscale() {
|
|
58521
58633
|
return Promise.resolve({ state: null, hostname: null, dnsName: null, ip: null, ips: [] });
|
|
58522
58634
|
}
|
|
58523
58635
|
|
|
58524
|
-
// packages/daemon/
|
|
58636
|
+
// packages/daemon/src/test-utils/platform/build-stub-ticker-timer.ts
|
|
58525
58637
|
function buildStubTickerTimer() {
|
|
58526
58638
|
const pending = new Map;
|
|
58527
58639
|
const holdRun = (label, run, durationMs) => {
|
|
@@ -58547,12 +58659,12 @@ function buildStubTickerTimer() {
|
|
|
58547
58659
|
};
|
|
58548
58660
|
}
|
|
58549
58661
|
|
|
58550
|
-
// packages/daemon/
|
|
58662
|
+
// packages/daemon/src/test-utils/platform/format-run-prefix.ts
|
|
58551
58663
|
function formatRunPrefix(owner) {
|
|
58552
58664
|
return `pid${String(owner.pid)}-ns${owner.namespace}-`;
|
|
58553
58665
|
}
|
|
58554
58666
|
|
|
58555
|
-
// packages/daemon/
|
|
58667
|
+
// packages/daemon/src/test-utils/platform/open-migrated-copy.ts
|
|
58556
58668
|
import { Database as Database4 } from "bun:sqlite";
|
|
58557
58669
|
var TEMPLATE = {};
|
|
58558
58670
|
function openMigratedCopy() {
|
|
@@ -58567,14 +58679,14 @@ async function serializeMigratedDatabase() {
|
|
|
58567
58679
|
return bytes;
|
|
58568
58680
|
}
|
|
58569
58681
|
|
|
58570
|
-
// packages/daemon/
|
|
58682
|
+
// packages/daemon/src/test-utils/platform/pick-impd-port.ts
|
|
58571
58683
|
var FIRST_PORT = 10100;
|
|
58572
58684
|
var PORT_SPAN = 9900;
|
|
58573
58685
|
function pickImpdPort(draw) {
|
|
58574
58686
|
return FIRST_PORT + Math.floor(draw * PORT_SPAN);
|
|
58575
58687
|
}
|
|
58576
58688
|
|
|
58577
|
-
// packages/daemon/
|
|
58689
|
+
// packages/daemon/src/test-utils/platform/read-run-owner.ts
|
|
58578
58690
|
import { readlinkSync as readlinkSync2 } from "fs";
|
|
58579
58691
|
function readRunOwner() {
|
|
58580
58692
|
return {
|
|
@@ -58583,7 +58695,7 @@ function readRunOwner() {
|
|
|
58583
58695
|
};
|
|
58584
58696
|
}
|
|
58585
58697
|
|
|
58586
|
-
// packages/daemon/
|
|
58698
|
+
// packages/daemon/src/test-utils/platform/start-impd-log-capture.ts
|
|
58587
58699
|
import { resetSync } from "@logtape/logtape";
|
|
58588
58700
|
|
|
58589
58701
|
// packages/daemon/src/platform/process/setup-logging.ts
|
|
@@ -58616,7 +58728,7 @@ function renderLogLine(record) {
|
|
|
58616
58728
|
`;
|
|
58617
58729
|
}
|
|
58618
58730
|
|
|
58619
|
-
// packages/daemon/
|
|
58731
|
+
// packages/daemon/src/test-utils/platform/start-impd-log-capture.ts
|
|
58620
58732
|
var listeners = new Set;
|
|
58621
58733
|
function startImpdLogCapture(onLine) {
|
|
58622
58734
|
if (listeners.size === 0) {
|
|
@@ -58634,10 +58746,10 @@ function startImpdLogCapture(onLine) {
|
|
|
58634
58746
|
};
|
|
58635
58747
|
}
|
|
58636
58748
|
|
|
58637
|
-
// packages/daemon/
|
|
58749
|
+
// packages/daemon/src/test-utils/test-token.ts
|
|
58638
58750
|
var TEST_TOKEN = "test-token";
|
|
58639
58751
|
|
|
58640
|
-
// packages/daemon/
|
|
58752
|
+
// packages/daemon/src/test-utils/vm/build-stub-vmm.ts
|
|
58641
58753
|
import { existsSync as existsSync33, mkdirSync as mkdirSync30, renameSync as renameSync8, writeFileSync as writeFileSync15 } from "fs";
|
|
58642
58754
|
var STUB_AGENT_VERSION = "0.1.0";
|
|
58643
58755
|
var IMPD_OWNER = { uid: process.getuid?.() ?? 0, cgroup: null };
|
|
@@ -59041,7 +59153,7 @@ log tail`);
|
|
|
59041
59153
|
};
|
|
59042
59154
|
}
|
|
59043
59155
|
|
|
59044
|
-
// packages/daemon/
|
|
59156
|
+
// packages/daemon/src/test-utils/start-test-impd.ts
|
|
59045
59157
|
var FAKE_VM_RAM_MIB = 300;
|
|
59046
59158
|
var FAKE_VM_RSS_MIB = 340;
|
|
59047
59159
|
var TEST_DRIVE = "d1".repeat(32);
|
|
@@ -59384,7 +59496,7 @@ function buildTestIdentity(dataDir, drive) {
|
|
|
59384
59496
|
import { randomBytes as randomBytes12 } from "crypto";
|
|
59385
59497
|
import { Duplex } from "stream";
|
|
59386
59498
|
|
|
59387
|
-
// packages/daemon/
|
|
59499
|
+
// packages/daemon/src/test-utils/vm/start-stub-agent.ts
|
|
59388
59500
|
import { onTestFinished as onTestFinished2 } from "bun:test";
|
|
59389
59501
|
import { mkdirSync as mkdirSync32 } from "fs";
|
|
59390
59502
|
import { createServer as createServer3 } from "net";
|