@impz/testing 6.7.0 → 6.8.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/dist/index.js +813 -714
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -18998,517 +18998,82 @@ import invariant7 from "tiny-invariant";
|
|
|
18998
18998
|
|
|
18999
18999
|
// packages/daemon/src/config.ts
|
|
19000
19000
|
import { join as join4 } from "path";
|
|
19001
|
-
|
|
19002
|
-
|
|
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/get-error-message.ts
|
|
19015
|
-
function getErrorMessage(error) {
|
|
19016
|
-
return error instanceof Error ? error.message : String(error);
|
|
19017
|
-
}
|
|
19018
|
-
// packages/shared/src/merge-bytes.ts
|
|
19019
|
-
function mergeBytes(chunks) {
|
|
19020
|
-
const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
|
|
19021
|
-
let offset = 0;
|
|
19022
|
-
for (const chunk of chunks) {
|
|
19023
|
-
merged.set(chunk, offset);
|
|
19024
|
-
offset += chunk.byteLength;
|
|
19025
|
-
}
|
|
19026
|
-
return merged;
|
|
19027
|
-
}
|
|
19028
|
-
// packages/shared/src/to-plain-octets.ts
|
|
19029
|
-
var DOTTED_ENTRY = /^(?<octets>\d{1,3}(?:\.\d{1,3}){3})(?<prefix>\/\d{1,2})?$/u;
|
|
19030
|
-
function toPlainOctets(entry) {
|
|
19031
|
-
const groups = DOTTED_ENTRY.exec(entry)?.groups;
|
|
19032
|
-
if (groups === undefined) {
|
|
19033
|
-
return entry;
|
|
19034
|
-
}
|
|
19035
|
-
const octets = (groups["octets"] ?? "").split(".").map((octet) => String(Number(octet)));
|
|
19036
|
-
return `${octets.join(".")}${groups["prefix"] ?? ""}`;
|
|
19037
|
-
}
|
|
19038
|
-
// packages/daemon/src/host-net/net/addressing.ts
|
|
19039
|
-
import { IPv4 as IPv43 } from "ipaddr.js";
|
|
19001
|
+
// packages/api/src/schemas/lease-schema.ts
|
|
19002
|
+
import * as z2 from "zod";
|
|
19040
19003
|
|
|
19041
|
-
// packages/
|
|
19042
|
-
import
|
|
19004
|
+
// packages/api/src/schemas/name-schema.ts
|
|
19005
|
+
import * as z from "zod";
|
|
19006
|
+
var NameSchema = z.string().regex(/^[a-z][a-z0-9-]{0,30}$/u, "must be a lowercase letter followed by up to 30 lowercase letters, digits or hyphens");
|
|
19043
19007
|
|
|
19044
|
-
// packages/
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
|
|
19053
|
-
|
|
19054
|
-
|
|
19055
|
-
|
|
19056
|
-
|
|
19057
|
-
|
|
19058
|
-
|
|
19059
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19067
|
-
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
}
|
|
19008
|
+
// packages/api/src/schemas/lease-schema.ts
|
|
19009
|
+
var LeaseLabelSchema = z2.string().regex(/^[\w.:-]{1,64}$/u, "must be 1\u201364 letters, digits, dots, underscores, colons or hyphens");
|
|
19010
|
+
var LeaseTtlSchema = z2.int().min(10).max(3600);
|
|
19011
|
+
var LeaseOwnerSchema = z2.object({
|
|
19012
|
+
principal: z2.string(),
|
|
19013
|
+
display: z2.string(),
|
|
19014
|
+
label: z2.string()
|
|
19015
|
+
});
|
|
19016
|
+
var LeaseSchema = z2.object({
|
|
19017
|
+
name: NameSchema,
|
|
19018
|
+
owner: LeaseOwnerSchema,
|
|
19019
|
+
until: z2.date().nullable()
|
|
19020
|
+
});
|
|
19021
|
+
var LeaseSummarySchema = z2.object({
|
|
19022
|
+
leases: z2.array(LeaseSchema).readonly(),
|
|
19023
|
+
otherCount: z2.int().nonnegative()
|
|
19024
|
+
});
|
|
19025
|
+
// packages/api/src/protocols/build-context-protocol.ts
|
|
19026
|
+
import * as z3 from "zod";
|
|
19027
|
+
var BUILD_CONTEXT_PATH = "/images/build-context";
|
|
19028
|
+
var DockerfilePathSchema = z3.string().min(1).refine((path) => !path.startsWith("/") && !path.split(/[/\\]/u).includes(".."), {
|
|
19029
|
+
message: "the Dockerfile path must be relative and stay inside the build context"
|
|
19030
|
+
});
|
|
19031
|
+
var BuildContextQuerySchema = z3.object({
|
|
19032
|
+
name: NameSchema,
|
|
19033
|
+
dockerfile: DockerfilePathSchema.optional()
|
|
19034
|
+
});
|
|
19035
|
+
var BuildContextUploadSchema = z3.object({
|
|
19036
|
+
id: z3.string().min(1),
|
|
19037
|
+
expiresAt: z3.coerce.date()
|
|
19038
|
+
});
|
|
19039
|
+
var BuildContextErrorSchema = z3.object({ code: z3.string(), message: z3.string() });
|
|
19040
|
+
// packages/api/src/schemas/image-op-event-schema.ts
|
|
19041
|
+
import * as z5 from "zod";
|
|
19071
19042
|
|
|
19072
|
-
// packages/
|
|
19073
|
-
import
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19043
|
+
// packages/api/src/schemas/image-schema.ts
|
|
19044
|
+
import * as z4 from "zod";
|
|
19045
|
+
var ImageSourceSchema = z4.enum(["oci", "imp"]);
|
|
19046
|
+
var ImageSchema = z4.object({
|
|
19047
|
+
id: z4.string(),
|
|
19048
|
+
name: NameSchema,
|
|
19049
|
+
ref: z4.string(),
|
|
19050
|
+
digest: z4.string(),
|
|
19051
|
+
source: ImageSourceSchema,
|
|
19052
|
+
createdAt: z4.date(),
|
|
19053
|
+
sizeBytes: z4.int().nonnegative(),
|
|
19054
|
+
pinned: z4.boolean().default(false)
|
|
19055
|
+
});
|
|
19084
19056
|
|
|
19085
|
-
// packages/
|
|
19086
|
-
var
|
|
19087
|
-
var
|
|
19088
|
-
var
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
}
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
|
|
19097
|
-
}
|
|
19098
|
-
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
return `${formatIpv6(network)}/${String(parsed.bits)}`;
|
|
19106
|
-
}
|
|
19107
|
-
function parsePrefix64(cidr) {
|
|
19108
|
-
const parsed = parseCidr(cidr);
|
|
19109
|
-
const network = parsed?.address instanceof IPv62 ? toBigint(parsed.address) : null;
|
|
19110
|
-
if (network === null || !cidr.endsWith(`/${String(PREFIX_LENGTH)}`) || network % 2n ** 64n !== 0n) {
|
|
19111
|
-
return null;
|
|
19112
|
-
}
|
|
19113
|
-
return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
|
|
19114
|
-
}
|
|
19115
|
-
function buildUlaPrefix(random) {
|
|
19116
|
-
if (random.length < 5) {
|
|
19117
|
-
throw new Error("a ULA global ID needs 5 random bytes");
|
|
19118
|
-
}
|
|
19119
|
-
const globalId = [...random.subarray(0, 5)].reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n);
|
|
19120
|
-
const network = (0xfdn << 40n) + globalId << 80n;
|
|
19121
|
-
return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
|
|
19122
|
-
}
|
|
19123
|
-
function findMappedIpv4(address) {
|
|
19124
|
-
const parsed = parseAddress(address);
|
|
19125
|
-
return parsed instanceof IPv62 && parsed.isIPv4MappedAddress() ? parsed.toIPv4Address().toString() : null;
|
|
19126
|
-
}
|
|
19127
|
-
function deriveGuestIp6(prefix, guestIpv4) {
|
|
19128
|
-
return formatIpv6(prefix.network + BigInt(guestIpv4));
|
|
19129
|
-
}
|
|
19130
|
-
function toBigint(address) {
|
|
19131
|
-
return address.parts.reduce((acc, group) => (acc << GROUP_BITS) + BigInt(group), 0n);
|
|
19132
|
-
}
|
|
19133
|
-
|
|
19134
|
-
// packages/daemon/src/host-net/net/addressing.ts
|
|
19135
|
-
var SLOT_SIZE = 4;
|
|
19136
|
-
var SLOT_PREFIX_LENGTH = 30;
|
|
19137
|
-
function parseSubnet(cidr) {
|
|
19138
|
-
const parsed = cidr.includes("/") ? parseCidr(cidr) : null;
|
|
19139
|
-
if (parsed === null || !(parsed.address instanceof IPv43)) {
|
|
19140
|
-
throw new Error(`subnet is not an IPv4 CIDR: ${cidr}`);
|
|
19141
|
-
}
|
|
19142
|
-
if (parsed.bits < 8 || parsed.bits > SLOT_PREFIX_LENGTH) {
|
|
19143
|
-
throw new Error(`subnet prefix must be between /8 and /30: ${cidr}`);
|
|
19144
|
-
}
|
|
19145
|
-
const network = toNumber(parsed.address);
|
|
19146
|
-
if (network % 2 ** (32 - parsed.bits) !== 0) {
|
|
19147
|
-
throw new Error(`subnet has host bits set: ${cidr}`);
|
|
19148
|
-
}
|
|
19149
|
-
return { network, prefixLength: parsed.bits };
|
|
19150
|
-
}
|
|
19151
|
-
var TAILNET_NETWORK = 100 * 2 ** 24 + 64 * 2 ** 16;
|
|
19152
|
-
var TAILNET_SIZE = 2 ** 22;
|
|
19153
|
-
function isTailnetOverlap(subnet) {
|
|
19154
|
-
const start = subnet.network;
|
|
19155
|
-
const end = start + 2 ** (32 - subnet.prefixLength);
|
|
19156
|
-
return start < TAILNET_NETWORK + TAILNET_SIZE && TAILNET_NETWORK < end;
|
|
19157
|
-
}
|
|
19158
|
-
function countSlots(subnet) {
|
|
19159
|
-
return 2 ** (32 - subnet.prefixLength) / SLOT_SIZE;
|
|
19160
|
-
}
|
|
19161
|
-
function deriveSlotAddress(slot, plan) {
|
|
19162
|
-
if (!Number.isInteger(slot) || slot < 0 || slot >= countSlots(plan.subnet)) {
|
|
19163
|
-
throw new RangeError(`slot ${String(slot)} is outside the subnet`);
|
|
19164
|
-
}
|
|
19165
|
-
const base = plan.subnet.network + slot * SLOT_SIZE;
|
|
19166
|
-
const guest = base + 2;
|
|
19167
|
-
return {
|
|
19168
|
-
slot,
|
|
19169
|
-
tap: `imp${String(slot)}`,
|
|
19170
|
-
hostIp: formatIpv4(base + 1),
|
|
19171
|
-
guestIp: formatIpv4(guest),
|
|
19172
|
-
prefixLength: SLOT_PREFIX_LENGTH,
|
|
19173
|
-
netmask: "255.255.255.252",
|
|
19174
|
-
guestMac: ["06", "00", ...splitOctets(guest).map((octet) => toHexByte(octet))].join(":"),
|
|
19175
|
-
hostMac: ["06", "01", ...splitOctets(base + 1).map((octet) => toHexByte(octet))].join(":"),
|
|
19176
|
-
guestIp6: plan.prefix6 === undefined || plan.prefix6 === null ? null : deriveGuestIp6(plan.prefix6, guest),
|
|
19177
|
-
tailnetPort: plan.portBase + slot
|
|
19178
|
-
};
|
|
19179
|
-
}
|
|
19180
|
-
function parseIpv4(text, allowMapped = false) {
|
|
19181
|
-
const address = parseAddress(text);
|
|
19182
|
-
if (address instanceof IPv43) {
|
|
19183
|
-
return toNumber(address);
|
|
19184
|
-
}
|
|
19185
|
-
return allowMapped && address?.isIPv4MappedAddress() === true ? toNumber(address.toIPv4Address()) : null;
|
|
19186
|
-
}
|
|
19187
|
-
function findPeerSlot(peer, local, subnet) {
|
|
19188
|
-
const peerIp = parseIpv4(peer, true);
|
|
19189
|
-
const localIp = parseIpv4(local, true);
|
|
19190
|
-
if (peerIp === null || localIp === null) {
|
|
19191
|
-
return null;
|
|
19192
|
-
}
|
|
19193
|
-
const offset = peerIp - subnet.network;
|
|
19194
|
-
if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
|
|
19195
|
-
return null;
|
|
19196
|
-
}
|
|
19197
|
-
return localIp === peerIp - 1 ? (offset - 2) / SLOT_SIZE : null;
|
|
19198
|
-
}
|
|
19199
|
-
function findGuestSlot(address, subnet) {
|
|
19200
|
-
const ip = parseIpv4(address, true);
|
|
19201
|
-
const offset = ip === null ? -1 : ip - subnet.network;
|
|
19202
|
-
if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
|
|
19203
|
-
return null;
|
|
19204
|
-
}
|
|
19205
|
-
return (offset - 2) / SLOT_SIZE;
|
|
19206
|
-
}
|
|
19207
|
-
function formatSubnet(subnet) {
|
|
19208
|
-
return `${formatIpv4(subnet.network)}/${String(subnet.prefixLength)}`;
|
|
19209
|
-
}
|
|
19210
|
-
function formatCidr4(text) {
|
|
19211
|
-
const parsed = parseCidr(toPlainOctets(text));
|
|
19212
|
-
if (parsed === null || !(parsed.address instanceof IPv43)) {
|
|
19213
|
-
return null;
|
|
19214
|
-
}
|
|
19215
|
-
const ip = toNumber(parsed.address);
|
|
19216
|
-
return formatSubnet({ network: ip - ip % 2 ** (32 - parsed.bits), prefixLength: parsed.bits });
|
|
19217
|
-
}
|
|
19218
|
-
function toNumber(address) {
|
|
19219
|
-
return address.octets.reduce((acc, octet) => acc * 256 + octet, 0);
|
|
19220
|
-
}
|
|
19221
|
-
function formatIpv4(address) {
|
|
19222
|
-
return splitOctets(address).join(".");
|
|
19223
|
-
}
|
|
19224
|
-
function splitOctets(address) {
|
|
19225
|
-
return [
|
|
19226
|
-
Math.floor(address / 2 ** 24) % 256,
|
|
19227
|
-
Math.floor(address / 2 ** 16) % 256,
|
|
19228
|
-
Math.floor(address / 2 ** 8) % 256,
|
|
19229
|
-
address % 256
|
|
19230
|
-
];
|
|
19231
|
-
}
|
|
19232
|
-
function toHexByte(octet) {
|
|
19233
|
-
return octet.toString(16).padStart(2, "0");
|
|
19234
|
-
}
|
|
19235
|
-
|
|
19236
|
-
// packages/daemon/src/host-net/net/ipv6-plan.ts
|
|
19237
|
-
import { randomBytes } from "crypto";
|
|
19238
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
19239
|
-
import { dirname, join as join2 } from "path";
|
|
19240
|
-
|
|
19241
|
-
// packages/daemon/src/platform/process/get-impd-logger.ts
|
|
19242
|
-
import { getLogger } from "@logtape/logtape";
|
|
19243
|
-
function getImpdLogger(area) {
|
|
19244
|
-
return getLogger(["impd", area]);
|
|
19245
|
-
}
|
|
19246
|
-
|
|
19247
|
-
// packages/daemon/src/platform/process/run-command.ts
|
|
19248
|
-
async function runCommand(argv, options = {}) {
|
|
19249
|
-
const child = Bun.spawn([...argv], {
|
|
19250
|
-
stdin: "ignore",
|
|
19251
|
-
stdout: "pipe",
|
|
19252
|
-
stderr: "pipe",
|
|
19253
|
-
env: options.env === undefined ? process.env : { ...options.env },
|
|
19254
|
-
...options.signal !== undefined && { signal: options.signal }
|
|
19255
|
-
});
|
|
19256
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
19257
|
-
new Response(child.stdout).text(),
|
|
19258
|
-
new Response(child.stderr).text(),
|
|
19259
|
-
child.exited
|
|
19260
|
-
]);
|
|
19261
|
-
return { exitCode, stdout, stderr };
|
|
19262
|
-
}
|
|
19263
|
-
async function runChecked(argv, options = {}, run = runCommand) {
|
|
19264
|
-
const result = await run(argv, options);
|
|
19265
|
-
if (result.exitCode !== 0) {
|
|
19266
|
-
throw buildExitError(argv, result);
|
|
19267
|
-
}
|
|
19268
|
-
return result.stdout;
|
|
19269
|
-
}
|
|
19270
|
-
function buildExitError(argv, result) {
|
|
19271
|
-
const output = result.stderr.trim() || (result.stdout ?? "").trim();
|
|
19272
|
-
return new Error(`${argv.join(" ")} exited ${String(result.exitCode)}: ${output}`);
|
|
19273
|
-
}
|
|
19274
|
-
|
|
19275
|
-
// packages/daemon/src/host-net/net/nat66-ruleset.ts
|
|
19276
|
-
var NAT66_TABLE = "ip6 imp_nat66";
|
|
19277
|
-
function buildNat66Ruleset(prefix, uplink) {
|
|
19278
|
-
return [
|
|
19279
|
-
`table ${NAT66_TABLE} {}`,
|
|
19280
|
-
`delete table ${NAT66_TABLE}`,
|
|
19281
|
-
`table ${NAT66_TABLE} {`,
|
|
19282
|
-
" chain postrouting {",
|
|
19283
|
-
" type nat hook postrouting priority srcnat; policy accept;",
|
|
19284
|
-
` oifname "${uplink}" ip6 saddr ${prefix} masquerade`,
|
|
19285
|
-
" }",
|
|
19286
|
-
"}",
|
|
19287
|
-
""
|
|
19288
|
-
].join(`
|
|
19289
|
-
`);
|
|
19290
|
-
}
|
|
19291
|
-
function buildNat66Removal() {
|
|
19292
|
-
return `table ${NAT66_TABLE} {}
|
|
19293
|
-
delete table ${NAT66_TABLE}
|
|
19294
|
-
`;
|
|
19295
|
-
}
|
|
19296
|
-
|
|
19297
|
-
// packages/daemon/src/host-net/net/ipv6-plan.ts
|
|
19298
|
-
function parseIpv6Setting(text) {
|
|
19299
|
-
if (text === "auto" || text === "off") {
|
|
19300
|
-
return { kind: text };
|
|
19301
|
-
}
|
|
19302
|
-
const prefix = parsePrefix64(text);
|
|
19303
|
-
if (prefix === null) {
|
|
19304
|
-
throw new Error(`IMP_SUBNET6 must be auto, off or an IPv6 /64, got ${text}`);
|
|
19305
|
-
}
|
|
19306
|
-
return { kind: "routed", prefix };
|
|
19307
|
-
}
|
|
19308
|
-
async function resolveIpv6Plan(setting, deps) {
|
|
19309
|
-
const stopIpv6 = async (reason) => {
|
|
19310
|
-
await removeNat66(deps);
|
|
19311
|
-
getImpdLogger("ipv6").info("off ({reason})", { reason });
|
|
19312
|
-
return null;
|
|
19313
|
-
};
|
|
19314
|
-
if (setting.kind === "off") {
|
|
19315
|
-
return stopIpv6("IMP_SUBNET6=off");
|
|
19316
|
-
}
|
|
19317
|
-
const uplink = await deps.readDefaultRoute();
|
|
19318
|
-
if (setting.kind === "auto" && uplink === null) {
|
|
19319
|
-
return stopIpv6("IMP_SUBNET6=auto, and the container has no IPv6 default route");
|
|
19320
|
-
}
|
|
19321
|
-
const missing = await deps.checkHostRules();
|
|
19322
|
-
if (missing !== null) {
|
|
19323
|
-
return stopIpv6(`the host's IPv6 rules are not in place: ${missing}`);
|
|
19324
|
-
}
|
|
19325
|
-
if (setting.kind === "routed") {
|
|
19326
|
-
await removeNat66(deps);
|
|
19327
|
-
getImpdLogger("ipv6").info("{text}, routed{route}", {
|
|
19328
|
-
text: setting.prefix.text,
|
|
19329
|
-
route: uplink === null ? "; the container has no IPv6 default route" : ` via ${uplink}`
|
|
19330
|
-
});
|
|
19331
|
-
return { prefix: setting.prefix, nat66: false, uplink };
|
|
19332
|
-
}
|
|
19333
|
-
const prefix = deps.readUlaPrefix();
|
|
19334
|
-
try {
|
|
19335
|
-
await deps.runNft(buildNat66Ruleset(prefix.text, uplink ?? ""));
|
|
19336
|
-
} catch (error) {
|
|
19337
|
-
return stopIpv6(`NAT66: ${getErrorMessage(error)}`);
|
|
19338
|
-
}
|
|
19339
|
-
getImpdLogger("ipv6").info("{text}, NAT66 out of {uplink}", {
|
|
19340
|
-
text: prefix.text,
|
|
19341
|
-
uplink: uplink ?? ""
|
|
19342
|
-
});
|
|
19343
|
-
return { prefix, nat66: true, uplink };
|
|
19344
|
-
}
|
|
19345
|
-
async function removeNat66(deps) {
|
|
19346
|
-
try {
|
|
19347
|
-
await deps.runNft(buildNat66Removal());
|
|
19348
|
-
} catch {}
|
|
19349
|
-
}
|
|
19350
|
-
var HOST_RULES6 = [
|
|
19351
|
-
["filter", "-A INPUT -i imp+ -j DROP"],
|
|
19352
|
-
["filter", "-A FORWARD -i imp+ -o imp+ -j DROP"],
|
|
19353
|
-
["filter", "-A FORWARD -o imp+ -j DROP"],
|
|
19354
|
-
["filter", "-A FORWARD -i imp+ -j DROP"],
|
|
19355
|
-
["raw", "-A PREROUTING -i imp+ -m rpfilter --invert -j DROP"]
|
|
19356
|
-
];
|
|
19357
|
-
var HOST_SYSCTLS6 = [
|
|
19358
|
-
["default/accept_ra", "0"],
|
|
19359
|
-
["default/accept_redirects", "0"],
|
|
19360
|
-
["all/forwarding", "1"]
|
|
19361
|
-
];
|
|
19362
|
-
async function checkHostRules6(procSys = "/proc/sys/net/ipv6/conf", run = runCommand) {
|
|
19363
|
-
for (const table of ["filter", "raw"]) {
|
|
19364
|
-
const result = await run(["ip6tables", "-w", "-t", table, "-S"]);
|
|
19365
|
-
const rules = new Set(result.stdout.split(`
|
|
19366
|
-
`).map((line) => line.trim()));
|
|
19367
|
-
const missing = HOST_RULES6.find(([inTable, rule]) => inTable === table && !rules.has(rule));
|
|
19368
|
-
if (result.exitCode !== 0) {
|
|
19369
|
-
return `ip6tables -t ${table}: ${result.stderr.trim()}`;
|
|
19370
|
-
}
|
|
19371
|
-
if (missing !== undefined) {
|
|
19372
|
-
return `no ip6tables -t ${table} ${missing[1]}`;
|
|
19373
|
-
}
|
|
19374
|
-
}
|
|
19375
|
-
for (const [key, wanted] of HOST_SYSCTLS6) {
|
|
19376
|
-
const value = readSysctl(join2(procSys, key));
|
|
19377
|
-
if (value !== wanted) {
|
|
19378
|
-
return `net.ipv6.conf.${key.replace("/", ".")} is ${value ?? "missing"}, not ${wanted}`;
|
|
19379
|
-
}
|
|
19380
|
-
}
|
|
19381
|
-
return null;
|
|
19382
|
-
}
|
|
19383
|
-
function readSysctl(path) {
|
|
19384
|
-
try {
|
|
19385
|
-
return readFileSync2(path, "utf8").trim();
|
|
19386
|
-
} catch {
|
|
19387
|
-
return null;
|
|
19388
|
-
}
|
|
19389
|
-
}
|
|
19390
|
-
function readOrCreateUlaPrefix(path) {
|
|
19391
|
-
if (existsSync2(path)) {
|
|
19392
|
-
const prefix = parsePrefix64(readFileSync2(path, "utf8").trim());
|
|
19393
|
-
if (prefix !== null) {
|
|
19394
|
-
return prefix;
|
|
19395
|
-
}
|
|
19396
|
-
}
|
|
19397
|
-
const prefix = buildUlaPrefix(randomBytes(5));
|
|
19398
|
-
mkdirSync2(dirname(path), { recursive: true });
|
|
19399
|
-
writeFileSync(path, `${prefix.text}
|
|
19400
|
-
`);
|
|
19401
|
-
return prefix;
|
|
19402
|
-
}
|
|
19403
|
-
|
|
19404
|
-
// packages/daemon/src/host-net/net/range-matcher.ts
|
|
19405
|
-
import { IPv4 as IPv44 } from "ipaddr.js";
|
|
19406
|
-
function buildRangeMatcher(ranges) {
|
|
19407
|
-
const ranges4 = [];
|
|
19408
|
-
const ranges6 = [];
|
|
19409
|
-
for (const range of ranges) {
|
|
19410
|
-
const cidr = parseCidr(range);
|
|
19411
|
-
if (cidr === null) {
|
|
19412
|
-
throw new Error(`not an address range: ${range}`);
|
|
19413
|
-
}
|
|
19414
|
-
if (cidr.address instanceof IPv44) {
|
|
19415
|
-
ranges4.push([cidr.address, cidr.bits]);
|
|
19416
|
-
} else {
|
|
19417
|
-
ranges6.push([cidr.address, cidr.bits]);
|
|
19418
|
-
}
|
|
19419
|
-
}
|
|
19420
|
-
const isIn4 = (ip) => ranges4.some(([network, bits]) => ip.match(network, bits));
|
|
19421
|
-
const isIn6 = (ip) => ranges6.some(([network, bits]) => ip.match(network, bits));
|
|
19422
|
-
return (address) => {
|
|
19423
|
-
const ip = parseAddress(address);
|
|
19424
|
-
if (ip === null) {
|
|
19425
|
-
return false;
|
|
19426
|
-
}
|
|
19427
|
-
if (ip instanceof IPv44) {
|
|
19428
|
-
return isIn4(ip);
|
|
19429
|
-
}
|
|
19430
|
-
return isIn6(ip) || ip.isIPv4MappedAddress() && isIn4(ip.toIPv4Address());
|
|
19431
|
-
};
|
|
19432
|
-
}
|
|
19433
|
-
|
|
19434
|
-
// packages/daemon/src/identity/auth/parse-tailnet-rules.ts
|
|
19435
|
-
import * as z48 from "zod";
|
|
19436
|
-
// packages/api/src/schemas/lease-schema.ts
|
|
19437
|
-
import * as z2 from "zod";
|
|
19438
|
-
|
|
19439
|
-
// packages/api/src/schemas/name-schema.ts
|
|
19440
|
-
import * as z from "zod";
|
|
19441
|
-
var NameSchema = z.string().regex(/^[a-z][a-z0-9-]{0,30}$/u, "must be a lowercase letter followed by up to 30 lowercase letters, digits or hyphens");
|
|
19442
|
-
|
|
19443
|
-
// packages/api/src/schemas/lease-schema.ts
|
|
19444
|
-
var LeaseLabelSchema = z2.string().regex(/^[\w.:-]{1,64}$/u, "must be 1\u201364 letters, digits, dots, underscores, colons or hyphens");
|
|
19445
|
-
var LeaseTtlSchema = z2.int().min(10).max(3600);
|
|
19446
|
-
var LeaseOwnerSchema = z2.object({
|
|
19447
|
-
principal: z2.string(),
|
|
19448
|
-
display: z2.string(),
|
|
19449
|
-
label: z2.string()
|
|
19450
|
-
});
|
|
19451
|
-
var LeaseSchema = z2.object({
|
|
19452
|
-
name: NameSchema,
|
|
19453
|
-
owner: LeaseOwnerSchema,
|
|
19454
|
-
until: z2.date().nullable()
|
|
19455
|
-
});
|
|
19456
|
-
var LeaseSummarySchema = z2.object({
|
|
19457
|
-
leases: z2.array(LeaseSchema).readonly(),
|
|
19458
|
-
otherCount: z2.int().nonnegative()
|
|
19459
|
-
});
|
|
19460
|
-
// packages/api/src/protocols/build-context-protocol.ts
|
|
19461
|
-
import * as z3 from "zod";
|
|
19462
|
-
var BUILD_CONTEXT_PATH = "/images/build-context";
|
|
19463
|
-
var DockerfilePathSchema = z3.string().min(1).refine((path) => !path.startsWith("/") && !path.split(/[/\\]/u).includes(".."), {
|
|
19464
|
-
message: "the Dockerfile path must be relative and stay inside the build context"
|
|
19465
|
-
});
|
|
19466
|
-
var BuildContextQuerySchema = z3.object({
|
|
19467
|
-
name: NameSchema,
|
|
19468
|
-
dockerfile: DockerfilePathSchema.optional()
|
|
19469
|
-
});
|
|
19470
|
-
var BuildContextUploadSchema = z3.object({
|
|
19471
|
-
id: z3.string().min(1),
|
|
19472
|
-
expiresAt: z3.coerce.date()
|
|
19473
|
-
});
|
|
19474
|
-
var BuildContextErrorSchema = z3.object({ code: z3.string(), message: z3.string() });
|
|
19475
|
-
// packages/api/src/schemas/image-op-event-schema.ts
|
|
19476
|
-
import * as z5 from "zod";
|
|
19477
|
-
|
|
19478
|
-
// packages/api/src/schemas/image-schema.ts
|
|
19479
|
-
import * as z4 from "zod";
|
|
19480
|
-
var ImageSourceSchema = z4.enum(["oci", "imp"]);
|
|
19481
|
-
var ImageSchema = z4.object({
|
|
19482
|
-
id: z4.string(),
|
|
19483
|
-
name: NameSchema,
|
|
19484
|
-
ref: z4.string(),
|
|
19485
|
-
digest: z4.string(),
|
|
19486
|
-
source: ImageSourceSchema,
|
|
19487
|
-
createdAt: z4.date(),
|
|
19488
|
-
sizeBytes: z4.int().nonnegative(),
|
|
19489
|
-
pinned: z4.boolean().default(false)
|
|
19490
|
-
});
|
|
19491
|
-
|
|
19492
|
-
// packages/api/src/schemas/image-op-event-schema.ts
|
|
19493
|
-
var ImageBuildPhaseSchema = z5.enum(["upload", "pack", "build", "pull", "unpack", "copy"]);
|
|
19494
|
-
var IMAGE_BUILD_LOG_MAX_CHARS = 8192;
|
|
19495
|
-
var ImageBuildProgressSchema = z5.object({
|
|
19496
|
-
type: z5.literal("progress"),
|
|
19497
|
-
phase: ImageBuildPhaseSchema,
|
|
19498
|
-
elapsedMs: z5.number().nonnegative(),
|
|
19499
|
-
step: z5.string().optional()
|
|
19500
|
-
});
|
|
19501
|
-
var ImageBuildLogSchema = z5.object({
|
|
19502
|
-
type: z5.literal("log"),
|
|
19503
|
-
text: z5.string().max(IMAGE_BUILD_LOG_MAX_CHARS)
|
|
19504
|
-
});
|
|
19505
|
-
var ImageOpEventSchema = z5.discriminatedUnion("type", [
|
|
19506
|
-
ImageBuildProgressSchema,
|
|
19507
|
-
ImageBuildLogSchema,
|
|
19508
|
-
z5.object({ type: z5.literal("image"), image: ImageSchema })
|
|
19509
|
-
]);
|
|
19510
|
-
// packages/api/src/protocols/exec-protocol.ts
|
|
19511
|
-
import * as z9 from "zod";
|
|
19057
|
+
// packages/api/src/schemas/image-op-event-schema.ts
|
|
19058
|
+
var ImageBuildPhaseSchema = z5.enum(["upload", "pack", "build", "pull", "unpack", "copy"]);
|
|
19059
|
+
var IMAGE_BUILD_LOG_MAX_CHARS = 8192;
|
|
19060
|
+
var ImageBuildProgressSchema = z5.object({
|
|
19061
|
+
type: z5.literal("progress"),
|
|
19062
|
+
phase: ImageBuildPhaseSchema,
|
|
19063
|
+
elapsedMs: z5.number().nonnegative(),
|
|
19064
|
+
step: z5.string().optional()
|
|
19065
|
+
});
|
|
19066
|
+
var ImageBuildLogSchema = z5.object({
|
|
19067
|
+
type: z5.literal("log"),
|
|
19068
|
+
text: z5.string().max(IMAGE_BUILD_LOG_MAX_CHARS)
|
|
19069
|
+
});
|
|
19070
|
+
var ImageOpEventSchema = z5.discriminatedUnion("type", [
|
|
19071
|
+
ImageBuildProgressSchema,
|
|
19072
|
+
ImageBuildLogSchema,
|
|
19073
|
+
z5.object({ type: z5.literal("image"), image: ImageSchema })
|
|
19074
|
+
]);
|
|
19075
|
+
// packages/api/src/protocols/exec-protocol.ts
|
|
19076
|
+
import * as z9 from "zod";
|
|
19512
19077
|
|
|
19513
19078
|
// packages/api/src/schemas/session-output-schema.ts
|
|
19514
19079
|
import * as z7 from "zod";
|
|
@@ -20643,6 +20208,7 @@ var SystemInfoSchema = z33.object({
|
|
|
20643
20208
|
ramUsedMib: z33.int().nonnegative(),
|
|
20644
20209
|
ramReservedMib: z33.int().nonnegative(),
|
|
20645
20210
|
ramCommittedMib: z33.int().nonnegative(),
|
|
20211
|
+
ramPriorityKeptMib: z33.int().nonnegative().optional(),
|
|
20646
20212
|
ramSleepingMib: z33.int().nonnegative().optional(),
|
|
20647
20213
|
awakeCount: z33.int().nonnegative(),
|
|
20648
20214
|
impCount: z33.int().nonnegative(),
|
|
@@ -21105,166 +20671,603 @@ var authContract = {
|
|
|
21105
20671
|
logout: contractBase.meta(ACCESS.noSession).output(EmptySchema)
|
|
21106
20672
|
};
|
|
21107
20673
|
|
|
21108
|
-
// packages/api/src/contract/session-contract.ts
|
|
21109
|
-
var sessionContract = { auth: authContract };
|
|
21110
|
-
// packages/api/src/contract/collect-procedure-access.ts
|
|
21111
|
-
import { isContractProcedure } from "@orpc/contract";
|
|
21112
|
-
import * as z45 from "zod";
|
|
20674
|
+
// packages/api/src/contract/session-contract.ts
|
|
20675
|
+
var sessionContract = { auth: authContract };
|
|
20676
|
+
// packages/api/src/contract/collect-procedure-access.ts
|
|
20677
|
+
import { isContractProcedure } from "@orpc/contract";
|
|
20678
|
+
import * as z45 from "zod";
|
|
20679
|
+
|
|
20680
|
+
// packages/api/src/schemas/procedure-access-schema.ts
|
|
20681
|
+
import * as z44 from "zod";
|
|
20682
|
+
var FieldsSchema = z44.array(z44.string()).readonly();
|
|
20683
|
+
var NoSessionAccessSchema = z44.object({ on: z44.literal("none") });
|
|
20684
|
+
var HostAccessSchema = z44.object({
|
|
20685
|
+
scope: ScopeSchema,
|
|
20686
|
+
on: z44.literal("host"),
|
|
20687
|
+
audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
|
|
20688
|
+
});
|
|
20689
|
+
var ImpAccessSchema = z44.object({
|
|
20690
|
+
scope: ScopeSchema,
|
|
20691
|
+
on: z44.literal("imp"),
|
|
20692
|
+
fields: FieldsSchema,
|
|
20693
|
+
noGrantable: z44.literal(true).optional(),
|
|
20694
|
+
audit: z44.literal(false).optional()
|
|
20695
|
+
});
|
|
20696
|
+
var GrantAccessSchema = z44.object({
|
|
20697
|
+
scope: ScopeSchema,
|
|
20698
|
+
on: z44.literal("grant"),
|
|
20699
|
+
fields: FieldsSchema,
|
|
20700
|
+
secretField: z44.string(),
|
|
20701
|
+
audit: z44.literal(false).optional()
|
|
20702
|
+
});
|
|
20703
|
+
var ImageAccessSchema = z44.object({
|
|
20704
|
+
scope: ScopeSchema,
|
|
20705
|
+
on: z44.literal("image"),
|
|
20706
|
+
held: z44.literal(true).optional(),
|
|
20707
|
+
audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
|
|
20708
|
+
});
|
|
20709
|
+
var AnyAccessSchema = z44.object({
|
|
20710
|
+
scope: ScopeSchema,
|
|
20711
|
+
on: z44.literal("any"),
|
|
20712
|
+
audit: z44.literal(false).optional()
|
|
20713
|
+
});
|
|
20714
|
+
var ProcedureAccessSchema = z44.discriminatedUnion("on", [
|
|
20715
|
+
NoSessionAccessSchema,
|
|
20716
|
+
HostAccessSchema,
|
|
20717
|
+
ImpAccessSchema,
|
|
20718
|
+
GrantAccessSchema,
|
|
20719
|
+
ImageAccessSchema,
|
|
20720
|
+
AnyAccessSchema
|
|
20721
|
+
]);
|
|
20722
|
+
|
|
20723
|
+
// packages/api/src/contract/collect-procedure-access.ts
|
|
20724
|
+
var MetaSchema = z45.object({ access: ProcedureAccessSchema });
|
|
20725
|
+
function collectProcedureAccess(router, prefix = "") {
|
|
20726
|
+
return Object.entries(router).flatMap(([key, value]) => {
|
|
20727
|
+
const path = `${prefix}${key}`;
|
|
20728
|
+
if (isContractProcedure(value)) {
|
|
20729
|
+
const meta = value["~orpc"].meta;
|
|
20730
|
+
const parsed = MetaSchema.safeParse(meta);
|
|
20731
|
+
return [{ path, access: parsed.success ? parsed.data.access : null }];
|
|
20732
|
+
}
|
|
20733
|
+
return typeof value === "object" && value !== null ? collectProcedureAccess(value, `${path}.`) : [];
|
|
20734
|
+
});
|
|
20735
|
+
}
|
|
20736
|
+
// packages/api/src/rules/is-imp-allowed.ts
|
|
20737
|
+
function isImpAllowed(patterns, name) {
|
|
20738
|
+
return patterns === null || patterns.some((pattern) => isPatternMatch(pattern, name));
|
|
20739
|
+
}
|
|
20740
|
+
function isPatternMatch(pattern, name) {
|
|
20741
|
+
const [first = "", ...rest] = pattern.split("*");
|
|
20742
|
+
if (rest.length === 0) {
|
|
20743
|
+
return name === first;
|
|
20744
|
+
}
|
|
20745
|
+
const last = rest.pop() ?? "";
|
|
20746
|
+
if (!name.startsWith(first) || !name.endsWith(last) || name.length < first.length + last.length) {
|
|
20747
|
+
return false;
|
|
20748
|
+
}
|
|
20749
|
+
let at = first.length;
|
|
20750
|
+
const end = name.length - last.length;
|
|
20751
|
+
for (const part of rest) {
|
|
20752
|
+
const found = name.indexOf(part, at);
|
|
20753
|
+
if (found === -1 || found + part.length > end) {
|
|
20754
|
+
return false;
|
|
20755
|
+
}
|
|
20756
|
+
at = found + part.length;
|
|
20757
|
+
}
|
|
20758
|
+
return true;
|
|
20759
|
+
}
|
|
20760
|
+
// packages/api/src/protocols/login-shell.ts
|
|
20761
|
+
var CONSOLE_SHELL = buildConsoleShell("/etc/passwd", ["/bin/bash", "/bin/sh"]);
|
|
20762
|
+
function buildConsoleShell(passwdPath, fallbacks) {
|
|
20763
|
+
return [
|
|
20764
|
+
"uid=$(id -u 2>/dev/null) || uid=0",
|
|
20765
|
+
"shell=",
|
|
20766
|
+
"while IFS=: read -r _ _ id _ _ _ login; do",
|
|
20767
|
+
' if [ "$id" = "$uid" ]; then shell=$login; break; fi',
|
|
20768
|
+
`done < ${passwdPath}`,
|
|
20769
|
+
...fallbacks.map((fallback) => `[ -x "$shell" ] || shell=${fallback}`),
|
|
20770
|
+
'exec "$shell" -l'
|
|
20771
|
+
].join(`
|
|
20772
|
+
`);
|
|
20773
|
+
}
|
|
20774
|
+
// packages/api/src/protocols/mcp-path.ts
|
|
20775
|
+
var MCP_PATH = "/mcp";
|
|
20776
|
+
// packages/api/src/protocols/move-receive-path.ts
|
|
20777
|
+
var MOVE_RECEIVE_PATH = "/move/receive";
|
|
20778
|
+
// packages/api/src/contract/move-peer-contract.ts
|
|
20779
|
+
import { oc as oc2 } from "@orpc/contract";
|
|
20780
|
+
|
|
20781
|
+
// packages/api/src/schemas/move-peer-schema.ts
|
|
20782
|
+
import * as z46 from "zod";
|
|
20783
|
+
var SystemDriveSchema = z46.string().regex(/^[\da-f]{64}$/v);
|
|
20784
|
+
var MoveOfferSchema = z46.object({
|
|
20785
|
+
imageDigest: z46.string(),
|
|
20786
|
+
receivedImageDigest: z46.string().nullable().default(null),
|
|
20787
|
+
systemDrive: SystemDriveSchema.optional()
|
|
20788
|
+
});
|
|
20789
|
+
var MoveOfferReplySchema = z46.object({
|
|
20790
|
+
needsImage: z46.boolean(),
|
|
20791
|
+
needsSystemDrive: z46.boolean().default(false),
|
|
20792
|
+
storage: z46.enum(["xfs", "zfs"]),
|
|
20793
|
+
keepsMaxMemory: z46.boolean().default(false),
|
|
20794
|
+
keepsLeases: z46.boolean().default(false),
|
|
20795
|
+
keepsPublicEgress: z46.boolean().default(false),
|
|
20796
|
+
keepsBackupOff: z46.boolean().default(false)
|
|
20797
|
+
});
|
|
20798
|
+
var MoveCommitReplySchema = z46.object({ isCommitted: z46.boolean() });
|
|
20799
|
+
|
|
20800
|
+
// packages/api/src/contract/move-peer-contract.ts
|
|
20801
|
+
var movePeerContract = {
|
|
20802
|
+
offer: oc2.route({ method: "POST", path: "/move/offer" }).input(MoveOfferSchema).output(MoveOfferReplySchema),
|
|
20803
|
+
commit: oc2.route({ method: "POST", path: "/move/commit" }).output(MoveCommitReplySchema),
|
|
20804
|
+
abort: oc2.route({ method: "POST", path: "/move/abort" }).output(MoveCommitReplySchema)
|
|
20805
|
+
};
|
|
20806
|
+
// packages/api/src/schemas/secret-presets.ts
|
|
20807
|
+
var SECRET_PRESETS = {
|
|
20808
|
+
github: {
|
|
20809
|
+
kind: "static",
|
|
20810
|
+
rules: [
|
|
20811
|
+
{ host: "github.com", header: "authorization", scheme: "basic", user: "x-access-token" },
|
|
20812
|
+
{ host: "api.github.com", header: "authorization", scheme: "bearer" },
|
|
20813
|
+
{ host: "uploads.github.com", header: "authorization", scheme: "bearer" }
|
|
20814
|
+
],
|
|
20815
|
+
env: ["GH_TOKEN", "GITHUB_TOKEN"]
|
|
20816
|
+
},
|
|
20817
|
+
anthropic: {
|
|
20818
|
+
kind: "static",
|
|
20819
|
+
rules: [{ host: "api.anthropic.com", header: "x-api-key", scheme: "raw" }],
|
|
20820
|
+
env: ["ANTHROPIC_API_KEY"]
|
|
20821
|
+
},
|
|
20822
|
+
npm: {
|
|
20823
|
+
kind: "static",
|
|
20824
|
+
rules: [{ host: "registry.npmjs.org", header: "authorization", scheme: "bearer" }],
|
|
20825
|
+
env: ["NPM_TOKEN"]
|
|
20826
|
+
}
|
|
20827
|
+
};
|
|
20828
|
+
// packages/api/src/rules/has-scope.ts
|
|
20829
|
+
function hasScope(granted, needed) {
|
|
20830
|
+
const grantedRank = ScopeSchema.options.indexOf(granted);
|
|
20831
|
+
const neededRank = ScopeSchema.options.indexOf(needed);
|
|
20832
|
+
return grantedRank !== -1 && neededRank !== -1 && grantedRank >= neededRank;
|
|
20833
|
+
}
|
|
20834
|
+
// packages/daemon/src/config.ts
|
|
20835
|
+
import * as z53 from "zod";
|
|
20836
|
+
// packages/shared/src/format-bytes.ts
|
|
20837
|
+
var UNITS = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
20838
|
+
function formatBytes(bytes) {
|
|
20839
|
+
let value = bytes;
|
|
20840
|
+
let unit = 0;
|
|
20841
|
+
while (value >= 1024 && unit < UNITS.length - 1) {
|
|
20842
|
+
value /= 1024;
|
|
20843
|
+
unit += 1;
|
|
20844
|
+
}
|
|
20845
|
+
const digits = unit === 0 || value >= 100 ? 0 : 1;
|
|
20846
|
+
return `${value.toFixed(digits)} ${UNITS[unit] ?? "B"}`;
|
|
20847
|
+
}
|
|
20848
|
+
// packages/shared/src/get-error-message.ts
|
|
20849
|
+
function getErrorMessage(error) {
|
|
20850
|
+
return error instanceof Error ? error.message : String(error);
|
|
20851
|
+
}
|
|
20852
|
+
// packages/shared/src/merge-bytes.ts
|
|
20853
|
+
function mergeBytes(chunks) {
|
|
20854
|
+
const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
|
|
20855
|
+
let offset = 0;
|
|
20856
|
+
for (const chunk of chunks) {
|
|
20857
|
+
merged.set(chunk, offset);
|
|
20858
|
+
offset += chunk.byteLength;
|
|
20859
|
+
}
|
|
20860
|
+
return merged;
|
|
20861
|
+
}
|
|
20862
|
+
// packages/shared/src/to-plain-octets.ts
|
|
20863
|
+
var DOTTED_ENTRY = /^(?<octets>\d{1,3}(?:\.\d{1,3}){3})(?<prefix>\/\d{1,2})?$/u;
|
|
20864
|
+
function toPlainOctets(entry) {
|
|
20865
|
+
const groups = DOTTED_ENTRY.exec(entry)?.groups;
|
|
20866
|
+
if (groups === undefined) {
|
|
20867
|
+
return entry;
|
|
20868
|
+
}
|
|
20869
|
+
const octets = (groups["octets"] ?? "").split(".").map((octet) => String(Number(octet)));
|
|
20870
|
+
return `${octets.join(".")}${groups["prefix"] ?? ""}`;
|
|
20871
|
+
}
|
|
20872
|
+
// packages/daemon/src/host-net/net/addressing.ts
|
|
20873
|
+
import { IPv4 as IPv43 } from "ipaddr.js";
|
|
20874
|
+
|
|
20875
|
+
// packages/daemon/src/host-net/net/addressing6.ts
|
|
20876
|
+
import { IPv6 as IPv62 } from "ipaddr.js";
|
|
20877
|
+
|
|
20878
|
+
// packages/daemon/src/host-net/net/parse-address.ts
|
|
20879
|
+
import { IPv4, IPv6 } from "ipaddr.js";
|
|
20880
|
+
function parseAddress(text) {
|
|
20881
|
+
if (IPv4.isValidFourPartDecimal(text)) {
|
|
20882
|
+
return IPv4.parse(text);
|
|
20883
|
+
}
|
|
20884
|
+
if (text.includes("%")) {
|
|
20885
|
+
return null;
|
|
20886
|
+
}
|
|
20887
|
+
const hex = toHexGroups(text);
|
|
20888
|
+
return hex !== null && IPv6.isValid(hex) ? IPv6.parse(hex) : null;
|
|
20889
|
+
}
|
|
20890
|
+
function toHexGroups(text) {
|
|
20891
|
+
const split = text.lastIndexOf(":");
|
|
20892
|
+
const tail = text.slice(split + 1);
|
|
20893
|
+
if (!tail.includes(".")) {
|
|
20894
|
+
return text;
|
|
20895
|
+
}
|
|
20896
|
+
if (!IPv4.isValidFourPartDecimal(tail)) {
|
|
20897
|
+
return null;
|
|
20898
|
+
}
|
|
20899
|
+
const [first = 0, second = 0, third = 0, fourth = 0] = IPv4.parse(tail).octets;
|
|
20900
|
+
return `${text.slice(0, split + 1)}${toGroup(first, second)}:${toGroup(third, fourth)}`;
|
|
20901
|
+
}
|
|
20902
|
+
function toGroup(high, low) {
|
|
20903
|
+
return (high * 256 + low).toString(16);
|
|
20904
|
+
}
|
|
20905
|
+
|
|
20906
|
+
// packages/daemon/src/host-net/net/parse-cidr.ts
|
|
20907
|
+
import { IPv4 as IPv42 } from "ipaddr.js";
|
|
20908
|
+
function parseCidr(text) {
|
|
20909
|
+
const [addressText = "", prefix, ...rest] = text.split("/");
|
|
20910
|
+
const address = parseAddress(addressText);
|
|
20911
|
+
if (address === null || rest.length > 0 || prefix !== undefined && !/^\d{1,3}$/v.test(prefix)) {
|
|
20912
|
+
return null;
|
|
20913
|
+
}
|
|
20914
|
+
const maxBits = address instanceof IPv42 ? 32 : 128;
|
|
20915
|
+
const bits = prefix === undefined ? maxBits : Number(prefix);
|
|
20916
|
+
return bits > maxBits ? null : { address, bits };
|
|
20917
|
+
}
|
|
20918
|
+
|
|
20919
|
+
// packages/daemon/src/host-net/net/addressing6.ts
|
|
20920
|
+
var GROUPS = 8;
|
|
20921
|
+
var GROUP_BITS = 16n;
|
|
20922
|
+
var PREFIX_LENGTH = 64;
|
|
20923
|
+
var GATEWAY_IP6 = "fe80::1";
|
|
20924
|
+
function parseIpv6(text) {
|
|
20925
|
+
const address = parseAddress(text);
|
|
20926
|
+
return address instanceof IPv62 ? toBigint(address) : null;
|
|
20927
|
+
}
|
|
20928
|
+
function formatIpv6(address) {
|
|
20929
|
+
const groups = Array.from({ length: GROUPS }, (_, index) => Number(address >> GROUP_BITS * BigInt(GROUPS - 1 - index) & 0xffffn));
|
|
20930
|
+
return new IPv62(groups).toRFC5952String();
|
|
20931
|
+
}
|
|
20932
|
+
function formatCidr6(text) {
|
|
20933
|
+
const parsed = parseCidr(text);
|
|
20934
|
+
if (parsed === null || !(parsed.address instanceof IPv62)) {
|
|
20935
|
+
return null;
|
|
20936
|
+
}
|
|
20937
|
+
const hostBits = BigInt(128 - parsed.bits);
|
|
20938
|
+
const network = toBigint(parsed.address) >> hostBits << hostBits;
|
|
20939
|
+
return `${formatIpv6(network)}/${String(parsed.bits)}`;
|
|
20940
|
+
}
|
|
20941
|
+
function parsePrefix64(cidr) {
|
|
20942
|
+
const parsed = parseCidr(cidr);
|
|
20943
|
+
const network = parsed?.address instanceof IPv62 ? toBigint(parsed.address) : null;
|
|
20944
|
+
if (network === null || !cidr.endsWith(`/${String(PREFIX_LENGTH)}`) || network % 2n ** 64n !== 0n) {
|
|
20945
|
+
return null;
|
|
20946
|
+
}
|
|
20947
|
+
return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
|
|
20948
|
+
}
|
|
20949
|
+
function buildUlaPrefix(random) {
|
|
20950
|
+
if (random.length < 5) {
|
|
20951
|
+
throw new Error("a ULA global ID needs 5 random bytes");
|
|
20952
|
+
}
|
|
20953
|
+
const globalId = [...random.subarray(0, 5)].reduce((acc, byte) => (acc << 8n) + BigInt(byte), 0n);
|
|
20954
|
+
const network = (0xfdn << 40n) + globalId << 80n;
|
|
20955
|
+
return { network, text: `${formatIpv6(network)}/${String(PREFIX_LENGTH)}` };
|
|
20956
|
+
}
|
|
20957
|
+
function findMappedIpv4(address) {
|
|
20958
|
+
const parsed = parseAddress(address);
|
|
20959
|
+
return parsed instanceof IPv62 && parsed.isIPv4MappedAddress() ? parsed.toIPv4Address().toString() : null;
|
|
20960
|
+
}
|
|
20961
|
+
function deriveGuestIp6(prefix, guestIpv4) {
|
|
20962
|
+
return formatIpv6(prefix.network + BigInt(guestIpv4));
|
|
20963
|
+
}
|
|
20964
|
+
function toBigint(address) {
|
|
20965
|
+
return address.parts.reduce((acc, group) => (acc << GROUP_BITS) + BigInt(group), 0n);
|
|
20966
|
+
}
|
|
20967
|
+
|
|
20968
|
+
// packages/daemon/src/host-net/net/addressing.ts
|
|
20969
|
+
var SLOT_SIZE = 4;
|
|
20970
|
+
var SLOT_PREFIX_LENGTH = 30;
|
|
20971
|
+
function parseSubnet(cidr) {
|
|
20972
|
+
const parsed = cidr.includes("/") ? parseCidr(cidr) : null;
|
|
20973
|
+
if (parsed === null || !(parsed.address instanceof IPv43)) {
|
|
20974
|
+
throw new Error(`subnet is not an IPv4 CIDR: ${cidr}`);
|
|
20975
|
+
}
|
|
20976
|
+
if (parsed.bits < 8 || parsed.bits > SLOT_PREFIX_LENGTH) {
|
|
20977
|
+
throw new Error(`subnet prefix must be between /8 and /30: ${cidr}`);
|
|
20978
|
+
}
|
|
20979
|
+
const network = toNumber(parsed.address);
|
|
20980
|
+
if (network % 2 ** (32 - parsed.bits) !== 0) {
|
|
20981
|
+
throw new Error(`subnet has host bits set: ${cidr}`);
|
|
20982
|
+
}
|
|
20983
|
+
return { network, prefixLength: parsed.bits };
|
|
20984
|
+
}
|
|
20985
|
+
var TAILNET_NETWORK = 100 * 2 ** 24 + 64 * 2 ** 16;
|
|
20986
|
+
var TAILNET_SIZE = 2 ** 22;
|
|
20987
|
+
function isTailnetOverlap(subnet) {
|
|
20988
|
+
const start = subnet.network;
|
|
20989
|
+
const end = start + 2 ** (32 - subnet.prefixLength);
|
|
20990
|
+
return start < TAILNET_NETWORK + TAILNET_SIZE && TAILNET_NETWORK < end;
|
|
20991
|
+
}
|
|
20992
|
+
function countSlots(subnet) {
|
|
20993
|
+
return 2 ** (32 - subnet.prefixLength) / SLOT_SIZE;
|
|
20994
|
+
}
|
|
20995
|
+
function deriveSlotAddress(slot, plan) {
|
|
20996
|
+
if (!Number.isInteger(slot) || slot < 0 || slot >= countSlots(plan.subnet)) {
|
|
20997
|
+
throw new RangeError(`slot ${String(slot)} is outside the subnet`);
|
|
20998
|
+
}
|
|
20999
|
+
const base = plan.subnet.network + slot * SLOT_SIZE;
|
|
21000
|
+
const guest = base + 2;
|
|
21001
|
+
return {
|
|
21002
|
+
slot,
|
|
21003
|
+
tap: `imp${String(slot)}`,
|
|
21004
|
+
hostIp: formatIpv4(base + 1),
|
|
21005
|
+
guestIp: formatIpv4(guest),
|
|
21006
|
+
prefixLength: SLOT_PREFIX_LENGTH,
|
|
21007
|
+
netmask: "255.255.255.252",
|
|
21008
|
+
guestMac: ["06", "00", ...splitOctets(guest).map((octet) => toHexByte(octet))].join(":"),
|
|
21009
|
+
hostMac: ["06", "01", ...splitOctets(base + 1).map((octet) => toHexByte(octet))].join(":"),
|
|
21010
|
+
guestIp6: plan.prefix6 === undefined || plan.prefix6 === null ? null : deriveGuestIp6(plan.prefix6, guest),
|
|
21011
|
+
tailnetPort: plan.portBase + slot
|
|
21012
|
+
};
|
|
21013
|
+
}
|
|
21014
|
+
function parseIpv4(text, allowMapped = false) {
|
|
21015
|
+
const address = parseAddress(text);
|
|
21016
|
+
if (address instanceof IPv43) {
|
|
21017
|
+
return toNumber(address);
|
|
21018
|
+
}
|
|
21019
|
+
return allowMapped && address?.isIPv4MappedAddress() === true ? toNumber(address.toIPv4Address()) : null;
|
|
21020
|
+
}
|
|
21021
|
+
function findPeerSlot(peer, local, subnet) {
|
|
21022
|
+
const peerIp = parseIpv4(peer, true);
|
|
21023
|
+
const localIp = parseIpv4(local, true);
|
|
21024
|
+
if (peerIp === null || localIp === null) {
|
|
21025
|
+
return null;
|
|
21026
|
+
}
|
|
21027
|
+
const offset = peerIp - subnet.network;
|
|
21028
|
+
if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
|
|
21029
|
+
return null;
|
|
21030
|
+
}
|
|
21031
|
+
return localIp === peerIp - 1 ? (offset - 2) / SLOT_SIZE : null;
|
|
21032
|
+
}
|
|
21033
|
+
function findGuestSlot(address, subnet) {
|
|
21034
|
+
const ip = parseIpv4(address, true);
|
|
21035
|
+
const offset = ip === null ? -1 : ip - subnet.network;
|
|
21036
|
+
if (offset < 0 || offset >= 2 ** (32 - subnet.prefixLength) || offset % SLOT_SIZE !== 2) {
|
|
21037
|
+
return null;
|
|
21038
|
+
}
|
|
21039
|
+
return (offset - 2) / SLOT_SIZE;
|
|
21040
|
+
}
|
|
21041
|
+
function formatSubnet(subnet) {
|
|
21042
|
+
return `${formatIpv4(subnet.network)}/${String(subnet.prefixLength)}`;
|
|
21043
|
+
}
|
|
21044
|
+
function formatCidr4(text) {
|
|
21045
|
+
const parsed = parseCidr(toPlainOctets(text));
|
|
21046
|
+
if (parsed === null || !(parsed.address instanceof IPv43)) {
|
|
21047
|
+
return null;
|
|
21048
|
+
}
|
|
21049
|
+
const ip = toNumber(parsed.address);
|
|
21050
|
+
return formatSubnet({ network: ip - ip % 2 ** (32 - parsed.bits), prefixLength: parsed.bits });
|
|
21051
|
+
}
|
|
21052
|
+
function toNumber(address) {
|
|
21053
|
+
return address.octets.reduce((acc, octet) => acc * 256 + octet, 0);
|
|
21054
|
+
}
|
|
21055
|
+
function formatIpv4(address) {
|
|
21056
|
+
return splitOctets(address).join(".");
|
|
21057
|
+
}
|
|
21058
|
+
function splitOctets(address) {
|
|
21059
|
+
return [
|
|
21060
|
+
Math.floor(address / 2 ** 24) % 256,
|
|
21061
|
+
Math.floor(address / 2 ** 16) % 256,
|
|
21062
|
+
Math.floor(address / 2 ** 8) % 256,
|
|
21063
|
+
address % 256
|
|
21064
|
+
];
|
|
21065
|
+
}
|
|
21066
|
+
function toHexByte(octet) {
|
|
21067
|
+
return octet.toString(16).padStart(2, "0");
|
|
21068
|
+
}
|
|
21113
21069
|
|
|
21114
|
-
// packages/
|
|
21115
|
-
import
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
var HostAccessSchema = z44.object({
|
|
21119
|
-
scope: ScopeSchema,
|
|
21120
|
-
on: z44.literal("host"),
|
|
21121
|
-
audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
|
|
21122
|
-
});
|
|
21123
|
-
var ImpAccessSchema = z44.object({
|
|
21124
|
-
scope: ScopeSchema,
|
|
21125
|
-
on: z44.literal("imp"),
|
|
21126
|
-
fields: FieldsSchema,
|
|
21127
|
-
noGrantable: z44.literal(true).optional(),
|
|
21128
|
-
audit: z44.literal(false).optional()
|
|
21129
|
-
});
|
|
21130
|
-
var GrantAccessSchema = z44.object({
|
|
21131
|
-
scope: ScopeSchema,
|
|
21132
|
-
on: z44.literal("grant"),
|
|
21133
|
-
fields: FieldsSchema,
|
|
21134
|
-
secretField: z44.string(),
|
|
21135
|
-
audit: z44.literal(false).optional()
|
|
21136
|
-
});
|
|
21137
|
-
var ImageAccessSchema = z44.object({
|
|
21138
|
-
scope: ScopeSchema,
|
|
21139
|
-
on: z44.literal("image"),
|
|
21140
|
-
held: z44.literal(true).optional(),
|
|
21141
|
-
audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
|
|
21142
|
-
});
|
|
21143
|
-
var AnyAccessSchema = z44.object({
|
|
21144
|
-
scope: ScopeSchema,
|
|
21145
|
-
on: z44.literal("any"),
|
|
21146
|
-
audit: z44.literal(false).optional()
|
|
21147
|
-
});
|
|
21148
|
-
var ProcedureAccessSchema = z44.discriminatedUnion("on", [
|
|
21149
|
-
NoSessionAccessSchema,
|
|
21150
|
-
HostAccessSchema,
|
|
21151
|
-
ImpAccessSchema,
|
|
21152
|
-
GrantAccessSchema,
|
|
21153
|
-
ImageAccessSchema,
|
|
21154
|
-
AnyAccessSchema
|
|
21155
|
-
]);
|
|
21070
|
+
// packages/daemon/src/host-net/net/ipv6-plan.ts
|
|
21071
|
+
import { randomBytes } from "crypto";
|
|
21072
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
21073
|
+
import { dirname, join as join2 } from "path";
|
|
21156
21074
|
|
|
21157
|
-
// packages/
|
|
21158
|
-
|
|
21159
|
-
function
|
|
21160
|
-
return
|
|
21161
|
-
|
|
21162
|
-
|
|
21163
|
-
|
|
21164
|
-
|
|
21165
|
-
|
|
21166
|
-
|
|
21167
|
-
|
|
21075
|
+
// packages/daemon/src/platform/process/get-impd-logger.ts
|
|
21076
|
+
import { getLogger } from "@logtape/logtape";
|
|
21077
|
+
function getImpdLogger(area) {
|
|
21078
|
+
return getLogger(["impd", area]);
|
|
21079
|
+
}
|
|
21080
|
+
|
|
21081
|
+
// packages/daemon/src/platform/process/run-command.ts
|
|
21082
|
+
async function runCommand(argv, options = {}) {
|
|
21083
|
+
const child = Bun.spawn([...argv], {
|
|
21084
|
+
stdin: "ignore",
|
|
21085
|
+
stdout: "pipe",
|
|
21086
|
+
stderr: "pipe",
|
|
21087
|
+
env: options.env === undefined ? process.env : { ...options.env },
|
|
21088
|
+
...options.signal !== undefined && { signal: options.signal }
|
|
21168
21089
|
});
|
|
21090
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
21091
|
+
new Response(child.stdout).text(),
|
|
21092
|
+
new Response(child.stderr).text(),
|
|
21093
|
+
child.exited
|
|
21094
|
+
]);
|
|
21095
|
+
return { exitCode, stdout, stderr };
|
|
21096
|
+
}
|
|
21097
|
+
async function runChecked(argv, options = {}, run = runCommand) {
|
|
21098
|
+
const result = await run(argv, options);
|
|
21099
|
+
if (result.exitCode !== 0) {
|
|
21100
|
+
throw buildExitError(argv, result);
|
|
21101
|
+
}
|
|
21102
|
+
return result.stdout;
|
|
21103
|
+
}
|
|
21104
|
+
function buildExitError(argv, result) {
|
|
21105
|
+
const output = result.stderr.trim() || (result.stdout ?? "").trim();
|
|
21106
|
+
return new Error(`${argv.join(" ")} exited ${String(result.exitCode)}: ${output}`);
|
|
21107
|
+
}
|
|
21108
|
+
|
|
21109
|
+
// packages/daemon/src/host-net/net/nat66-ruleset.ts
|
|
21110
|
+
var NAT66_TABLE = "ip6 imp_nat66";
|
|
21111
|
+
function buildNat66Ruleset(prefix, uplink) {
|
|
21112
|
+
return [
|
|
21113
|
+
`table ${NAT66_TABLE} {}`,
|
|
21114
|
+
`delete table ${NAT66_TABLE}`,
|
|
21115
|
+
`table ${NAT66_TABLE} {`,
|
|
21116
|
+
" chain postrouting {",
|
|
21117
|
+
" type nat hook postrouting priority srcnat; policy accept;",
|
|
21118
|
+
` oifname "${uplink}" ip6 saddr ${prefix} masquerade`,
|
|
21119
|
+
" }",
|
|
21120
|
+
"}",
|
|
21121
|
+
""
|
|
21122
|
+
].join(`
|
|
21123
|
+
`);
|
|
21124
|
+
}
|
|
21125
|
+
function buildNat66Removal() {
|
|
21126
|
+
return `table ${NAT66_TABLE} {}
|
|
21127
|
+
delete table ${NAT66_TABLE}
|
|
21128
|
+
`;
|
|
21169
21129
|
}
|
|
21170
|
-
|
|
21171
|
-
|
|
21172
|
-
|
|
21130
|
+
|
|
21131
|
+
// packages/daemon/src/host-net/net/ipv6-plan.ts
|
|
21132
|
+
function parseIpv6Setting(text) {
|
|
21133
|
+
if (text === "auto" || text === "off") {
|
|
21134
|
+
return { kind: text };
|
|
21135
|
+
}
|
|
21136
|
+
const prefix = parsePrefix64(text);
|
|
21137
|
+
if (prefix === null) {
|
|
21138
|
+
throw new Error(`IMP_SUBNET6 must be auto, off or an IPv6 /64, got ${text}`);
|
|
21139
|
+
}
|
|
21140
|
+
return { kind: "routed", prefix };
|
|
21173
21141
|
}
|
|
21174
|
-
function
|
|
21175
|
-
const
|
|
21176
|
-
|
|
21177
|
-
|
|
21142
|
+
async function resolveIpv6Plan(setting, deps) {
|
|
21143
|
+
const stopIpv6 = async (reason) => {
|
|
21144
|
+
await removeNat66(deps);
|
|
21145
|
+
getImpdLogger("ipv6").info("off ({reason})", { reason });
|
|
21146
|
+
return null;
|
|
21147
|
+
};
|
|
21148
|
+
if (setting.kind === "off") {
|
|
21149
|
+
return stopIpv6("IMP_SUBNET6=off");
|
|
21178
21150
|
}
|
|
21179
|
-
const
|
|
21180
|
-
if (
|
|
21181
|
-
return
|
|
21151
|
+
const uplink = await deps.readDefaultRoute();
|
|
21152
|
+
if (setting.kind === "auto" && uplink === null) {
|
|
21153
|
+
return stopIpv6("IMP_SUBNET6=auto, and the container has no IPv6 default route");
|
|
21182
21154
|
}
|
|
21183
|
-
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
|
|
21188
|
-
|
|
21155
|
+
const missing = await deps.checkHostRules();
|
|
21156
|
+
if (missing !== null) {
|
|
21157
|
+
return stopIpv6(`the host's IPv6 rules are not in place: ${missing}`);
|
|
21158
|
+
}
|
|
21159
|
+
if (setting.kind === "routed") {
|
|
21160
|
+
await removeNat66(deps);
|
|
21161
|
+
getImpdLogger("ipv6").info("{text}, routed{route}", {
|
|
21162
|
+
text: setting.prefix.text,
|
|
21163
|
+
route: uplink === null ? "; the container has no IPv6 default route" : ` via ${uplink}`
|
|
21164
|
+
});
|
|
21165
|
+
return { prefix: setting.prefix, nat66: false, uplink };
|
|
21166
|
+
}
|
|
21167
|
+
const prefix = deps.readUlaPrefix();
|
|
21168
|
+
try {
|
|
21169
|
+
await deps.runNft(buildNat66Ruleset(prefix.text, uplink ?? ""));
|
|
21170
|
+
} catch (error) {
|
|
21171
|
+
return stopIpv6(`NAT66: ${getErrorMessage(error)}`);
|
|
21172
|
+
}
|
|
21173
|
+
getImpdLogger("ipv6").info("{text}, NAT66 out of {uplink}", {
|
|
21174
|
+
text: prefix.text,
|
|
21175
|
+
uplink: uplink ?? ""
|
|
21176
|
+
});
|
|
21177
|
+
return { prefix, nat66: true, uplink };
|
|
21178
|
+
}
|
|
21179
|
+
async function removeNat66(deps) {
|
|
21180
|
+
try {
|
|
21181
|
+
await deps.runNft(buildNat66Removal());
|
|
21182
|
+
} catch {}
|
|
21183
|
+
}
|
|
21184
|
+
var HOST_RULES6 = [
|
|
21185
|
+
["filter", "-A INPUT -i imp+ -j DROP"],
|
|
21186
|
+
["filter", "-A FORWARD -i imp+ -o imp+ -j DROP"],
|
|
21187
|
+
["filter", "-A FORWARD -o imp+ -j DROP"],
|
|
21188
|
+
["filter", "-A FORWARD -i imp+ -j DROP"],
|
|
21189
|
+
["raw", "-A PREROUTING -i imp+ -m rpfilter --invert -j DROP"]
|
|
21190
|
+
];
|
|
21191
|
+
var HOST_SYSCTLS6 = [
|
|
21192
|
+
["default/accept_ra", "0"],
|
|
21193
|
+
["default/accept_redirects", "0"],
|
|
21194
|
+
["all/forwarding", "1"]
|
|
21195
|
+
];
|
|
21196
|
+
async function checkHostRules6(procSys = "/proc/sys/net/ipv6/conf", run = runCommand) {
|
|
21197
|
+
for (const table of ["filter", "raw"]) {
|
|
21198
|
+
const result = await run(["ip6tables", "-w", "-t", table, "-S"]);
|
|
21199
|
+
const rules = new Set(result.stdout.split(`
|
|
21200
|
+
`).map((line) => line.trim()));
|
|
21201
|
+
const missing = HOST_RULES6.find(([inTable, rule]) => inTable === table && !rules.has(rule));
|
|
21202
|
+
if (result.exitCode !== 0) {
|
|
21203
|
+
return `ip6tables -t ${table}: ${result.stderr.trim()}`;
|
|
21204
|
+
}
|
|
21205
|
+
if (missing !== undefined) {
|
|
21206
|
+
return `no ip6tables -t ${table} ${missing[1]}`;
|
|
21189
21207
|
}
|
|
21190
|
-
at = found + part.length;
|
|
21191
21208
|
}
|
|
21192
|
-
|
|
21209
|
+
for (const [key, wanted] of HOST_SYSCTLS6) {
|
|
21210
|
+
const value = readSysctl(join2(procSys, key));
|
|
21211
|
+
if (value !== wanted) {
|
|
21212
|
+
return `net.ipv6.conf.${key.replace("/", ".")} is ${value ?? "missing"}, not ${wanted}`;
|
|
21213
|
+
}
|
|
21214
|
+
}
|
|
21215
|
+
return null;
|
|
21193
21216
|
}
|
|
21194
|
-
|
|
21195
|
-
|
|
21196
|
-
|
|
21197
|
-
|
|
21198
|
-
|
|
21199
|
-
|
|
21200
|
-
|
|
21201
|
-
|
|
21202
|
-
|
|
21203
|
-
|
|
21204
|
-
|
|
21205
|
-
|
|
21217
|
+
function readSysctl(path) {
|
|
21218
|
+
try {
|
|
21219
|
+
return readFileSync2(path, "utf8").trim();
|
|
21220
|
+
} catch {
|
|
21221
|
+
return null;
|
|
21222
|
+
}
|
|
21223
|
+
}
|
|
21224
|
+
function readOrCreateUlaPrefix(path) {
|
|
21225
|
+
if (existsSync2(path)) {
|
|
21226
|
+
const prefix = parsePrefix64(readFileSync2(path, "utf8").trim());
|
|
21227
|
+
if (prefix !== null) {
|
|
21228
|
+
return prefix;
|
|
21229
|
+
}
|
|
21230
|
+
}
|
|
21231
|
+
const prefix = buildUlaPrefix(randomBytes(5));
|
|
21232
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
21233
|
+
writeFileSync(path, `${prefix.text}
|
|
21206
21234
|
`);
|
|
21235
|
+
return prefix;
|
|
21207
21236
|
}
|
|
21208
|
-
// packages/api/src/protocols/mcp-path.ts
|
|
21209
|
-
var MCP_PATH = "/mcp";
|
|
21210
|
-
// packages/api/src/protocols/move-receive-path.ts
|
|
21211
|
-
var MOVE_RECEIVE_PATH = "/move/receive";
|
|
21212
|
-
// packages/api/src/contract/move-peer-contract.ts
|
|
21213
|
-
import { oc as oc2 } from "@orpc/contract";
|
|
21214
|
-
|
|
21215
|
-
// packages/api/src/schemas/move-peer-schema.ts
|
|
21216
|
-
import * as z46 from "zod";
|
|
21217
|
-
var SystemDriveSchema = z46.string().regex(/^[\da-f]{64}$/v);
|
|
21218
|
-
var MoveOfferSchema = z46.object({
|
|
21219
|
-
imageDigest: z46.string(),
|
|
21220
|
-
receivedImageDigest: z46.string().nullable().default(null),
|
|
21221
|
-
systemDrive: SystemDriveSchema.optional()
|
|
21222
|
-
});
|
|
21223
|
-
var MoveOfferReplySchema = z46.object({
|
|
21224
|
-
needsImage: z46.boolean(),
|
|
21225
|
-
needsSystemDrive: z46.boolean().default(false),
|
|
21226
|
-
storage: z46.enum(["xfs", "zfs"]),
|
|
21227
|
-
keepsMaxMemory: z46.boolean().default(false),
|
|
21228
|
-
keepsLeases: z46.boolean().default(false),
|
|
21229
|
-
keepsPublicEgress: z46.boolean().default(false),
|
|
21230
|
-
keepsBackupOff: z46.boolean().default(false)
|
|
21231
|
-
});
|
|
21232
|
-
var MoveCommitReplySchema = z46.object({ isCommitted: z46.boolean() });
|
|
21233
21237
|
|
|
21234
|
-
// packages/
|
|
21235
|
-
|
|
21236
|
-
|
|
21237
|
-
|
|
21238
|
-
|
|
21239
|
-
|
|
21240
|
-
|
|
21241
|
-
|
|
21242
|
-
|
|
21243
|
-
|
|
21244
|
-
|
|
21245
|
-
|
|
21246
|
-
|
|
21247
|
-
|
|
21248
|
-
|
|
21249
|
-
env: ["GH_TOKEN", "GITHUB_TOKEN"]
|
|
21250
|
-
},
|
|
21251
|
-
anthropic: {
|
|
21252
|
-
kind: "static",
|
|
21253
|
-
rules: [{ host: "api.anthropic.com", header: "x-api-key", scheme: "raw" }],
|
|
21254
|
-
env: ["ANTHROPIC_API_KEY"]
|
|
21255
|
-
},
|
|
21256
|
-
npm: {
|
|
21257
|
-
kind: "static",
|
|
21258
|
-
rules: [{ host: "registry.npmjs.org", header: "authorization", scheme: "bearer" }],
|
|
21259
|
-
env: ["NPM_TOKEN"]
|
|
21238
|
+
// packages/daemon/src/host-net/net/range-matcher.ts
|
|
21239
|
+
import { IPv4 as IPv44 } from "ipaddr.js";
|
|
21240
|
+
function buildRangeMatcher(ranges) {
|
|
21241
|
+
const ranges4 = [];
|
|
21242
|
+
const ranges6 = [];
|
|
21243
|
+
for (const range of ranges) {
|
|
21244
|
+
const cidr = parseCidr(range);
|
|
21245
|
+
if (cidr === null) {
|
|
21246
|
+
throw new Error(`not an address range: ${range}`);
|
|
21247
|
+
}
|
|
21248
|
+
if (cidr.address instanceof IPv44) {
|
|
21249
|
+
ranges4.push([cidr.address, cidr.bits]);
|
|
21250
|
+
} else {
|
|
21251
|
+
ranges6.push([cidr.address, cidr.bits]);
|
|
21252
|
+
}
|
|
21260
21253
|
}
|
|
21261
|
-
|
|
21262
|
-
|
|
21263
|
-
|
|
21264
|
-
|
|
21265
|
-
|
|
21266
|
-
|
|
21254
|
+
const isIn4 = (ip) => ranges4.some(([network, bits]) => ip.match(network, bits));
|
|
21255
|
+
const isIn6 = (ip) => ranges6.some(([network, bits]) => ip.match(network, bits));
|
|
21256
|
+
return (address) => {
|
|
21257
|
+
const ip = parseAddress(address);
|
|
21258
|
+
if (ip === null) {
|
|
21259
|
+
return false;
|
|
21260
|
+
}
|
|
21261
|
+
if (ip instanceof IPv44) {
|
|
21262
|
+
return isIn4(ip);
|
|
21263
|
+
}
|
|
21264
|
+
return isIn6(ip) || ip.isIPv4MappedAddress() && isIn4(ip.toIPv4Address());
|
|
21265
|
+
};
|
|
21267
21266
|
}
|
|
21267
|
+
|
|
21268
|
+
// packages/daemon/src/identity/auth/parse-tailnet-rules.ts
|
|
21269
|
+
import * as z48 from "zod";
|
|
21270
|
+
|
|
21268
21271
|
// packages/daemon/src/identity/auth/tailnet-identity.ts
|
|
21269
21272
|
import * as z47 from "zod";
|
|
21270
21273
|
|
|
@@ -21702,6 +21705,8 @@ var EnvSchema = z53.object({
|
|
|
21702
21705
|
IMP_EGRESS_DENY: z53.string().default(""),
|
|
21703
21706
|
IMP_HOST_ADDRESSES: z53.string().default(""),
|
|
21704
21707
|
IMP_RAM_BUDGET_MIB: CountSchema3.default(16384),
|
|
21708
|
+
IMP_PRIORITY_RAM_MIB: CountSchema3.optional(),
|
|
21709
|
+
IMP_PRIORITY_IMPS: z53.string().transform(splitList).pipe(z53.array(ImpPatternSchema)).optional(),
|
|
21705
21710
|
IMP_IDLE_TIMEOUT_S: CountSchema3.default(60),
|
|
21706
21711
|
IMP_IDLE_CPU_PERCENT: z53.coerce.number().nonnegative().default(10),
|
|
21707
21712
|
IMP_MEMORY_SHRINK_AFTER_S: CountSchema3.default(60),
|
|
@@ -21866,6 +21871,7 @@ function loadConfig(env) {
|
|
|
21866
21871
|
hostAddresses,
|
|
21867
21872
|
brokerTestUpstreams: parsed.IMP_BROKER_TEST_UPSTREAMS ?? null,
|
|
21868
21873
|
ramBudgetMib: parsed.IMP_RAM_BUDGET_MIB,
|
|
21874
|
+
priorityRam: parsePriorityRam(parsed),
|
|
21869
21875
|
idleTimeoutS: parsed.IMP_IDLE_TIMEOUT_S,
|
|
21870
21876
|
idleCpuPercent: parsed.IMP_IDLE_CPU_PERCENT,
|
|
21871
21877
|
memoryShrinkAfterMs: parsed.IMP_MEMORY_SHRINK_AFTER_S * 1000,
|
|
@@ -21922,6 +21928,20 @@ function loadConfig(env) {
|
|
|
21922
21928
|
warnings: listHttpsWarnings(parsed)
|
|
21923
21929
|
};
|
|
21924
21930
|
}
|
|
21931
|
+
function parsePriorityRam(parsed) {
|
|
21932
|
+
const mib = parsed.IMP_PRIORITY_RAM_MIB;
|
|
21933
|
+
const patterns = parsed.IMP_PRIORITY_IMPS;
|
|
21934
|
+
if (mib === undefined && patterns === undefined) {
|
|
21935
|
+
return null;
|
|
21936
|
+
}
|
|
21937
|
+
if (mib === undefined || patterns === undefined) {
|
|
21938
|
+
throw new Error("IMP_PRIORITY_RAM_MIB and IMP_PRIORITY_IMPS go together; set both or neither");
|
|
21939
|
+
}
|
|
21940
|
+
if (mib >= parsed.IMP_RAM_BUDGET_MIB) {
|
|
21941
|
+
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`);
|
|
21942
|
+
}
|
|
21943
|
+
return { mib, patterns };
|
|
21944
|
+
}
|
|
21925
21945
|
function parseTestRegistry(parsed) {
|
|
21926
21946
|
const registry = parsed.IMP_BUILD_TEST_REGISTRY;
|
|
21927
21947
|
const caPath = parsed.IMP_BUILD_TEST_REGISTRY_CA;
|
|
@@ -21971,7 +21991,7 @@ import { join as join48 } from "path";
|
|
|
21971
21991
|
// packages/daemon/package.json
|
|
21972
21992
|
var package_default = {
|
|
21973
21993
|
name: "@impz/daemon",
|
|
21974
|
-
version: "6.
|
|
21994
|
+
version: "6.8.0",
|
|
21975
21995
|
private: true,
|
|
21976
21996
|
description: "impd: the imp host daemon",
|
|
21977
21997
|
type: "module",
|
|
@@ -25003,7 +25023,7 @@ class RamBudgetError extends ImpdError {
|
|
|
25003
25023
|
budgetMib: shortfall.budgetMib,
|
|
25004
25024
|
usedMib: shortfall.usedMib,
|
|
25005
25025
|
requestedMib: shortfall.requestedMib,
|
|
25006
|
-
neededMib: buildNeededMib(shortfall.usedMib, shortfall.requestedMib, shortfall.budgetMib),
|
|
25026
|
+
neededMib: buildNeededMib(shortfall.usedMib + (shortfall.kept?.mib ?? 0), shortfall.requestedMib, shortfall.budgetMib),
|
|
25007
25027
|
protected: [],
|
|
25008
25028
|
protectedHidden: shortfall.protected.length
|
|
25009
25029
|
}
|
|
@@ -25023,25 +25043,35 @@ function buildRamBudgetError(shortfall) {
|
|
|
25023
25043
|
const used = String(shortfall.usedMib);
|
|
25024
25044
|
const budget = String(shortfall.budgetMib);
|
|
25025
25045
|
const requested = String(shortfall.requestedMib);
|
|
25026
|
-
|
|
25046
|
+
const kept = formatKept(shortfall.kept);
|
|
25047
|
+
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");
|
|
25027
25048
|
}
|
|
25028
25049
|
function buildOrdinaryBudgetError(shortfall) {
|
|
25029
25050
|
const used = String(shortfall.usedMib);
|
|
25030
25051
|
const budget = String(shortfall.budgetMib);
|
|
25031
25052
|
const requested = String(shortfall.requestedMib);
|
|
25032
|
-
|
|
25053
|
+
const kept = formatKept(shortfall.kept);
|
|
25054
|
+
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");
|
|
25033
25055
|
}
|
|
25034
25056
|
function buildHugePoolError(shortfall) {
|
|
25035
25057
|
const committed = String(shortfall.committedMib);
|
|
25036
25058
|
const total = String(shortfall.totalMib);
|
|
25037
25059
|
const requested = String(shortfall.requestedMib);
|
|
25038
|
-
|
|
25060
|
+
const kept = formatKept(shortfall.kept);
|
|
25061
|
+
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`, {
|
|
25039
25062
|
budgetMib: shortfall.totalMib,
|
|
25040
25063
|
usedMib: shortfall.committedMib,
|
|
25041
25064
|
requestedMib: shortfall.requestedMib,
|
|
25042
|
-
protected: []
|
|
25065
|
+
protected: [],
|
|
25066
|
+
...shortfall.kept !== undefined && { kept: shortfall.kept }
|
|
25043
25067
|
}, "pool");
|
|
25044
25068
|
}
|
|
25069
|
+
function formatKept(kept) {
|
|
25070
|
+
if (kept === undefined || kept.mib === 0) {
|
|
25071
|
+
return "";
|
|
25072
|
+
}
|
|
25073
|
+
return ` and ${String(kept.mib)} MiB kept for priority imps (${kept.patterns.join(", ")})`;
|
|
25074
|
+
}
|
|
25045
25075
|
function buildNeededMib(usedMib, requestedMib, budgetMib) {
|
|
25046
25076
|
return Math.max(0, usedMib + requestedMib - budgetMib);
|
|
25047
25077
|
}
|
|
@@ -30535,6 +30565,7 @@ async function readSystemInfo(deps) {
|
|
|
30535
30565
|
ramUsedMib: usage.usedMib,
|
|
30536
30566
|
ramReservedMib: usage.reservedMib,
|
|
30537
30567
|
ramCommittedMib: running.reduce((sum, imp) => sum + imp.maxMemoryMib, 0),
|
|
30568
|
+
ramPriorityKeptMib: usage.priorityKeptMib,
|
|
30538
30569
|
ramSleepingMib: sleeping.reduce((sum, imp) => sum + imp.memoryMib, 0),
|
|
30539
30570
|
awakeCount: running.length,
|
|
30540
30571
|
impCount: imps.length,
|
|
@@ -37697,6 +37728,61 @@ function buildBudgets(budgetMib, pool) {
|
|
|
37697
37728
|
};
|
|
37698
37729
|
}
|
|
37699
37730
|
|
|
37731
|
+
// packages/daemon/src/imp-ops/governor/build-usage-totals.ts
|
|
37732
|
+
function buildUsageTotals(input) {
|
|
37733
|
+
const reserved = new Map(input.reservations.map((reservation) => [reservation.id, reservation]));
|
|
37734
|
+
const byImp = new Map;
|
|
37735
|
+
let usedMib = input.lockedMib;
|
|
37736
|
+
let effectiveMib = input.lockedMib + input.headroomMib;
|
|
37737
|
+
let ordinaryUsedMib = input.lockedMib;
|
|
37738
|
+
let ordinaryEffectiveMib = input.lockedMib + input.headroomMib;
|
|
37739
|
+
const priority = { ramMib: 0, ordinaryMib: 0, poolMib: 0 };
|
|
37740
|
+
for (const [index, imp] of input.awake.entries()) {
|
|
37741
|
+
const reading = input.readings[index] ?? null;
|
|
37742
|
+
const measured = reading?.ramMib ?? 0;
|
|
37743
|
+
const effective = Math.max(measured, reserved.get(imp.id)?.mib ?? 0);
|
|
37744
|
+
const ordinary = imp.hugePagesMib > 0 ? Math.max(0, measured - (reading?.hugetlbMib ?? 0)) : effective;
|
|
37745
|
+
byImp.set(imp.id, effective);
|
|
37746
|
+
usedMib += measured;
|
|
37747
|
+
effectiveMib += effective;
|
|
37748
|
+
ordinaryUsedMib += imp.hugePagesMib > 0 ? ordinary : measured;
|
|
37749
|
+
ordinaryEffectiveMib += ordinary;
|
|
37750
|
+
if (imp.isPriority) {
|
|
37751
|
+
priority.ramMib += effective;
|
|
37752
|
+
priority.ordinaryMib += ordinary;
|
|
37753
|
+
priority.poolMib += imp.hugePagesMib;
|
|
37754
|
+
}
|
|
37755
|
+
}
|
|
37756
|
+
for (const reservation of input.reservations) {
|
|
37757
|
+
if (!byImp.has(reservation.id)) {
|
|
37758
|
+
const ordinary = reservation.isOnPool ? 0 : reservation.mib;
|
|
37759
|
+
effectiveMib += reservation.mib;
|
|
37760
|
+
ordinaryEffectiveMib += ordinary;
|
|
37761
|
+
if (reservation.isPriority) {
|
|
37762
|
+
priority.ramMib += reservation.mib;
|
|
37763
|
+
priority.ordinaryMib += ordinary;
|
|
37764
|
+
}
|
|
37765
|
+
}
|
|
37766
|
+
}
|
|
37767
|
+
for (const pending of input.pendingPool) {
|
|
37768
|
+
if (pending.isPriority && !byImp.has(pending.id)) {
|
|
37769
|
+
priority.poolMib += pending.mib;
|
|
37770
|
+
}
|
|
37771
|
+
}
|
|
37772
|
+
return {
|
|
37773
|
+
byImp,
|
|
37774
|
+
usedMib,
|
|
37775
|
+
effectiveMib,
|
|
37776
|
+
ordinaryUsedMib,
|
|
37777
|
+
ordinaryEffectiveMib,
|
|
37778
|
+
kept: {
|
|
37779
|
+
ramMib: Math.max(0, input.priorityMib - priority.ramMib),
|
|
37780
|
+
ordinaryMib: Math.max(0, input.priorityMib - priority.ordinaryMib),
|
|
37781
|
+
poolMib: Math.max(0, input.priorityMib - priority.poolMib)
|
|
37782
|
+
}
|
|
37783
|
+
};
|
|
37784
|
+
}
|
|
37785
|
+
|
|
37700
37786
|
// packages/daemon/src/imp-ops/governor/build-huge-pool-admission.ts
|
|
37701
37787
|
function buildHugePoolAdmission(input) {
|
|
37702
37788
|
const growthMib = input.awake.reduce((sum, guest) => sum + Math.max(0, guest.hugePagesMib - guest.heldMib), 0);
|
|
@@ -37704,21 +37790,22 @@ function buildHugePoolAdmission(input) {
|
|
|
37704
37790
|
const totalMib = input.pool?.totalMib ?? 0;
|
|
37705
37791
|
const freeMib = input.pool?.freeMib ?? 0;
|
|
37706
37792
|
const promisedMib = growthMib + pendingMib;
|
|
37707
|
-
|
|
37793
|
+
const wantedMib = promisedMib + input.keptMib + input.requestedMib;
|
|
37794
|
+
if (wantedMib <= freeMib) {
|
|
37708
37795
|
return { admitted: true };
|
|
37709
37796
|
}
|
|
37710
37797
|
return {
|
|
37711
37798
|
admitted: false,
|
|
37712
37799
|
totalMib,
|
|
37713
37800
|
committedMib: totalMib - freeMib + promisedMib,
|
|
37714
|
-
neededMib:
|
|
37801
|
+
neededMib: wantedMib - freeMib
|
|
37715
37802
|
};
|
|
37716
37803
|
}
|
|
37717
37804
|
|
|
37718
37805
|
// packages/daemon/src/imp-ops/governor/make-check-huge-pool.ts
|
|
37719
37806
|
function makeCheckHugePool(deps) {
|
|
37720
37807
|
const pendingHugePages = deps.pendingHugePages;
|
|
37721
|
-
return async (request, hugePagesMib) => {
|
|
37808
|
+
return async (request, hugePagesMib, kept) => {
|
|
37722
37809
|
const listed = await deps.listAwake();
|
|
37723
37810
|
const awake = listed.filter((imp) => imp.id !== request.id);
|
|
37724
37811
|
for (const imp of awake) {
|
|
@@ -37734,7 +37821,8 @@ function makeCheckHugePool(deps) {
|
|
|
37734
37821
|
heldMib: held[index] ?? 0
|
|
37735
37822
|
})),
|
|
37736
37823
|
pendingMib: [...pendingHugePages].filter(([id]) => id !== request.id).map(([, mib]) => mib),
|
|
37737
|
-
requestedMib: hugePagesMib
|
|
37824
|
+
requestedMib: hugePagesMib,
|
|
37825
|
+
keptMib: kept?.mib ?? 0
|
|
37738
37826
|
});
|
|
37739
37827
|
if (poolAdmission.admitted) {
|
|
37740
37828
|
return;
|
|
@@ -37750,7 +37838,8 @@ function makeCheckHugePool(deps) {
|
|
|
37750
37838
|
throw buildHugePoolError({
|
|
37751
37839
|
totalMib: poolAdmission.totalMib,
|
|
37752
37840
|
committedMib: poolAdmission.committedMib,
|
|
37753
|
-
requestedMib: hugePagesMib
|
|
37841
|
+
requestedMib: hugePagesMib,
|
|
37842
|
+
...kept !== null && { kept }
|
|
37754
37843
|
});
|
|
37755
37844
|
};
|
|
37756
37845
|
}
|
|
@@ -37775,6 +37864,17 @@ function pickShortfall(used, budgets, requestedMib) {
|
|
|
37775
37864
|
} : ram;
|
|
37776
37865
|
}
|
|
37777
37866
|
|
|
37867
|
+
// packages/daemon/src/imp-ops/governor/pick-effective-shortfall.ts
|
|
37868
|
+
function pickEffectiveShortfall(usage, budgets, requestedMib, isPriority) {
|
|
37869
|
+
const kept = isPriority ? { ramMib: 0, ordinaryMib: 0 } : usage.kept;
|
|
37870
|
+
const shortfall = pickShortfall({
|
|
37871
|
+
ramMib: usage.effectiveMib + kept.ramMib,
|
|
37872
|
+
ordinaryMib: usage.ordinaryEffectiveMib + kept.ordinaryMib
|
|
37873
|
+
}, budgets, requestedMib);
|
|
37874
|
+
const keptMib = shortfall.budget === "ordinary" ? kept.ordinaryMib : kept.ramMib;
|
|
37875
|
+
return { ...shortfall, usedMib: shortfall.usedMib - keptMib, keptMib };
|
|
37876
|
+
}
|
|
37877
|
+
|
|
37778
37878
|
// packages/daemon/src/imp-ops/governor/pick-sleep-victims.ts
|
|
37779
37879
|
function pickSleepVictims(candidates, needMib) {
|
|
37780
37880
|
if (needMib <= 0) {
|
|
@@ -37802,8 +37902,12 @@ function createRamGovernor(deps) {
|
|
|
37802
37902
|
const admission = createPrioritySemaphore(1);
|
|
37803
37903
|
const reservations = new Map;
|
|
37804
37904
|
const now = deps.now ?? Date.now;
|
|
37905
|
+
const priority = deps.priority ?? null;
|
|
37906
|
+
const isPriority = (name) => priority !== null && name !== null && isImpAllowed(priority.patterns, name);
|
|
37907
|
+
const buildKept = (mib) => priority === null || mib === 0 ? null : { mib, patterns: priority.patterns };
|
|
37805
37908
|
let stuckOver = false;
|
|
37806
37909
|
const pendingHugePages = new Map;
|
|
37910
|
+
const admittedPriority = new Set;
|
|
37807
37911
|
const emitDecision = (decision) => {
|
|
37808
37912
|
deps.events?.publish({
|
|
37809
37913
|
v: EVENT_VERSION,
|
|
@@ -37834,6 +37938,11 @@ function createRamGovernor(deps) {
|
|
|
37834
37938
|
if ((request.hugePagesMib ?? 0) > 0) {
|
|
37835
37939
|
pendingHugePages.set(request.id, request.hugePagesMib ?? 0);
|
|
37836
37940
|
}
|
|
37941
|
+
if (isPriority(request.name)) {
|
|
37942
|
+
admittedPriority.add(request.id);
|
|
37943
|
+
} else {
|
|
37944
|
+
admittedPriority.delete(request.id);
|
|
37945
|
+
}
|
|
37837
37946
|
emitAdmission(request, "admitted", usedMib);
|
|
37838
37947
|
};
|
|
37839
37948
|
const readReservation = (id) => {
|
|
@@ -37864,45 +37973,27 @@ function createRamGovernor(deps) {
|
|
|
37864
37973
|
const readEffectiveUsage = async (excludeId, readVm = deps.readVmRam) => {
|
|
37865
37974
|
const listed = await deps.listAwake();
|
|
37866
37975
|
const awake = listed.filter((imp) => imp.id !== excludeId);
|
|
37867
|
-
const byImp = new Map;
|
|
37868
37976
|
const lockedMib = deps.readLockedMib?.() ?? 0;
|
|
37869
|
-
let usedMib = lockedMib;
|
|
37870
|
-
let effectiveMib = lockedMib;
|
|
37871
|
-
let ordinaryUsedMib = lockedMib;
|
|
37872
|
-
let ordinaryEffectiveMib = lockedMib;
|
|
37873
37977
|
const readings = await Promise.all(awake.map((imp) => readVm(imp.pid, imp.apiSocket)));
|
|
37874
|
-
|
|
37875
|
-
|
|
37876
|
-
|
|
37877
|
-
|
|
37878
|
-
|
|
37879
|
-
|
|
37880
|
-
|
|
37881
|
-
effectiveMib += effective;
|
|
37882
|
-
ordinaryUsedMib += imp.hugePagesMib > 0 ? ordinary : measured;
|
|
37883
|
-
ordinaryEffectiveMib += ordinary;
|
|
37884
|
-
}
|
|
37885
|
-
for (const id of reservations.keys()) {
|
|
37886
|
-
if (id !== excludeId && !byImp.has(id)) {
|
|
37887
|
-
effectiveMib += readReservation(id);
|
|
37888
|
-
if (!pendingHugePages.has(id)) {
|
|
37889
|
-
ordinaryEffectiveMib += readReservation(id);
|
|
37890
|
-
}
|
|
37891
|
-
}
|
|
37892
|
-
}
|
|
37978
|
+
const live = [...reservations.keys()].filter((id) => id !== excludeId).map((id) => ({
|
|
37979
|
+
id,
|
|
37980
|
+
mib: readReservation(id),
|
|
37981
|
+
isPriority: admittedPriority.has(id),
|
|
37982
|
+
isOnPool: pendingHugePages.has(id)
|
|
37983
|
+
}));
|
|
37984
|
+
const pendingPool = [...pendingHugePages].filter(([id]) => id !== excludeId).map(([id, mib]) => ({ id, mib, isPriority: admittedPriority.has(id) }));
|
|
37893
37985
|
const headroom = deps.readHeadroomMib?.(awake.map((imp) => imp.pid)) ?? Promise.resolve(0);
|
|
37894
37986
|
const headroomMib = await headroom;
|
|
37895
|
-
|
|
37896
|
-
|
|
37897
|
-
|
|
37898
|
-
|
|
37899
|
-
|
|
37900
|
-
|
|
37901
|
-
|
|
37902
|
-
|
|
37903
|
-
|
|
37904
|
-
|
|
37905
|
-
};
|
|
37987
|
+
const totals = buildUsageTotals({
|
|
37988
|
+
awake: awake.map((imp) => ({ ...imp, isPriority: isPriority(imp.name) })),
|
|
37989
|
+
readings,
|
|
37990
|
+
reservations: live,
|
|
37991
|
+
pendingPool,
|
|
37992
|
+
lockedMib,
|
|
37993
|
+
headroomMib,
|
|
37994
|
+
priorityMib: priority?.mib ?? 0
|
|
37995
|
+
});
|
|
37996
|
+
return { awake, headroomMib, ...totals };
|
|
37906
37997
|
};
|
|
37907
37998
|
const readFreeRoom = async (excludeId, findShortfall) => {
|
|
37908
37999
|
const usage = await readEffectiveUsage(excludeId);
|
|
@@ -37930,7 +38021,7 @@ function createRamGovernor(deps) {
|
|
|
37930
38021
|
const shortfall = options.findShortfall(usage);
|
|
37931
38022
|
const missingMib = shortfall.missingMib;
|
|
37932
38023
|
const isOrdinary = shortfall.budget === "ordinary";
|
|
37933
|
-
const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0);
|
|
38024
|
+
const sleepable = usage.awake.filter((imp) => !isOrdinary || imp.hugePagesMib === 0).filter((imp) => !options.sparesPriority || !isPriority(imp.name));
|
|
37934
38025
|
if (missingMib <= 0) {
|
|
37935
38026
|
return {
|
|
37936
38027
|
fits: true,
|
|
@@ -38015,20 +38106,23 @@ function createRamGovernor(deps) {
|
|
|
38015
38106
|
requestedMib: request.memoryMib
|
|
38016
38107
|
});
|
|
38017
38108
|
}
|
|
38109
|
+
const bound = await readEffectiveUsage(request.id, deps.readResidentRam);
|
|
38110
|
+
const isPriorityRequest = isPriority(request.name);
|
|
38018
38111
|
if (hugePagesMib > 0) {
|
|
38019
|
-
|
|
38112
|
+
const kept = isPriorityRequest ? null : buildKept(bound.kept.poolMib);
|
|
38113
|
+
await checkHugePool(request, hugePagesMib, kept);
|
|
38020
38114
|
}
|
|
38021
|
-
const
|
|
38022
|
-
if (
|
|
38115
|
+
const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib, isPriorityRequest);
|
|
38116
|
+
if (findReserveShortfall(bound).missingMib <= 0) {
|
|
38023
38117
|
recordAdmission(request, bound.effectiveMib);
|
|
38024
38118
|
return;
|
|
38025
38119
|
}
|
|
38026
|
-
const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib);
|
|
38027
38120
|
const room = request.maySleepImps === false ? await readFreeRoom(request.id, findReserveShortfall) : await makeRoom({
|
|
38028
38121
|
excludeId: request.id,
|
|
38029
38122
|
reason: `to make room for ${request.name ?? request.id}`,
|
|
38030
38123
|
whenShort: "giveUp",
|
|
38031
|
-
findShortfall: findReserveShortfall
|
|
38124
|
+
findShortfall: findReserveShortfall,
|
|
38125
|
+
sparesPriority: !isPriorityRequest
|
|
38032
38126
|
});
|
|
38033
38127
|
const diskFull = room.diskFull ? deps.readDiskFullError?.() ?? null : null;
|
|
38034
38128
|
if (!room.fits && diskFull !== null) {
|
|
@@ -38038,8 +38132,9 @@ function createRamGovernor(deps) {
|
|
|
38038
38132
|
const usage = await readEffectiveUsage(request.id);
|
|
38039
38133
|
const shortfall = findReserveShortfall(usage);
|
|
38040
38134
|
const isOrdinary = shortfall.budget === "ordinary";
|
|
38135
|
+
const kept = buildKept(shortfall.keptMib);
|
|
38041
38136
|
emitAdmission(request, "refused", shortfall.usedMib, {
|
|
38042
|
-
neededMib:
|
|
38137
|
+
neededMib: Math.max(0, shortfall.missingMib),
|
|
38043
38138
|
protectedCount: room.protected.length,
|
|
38044
38139
|
...isOrdinary && { trigger: ORDINARY_TRIGGER }
|
|
38045
38140
|
});
|
|
@@ -38047,7 +38142,8 @@ function createRamGovernor(deps) {
|
|
|
38047
38142
|
budgetMib: shortfall.budgetMib,
|
|
38048
38143
|
usedMib: shortfall.usedMib,
|
|
38049
38144
|
requestedMib: request.reserveMib,
|
|
38050
|
-
protected: room.protected
|
|
38145
|
+
protected: room.protected,
|
|
38146
|
+
...kept !== null && { kept }
|
|
38051
38147
|
};
|
|
38052
38148
|
throw isOrdinary ? buildOrdinaryBudgetError(refusal) : buildRamBudgetError(refusal);
|
|
38053
38149
|
}
|
|
@@ -38056,6 +38152,7 @@ function createRamGovernor(deps) {
|
|
|
38056
38152
|
release: (id) => {
|
|
38057
38153
|
reservations.delete(id);
|
|
38058
38154
|
pendingHugePages.delete(id);
|
|
38155
|
+
admittedPriority.delete(id);
|
|
38059
38156
|
},
|
|
38060
38157
|
admitGrow: (request) => admission.run("background", async () => {
|
|
38061
38158
|
const listed = await deps.listAwake();
|
|
@@ -38063,15 +38160,17 @@ function createRamGovernor(deps) {
|
|
|
38063
38160
|
return false;
|
|
38064
38161
|
}
|
|
38065
38162
|
const budgets = readBudgets();
|
|
38163
|
+
const findGrowShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.mib, isPriority(request.name));
|
|
38066
38164
|
const room = await makeRoom({
|
|
38067
38165
|
excludeId: null,
|
|
38068
38166
|
reason: `to grow ${request.name}`,
|
|
38069
38167
|
whenShort: "giveUp",
|
|
38070
|
-
findShortfall:
|
|
38071
|
-
protectedId: request.id
|
|
38168
|
+
findShortfall: findGrowShortfall,
|
|
38169
|
+
protectedId: request.id,
|
|
38170
|
+
sparesPriority: !isPriority(request.name)
|
|
38072
38171
|
});
|
|
38073
38172
|
const usage = await readEffectiveUsage(null);
|
|
38074
|
-
const shortfall =
|
|
38173
|
+
const shortfall = findGrowShortfall(usage);
|
|
38075
38174
|
const fits = room.fits && usage.awake.some((imp) => imp.id === request.id);
|
|
38076
38175
|
emitDecision({
|
|
38077
38176
|
decision: fits ? "admitted" : "refused",
|
|
@@ -38111,7 +38210,8 @@ function createRamGovernor(deps) {
|
|
|
38111
38210
|
return {
|
|
38112
38211
|
usedMib: usage.usedMib,
|
|
38113
38212
|
reservedMib: usage.effectiveMib - usage.usedMib - usage.headroomMib,
|
|
38114
|
-
headroomMib: usage.headroomMib
|
|
38213
|
+
headroomMib: usage.headroomMib,
|
|
38214
|
+
priorityKeptMib: usage.kept.ramMib
|
|
38115
38215
|
};
|
|
38116
38216
|
},
|
|
38117
38217
|
enforce: () => admission.run("background", async () => {
|
|
@@ -38120,7 +38220,8 @@ function createRamGovernor(deps) {
|
|
|
38120
38220
|
excludeId: null,
|
|
38121
38221
|
reason: ENFORCE_TRIGGER,
|
|
38122
38222
|
whenShort: "sleepAll",
|
|
38123
|
-
findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0)
|
|
38223
|
+
findShortfall: (usage) => pickShortfall({ ramMib: usage.usedMib, ordinaryMib: usage.ordinaryUsedMib }, budgets, 0),
|
|
38224
|
+
sparesPriority: false
|
|
38124
38225
|
});
|
|
38125
38226
|
const over = `over budget by ${String(room.missingMib)} MiB`;
|
|
38126
38227
|
if (room.fits) {
|
|
@@ -38138,9 +38239,6 @@ function createRamGovernor(deps) {
|
|
|
38138
38239
|
})
|
|
38139
38240
|
};
|
|
38140
38241
|
}
|
|
38141
|
-
function pickEffectiveShortfall(usage, budgets, requestedMib) {
|
|
38142
|
-
return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
|
|
38143
|
-
}
|
|
38144
38242
|
function toTaskPriority(request) {
|
|
38145
38243
|
return request.isBackground === true ? "background" : "foreground";
|
|
38146
38244
|
}
|
|
@@ -45638,6 +45736,7 @@ function createImpdImps(config, deps, parts) {
|
|
|
45638
45736
|
});
|
|
45639
45737
|
const governor = createRamGovernor({
|
|
45640
45738
|
budgetMib: config.ramBudgetMib,
|
|
45739
|
+
priority: config.priorityRam,
|
|
45641
45740
|
listAwake: async () => {
|
|
45642
45741
|
const listed = await listImps(deps.db);
|
|
45643
45742
|
return listed.flatMap((imp) => {
|