@impz/testing 6.7.0 → 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 +809 -713
- 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";
|
|
@@ -21105,166 +20670,603 @@ var authContract = {
|
|
|
21105
20670
|
logout: contractBase.meta(ACCESS.noSession).output(EmptySchema)
|
|
21106
20671
|
};
|
|
21107
20672
|
|
|
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";
|
|
20673
|
+
// packages/api/src/contract/session-contract.ts
|
|
20674
|
+
var sessionContract = { auth: authContract };
|
|
20675
|
+
// packages/api/src/contract/collect-procedure-access.ts
|
|
20676
|
+
import { isContractProcedure } from "@orpc/contract";
|
|
20677
|
+
import * as z45 from "zod";
|
|
20678
|
+
|
|
20679
|
+
// packages/api/src/schemas/procedure-access-schema.ts
|
|
20680
|
+
import * as z44 from "zod";
|
|
20681
|
+
var FieldsSchema = z44.array(z44.string()).readonly();
|
|
20682
|
+
var NoSessionAccessSchema = z44.object({ on: z44.literal("none") });
|
|
20683
|
+
var HostAccessSchema = z44.object({
|
|
20684
|
+
scope: ScopeSchema,
|
|
20685
|
+
on: z44.literal("host"),
|
|
20686
|
+
audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
|
|
20687
|
+
});
|
|
20688
|
+
var ImpAccessSchema = z44.object({
|
|
20689
|
+
scope: ScopeSchema,
|
|
20690
|
+
on: z44.literal("imp"),
|
|
20691
|
+
fields: FieldsSchema,
|
|
20692
|
+
noGrantable: z44.literal(true).optional(),
|
|
20693
|
+
audit: z44.literal(false).optional()
|
|
20694
|
+
});
|
|
20695
|
+
var GrantAccessSchema = z44.object({
|
|
20696
|
+
scope: ScopeSchema,
|
|
20697
|
+
on: z44.literal("grant"),
|
|
20698
|
+
fields: FieldsSchema,
|
|
20699
|
+
secretField: z44.string(),
|
|
20700
|
+
audit: z44.literal(false).optional()
|
|
20701
|
+
});
|
|
20702
|
+
var ImageAccessSchema = z44.object({
|
|
20703
|
+
scope: ScopeSchema,
|
|
20704
|
+
on: z44.literal("image"),
|
|
20705
|
+
held: z44.literal(true).optional(),
|
|
20706
|
+
audit: z44.union([z44.literal(false), z44.literal("refusals")]).optional()
|
|
20707
|
+
});
|
|
20708
|
+
var AnyAccessSchema = z44.object({
|
|
20709
|
+
scope: ScopeSchema,
|
|
20710
|
+
on: z44.literal("any"),
|
|
20711
|
+
audit: z44.literal(false).optional()
|
|
20712
|
+
});
|
|
20713
|
+
var ProcedureAccessSchema = z44.discriminatedUnion("on", [
|
|
20714
|
+
NoSessionAccessSchema,
|
|
20715
|
+
HostAccessSchema,
|
|
20716
|
+
ImpAccessSchema,
|
|
20717
|
+
GrantAccessSchema,
|
|
20718
|
+
ImageAccessSchema,
|
|
20719
|
+
AnyAccessSchema
|
|
20720
|
+
]);
|
|
20721
|
+
|
|
20722
|
+
// packages/api/src/contract/collect-procedure-access.ts
|
|
20723
|
+
var MetaSchema = z45.object({ access: ProcedureAccessSchema });
|
|
20724
|
+
function collectProcedureAccess(router, prefix = "") {
|
|
20725
|
+
return Object.entries(router).flatMap(([key, value]) => {
|
|
20726
|
+
const path = `${prefix}${key}`;
|
|
20727
|
+
if (isContractProcedure(value)) {
|
|
20728
|
+
const meta = value["~orpc"].meta;
|
|
20729
|
+
const parsed = MetaSchema.safeParse(meta);
|
|
20730
|
+
return [{ path, access: parsed.success ? parsed.data.access : null }];
|
|
20731
|
+
}
|
|
20732
|
+
return typeof value === "object" && value !== null ? collectProcedureAccess(value, `${path}.`) : [];
|
|
20733
|
+
});
|
|
20734
|
+
}
|
|
20735
|
+
// packages/api/src/rules/is-imp-allowed.ts
|
|
20736
|
+
function isImpAllowed(patterns, name) {
|
|
20737
|
+
return patterns === null || patterns.some((pattern) => isPatternMatch(pattern, name));
|
|
20738
|
+
}
|
|
20739
|
+
function isPatternMatch(pattern, name) {
|
|
20740
|
+
const [first = "", ...rest] = pattern.split("*");
|
|
20741
|
+
if (rest.length === 0) {
|
|
20742
|
+
return name === first;
|
|
20743
|
+
}
|
|
20744
|
+
const last = rest.pop() ?? "";
|
|
20745
|
+
if (!name.startsWith(first) || !name.endsWith(last) || name.length < first.length + last.length) {
|
|
20746
|
+
return false;
|
|
20747
|
+
}
|
|
20748
|
+
let at = first.length;
|
|
20749
|
+
const end = name.length - last.length;
|
|
20750
|
+
for (const part of rest) {
|
|
20751
|
+
const found = name.indexOf(part, at);
|
|
20752
|
+
if (found === -1 || found + part.length > end) {
|
|
20753
|
+
return false;
|
|
20754
|
+
}
|
|
20755
|
+
at = found + part.length;
|
|
20756
|
+
}
|
|
20757
|
+
return true;
|
|
20758
|
+
}
|
|
20759
|
+
// packages/api/src/protocols/login-shell.ts
|
|
20760
|
+
var CONSOLE_SHELL = buildConsoleShell("/etc/passwd", ["/bin/bash", "/bin/sh"]);
|
|
20761
|
+
function buildConsoleShell(passwdPath, fallbacks) {
|
|
20762
|
+
return [
|
|
20763
|
+
"uid=$(id -u 2>/dev/null) || uid=0",
|
|
20764
|
+
"shell=",
|
|
20765
|
+
"while IFS=: read -r _ _ id _ _ _ login; do",
|
|
20766
|
+
' if [ "$id" = "$uid" ]; then shell=$login; break; fi',
|
|
20767
|
+
`done < ${passwdPath}`,
|
|
20768
|
+
...fallbacks.map((fallback) => `[ -x "$shell" ] || shell=${fallback}`),
|
|
20769
|
+
'exec "$shell" -l'
|
|
20770
|
+
].join(`
|
|
20771
|
+
`);
|
|
20772
|
+
}
|
|
20773
|
+
// packages/api/src/protocols/mcp-path.ts
|
|
20774
|
+
var MCP_PATH = "/mcp";
|
|
20775
|
+
// packages/api/src/protocols/move-receive-path.ts
|
|
20776
|
+
var MOVE_RECEIVE_PATH = "/move/receive";
|
|
20777
|
+
// packages/api/src/contract/move-peer-contract.ts
|
|
20778
|
+
import { oc as oc2 } from "@orpc/contract";
|
|
20779
|
+
|
|
20780
|
+
// packages/api/src/schemas/move-peer-schema.ts
|
|
20781
|
+
import * as z46 from "zod";
|
|
20782
|
+
var SystemDriveSchema = z46.string().regex(/^[\da-f]{64}$/v);
|
|
20783
|
+
var MoveOfferSchema = z46.object({
|
|
20784
|
+
imageDigest: z46.string(),
|
|
20785
|
+
receivedImageDigest: z46.string().nullable().default(null),
|
|
20786
|
+
systemDrive: SystemDriveSchema.optional()
|
|
20787
|
+
});
|
|
20788
|
+
var MoveOfferReplySchema = z46.object({
|
|
20789
|
+
needsImage: z46.boolean(),
|
|
20790
|
+
needsSystemDrive: z46.boolean().default(false),
|
|
20791
|
+
storage: z46.enum(["xfs", "zfs"]),
|
|
20792
|
+
keepsMaxMemory: z46.boolean().default(false),
|
|
20793
|
+
keepsLeases: z46.boolean().default(false),
|
|
20794
|
+
keepsPublicEgress: z46.boolean().default(false),
|
|
20795
|
+
keepsBackupOff: z46.boolean().default(false)
|
|
20796
|
+
});
|
|
20797
|
+
var MoveCommitReplySchema = z46.object({ isCommitted: z46.boolean() });
|
|
20798
|
+
|
|
20799
|
+
// packages/api/src/contract/move-peer-contract.ts
|
|
20800
|
+
var movePeerContract = {
|
|
20801
|
+
offer: oc2.route({ method: "POST", path: "/move/offer" }).input(MoveOfferSchema).output(MoveOfferReplySchema),
|
|
20802
|
+
commit: oc2.route({ method: "POST", path: "/move/commit" }).output(MoveCommitReplySchema),
|
|
20803
|
+
abort: oc2.route({ method: "POST", path: "/move/abort" }).output(MoveCommitReplySchema)
|
|
20804
|
+
};
|
|
20805
|
+
// packages/api/src/schemas/secret-presets.ts
|
|
20806
|
+
var SECRET_PRESETS = {
|
|
20807
|
+
github: {
|
|
20808
|
+
kind: "static",
|
|
20809
|
+
rules: [
|
|
20810
|
+
{ host: "github.com", header: "authorization", scheme: "basic", user: "x-access-token" },
|
|
20811
|
+
{ host: "api.github.com", header: "authorization", scheme: "bearer" },
|
|
20812
|
+
{ host: "uploads.github.com", header: "authorization", scheme: "bearer" }
|
|
20813
|
+
],
|
|
20814
|
+
env: ["GH_TOKEN", "GITHUB_TOKEN"]
|
|
20815
|
+
},
|
|
20816
|
+
anthropic: {
|
|
20817
|
+
kind: "static",
|
|
20818
|
+
rules: [{ host: "api.anthropic.com", header: "x-api-key", scheme: "raw" }],
|
|
20819
|
+
env: ["ANTHROPIC_API_KEY"]
|
|
20820
|
+
},
|
|
20821
|
+
npm: {
|
|
20822
|
+
kind: "static",
|
|
20823
|
+
rules: [{ host: "registry.npmjs.org", header: "authorization", scheme: "bearer" }],
|
|
20824
|
+
env: ["NPM_TOKEN"]
|
|
20825
|
+
}
|
|
20826
|
+
};
|
|
20827
|
+
// packages/api/src/rules/has-scope.ts
|
|
20828
|
+
function hasScope(granted, needed) {
|
|
20829
|
+
const grantedRank = ScopeSchema.options.indexOf(granted);
|
|
20830
|
+
const neededRank = ScopeSchema.options.indexOf(needed);
|
|
20831
|
+
return grantedRank !== -1 && neededRank !== -1 && grantedRank >= neededRank;
|
|
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
|
+
}
|
|
21113
21068
|
|
|
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
|
-
]);
|
|
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";
|
|
21156
21073
|
|
|
21157
|
-
// packages/
|
|
21158
|
-
|
|
21159
|
-
function
|
|
21160
|
-
return
|
|
21161
|
-
|
|
21162
|
-
|
|
21163
|
-
|
|
21164
|
-
|
|
21165
|
-
|
|
21166
|
-
|
|
21167
|
-
|
|
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 }
|
|
21168
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
|
+
`;
|
|
21169
21128
|
}
|
|
21170
|
-
|
|
21171
|
-
|
|
21172
|
-
|
|
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 };
|
|
21173
21140
|
}
|
|
21174
|
-
function
|
|
21175
|
-
const
|
|
21176
|
-
|
|
21177
|
-
|
|
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");
|
|
21178
21149
|
}
|
|
21179
|
-
const
|
|
21180
|
-
if (
|
|
21181
|
-
return
|
|
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");
|
|
21182
21153
|
}
|
|
21183
|
-
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
|
|
21188
|
-
|
|
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]}`;
|
|
21189
21206
|
}
|
|
21190
|
-
at = found + part.length;
|
|
21191
21207
|
}
|
|
21192
|
-
|
|
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;
|
|
21193
21215
|
}
|
|
21194
|
-
|
|
21195
|
-
|
|
21196
|
-
|
|
21197
|
-
|
|
21198
|
-
|
|
21199
|
-
|
|
21200
|
-
|
|
21201
|
-
|
|
21202
|
-
|
|
21203
|
-
|
|
21204
|
-
|
|
21205
|
-
|
|
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}
|
|
21206
21233
|
`);
|
|
21234
|
+
return prefix;
|
|
21207
21235
|
}
|
|
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
21236
|
|
|
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"]
|
|
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
|
+
}
|
|
21260
21252
|
}
|
|
21261
|
-
|
|
21262
|
-
|
|
21263
|
-
|
|
21264
|
-
|
|
21265
|
-
|
|
21266
|
-
|
|
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
|
+
};
|
|
21267
21265
|
}
|
|
21266
|
+
|
|
21267
|
+
// packages/daemon/src/identity/auth/parse-tailnet-rules.ts
|
|
21268
|
+
import * as z48 from "zod";
|
|
21269
|
+
|
|
21268
21270
|
// packages/daemon/src/identity/auth/tailnet-identity.ts
|
|
21269
21271
|
import * as z47 from "zod";
|
|
21270
21272
|
|
|
@@ -21702,6 +21704,8 @@ var EnvSchema = z53.object({
|
|
|
21702
21704
|
IMP_EGRESS_DENY: z53.string().default(""),
|
|
21703
21705
|
IMP_HOST_ADDRESSES: z53.string().default(""),
|
|
21704
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(),
|
|
21705
21709
|
IMP_IDLE_TIMEOUT_S: CountSchema3.default(60),
|
|
21706
21710
|
IMP_IDLE_CPU_PERCENT: z53.coerce.number().nonnegative().default(10),
|
|
21707
21711
|
IMP_MEMORY_SHRINK_AFTER_S: CountSchema3.default(60),
|
|
@@ -21866,6 +21870,7 @@ function loadConfig(env) {
|
|
|
21866
21870
|
hostAddresses,
|
|
21867
21871
|
brokerTestUpstreams: parsed.IMP_BROKER_TEST_UPSTREAMS ?? null,
|
|
21868
21872
|
ramBudgetMib: parsed.IMP_RAM_BUDGET_MIB,
|
|
21873
|
+
priorityRam: parsePriorityRam(parsed),
|
|
21869
21874
|
idleTimeoutS: parsed.IMP_IDLE_TIMEOUT_S,
|
|
21870
21875
|
idleCpuPercent: parsed.IMP_IDLE_CPU_PERCENT,
|
|
21871
21876
|
memoryShrinkAfterMs: parsed.IMP_MEMORY_SHRINK_AFTER_S * 1000,
|
|
@@ -21922,6 +21927,20 @@ function loadConfig(env) {
|
|
|
21922
21927
|
warnings: listHttpsWarnings(parsed)
|
|
21923
21928
|
};
|
|
21924
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
|
+
}
|
|
21925
21944
|
function parseTestRegistry(parsed) {
|
|
21926
21945
|
const registry = parsed.IMP_BUILD_TEST_REGISTRY;
|
|
21927
21946
|
const caPath = parsed.IMP_BUILD_TEST_REGISTRY_CA;
|
|
@@ -21971,7 +21990,7 @@ import { join as join48 } from "path";
|
|
|
21971
21990
|
// packages/daemon/package.json
|
|
21972
21991
|
var package_default = {
|
|
21973
21992
|
name: "@impz/daemon",
|
|
21974
|
-
version: "6.7.
|
|
21993
|
+
version: "6.7.1",
|
|
21975
21994
|
private: true,
|
|
21976
21995
|
description: "impd: the imp host daemon",
|
|
21977
21996
|
type: "module",
|
|
@@ -25003,7 +25022,7 @@ class RamBudgetError extends ImpdError {
|
|
|
25003
25022
|
budgetMib: shortfall.budgetMib,
|
|
25004
25023
|
usedMib: shortfall.usedMib,
|
|
25005
25024
|
requestedMib: shortfall.requestedMib,
|
|
25006
|
-
neededMib: buildNeededMib(shortfall.usedMib, shortfall.requestedMib, shortfall.budgetMib),
|
|
25025
|
+
neededMib: buildNeededMib(shortfall.usedMib + (shortfall.kept?.mib ?? 0), shortfall.requestedMib, shortfall.budgetMib),
|
|
25007
25026
|
protected: [],
|
|
25008
25027
|
protectedHidden: shortfall.protected.length
|
|
25009
25028
|
}
|
|
@@ -25023,25 +25042,35 @@ function buildRamBudgetError(shortfall) {
|
|
|
25023
25042
|
const used = String(shortfall.usedMib);
|
|
25024
25043
|
const budget = String(shortfall.budgetMib);
|
|
25025
25044
|
const requested = String(shortfall.requestedMib);
|
|
25026
|
-
|
|
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");
|
|
25027
25047
|
}
|
|
25028
25048
|
function buildOrdinaryBudgetError(shortfall) {
|
|
25029
25049
|
const used = String(shortfall.usedMib);
|
|
25030
25050
|
const budget = String(shortfall.budgetMib);
|
|
25031
25051
|
const requested = String(shortfall.requestedMib);
|
|
25032
|
-
|
|
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");
|
|
25033
25054
|
}
|
|
25034
25055
|
function buildHugePoolError(shortfall) {
|
|
25035
25056
|
const committed = String(shortfall.committedMib);
|
|
25036
25057
|
const total = String(shortfall.totalMib);
|
|
25037
25058
|
const requested = String(shortfall.requestedMib);
|
|
25038
|
-
|
|
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`, {
|
|
25039
25061
|
budgetMib: shortfall.totalMib,
|
|
25040
25062
|
usedMib: shortfall.committedMib,
|
|
25041
25063
|
requestedMib: shortfall.requestedMib,
|
|
25042
|
-
protected: []
|
|
25064
|
+
protected: [],
|
|
25065
|
+
...shortfall.kept !== undefined && { kept: shortfall.kept }
|
|
25043
25066
|
}, "pool");
|
|
25044
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
|
+
}
|
|
25045
25074
|
function buildNeededMib(usedMib, requestedMib, budgetMib) {
|
|
25046
25075
|
return Math.max(0, usedMib + requestedMib - budgetMib);
|
|
25047
25076
|
}
|
|
@@ -37697,6 +37726,61 @@ function buildBudgets(budgetMib, pool) {
|
|
|
37697
37726
|
};
|
|
37698
37727
|
}
|
|
37699
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
|
+
|
|
37700
37784
|
// packages/daemon/src/imp-ops/governor/build-huge-pool-admission.ts
|
|
37701
37785
|
function buildHugePoolAdmission(input) {
|
|
37702
37786
|
const growthMib = input.awake.reduce((sum, guest) => sum + Math.max(0, guest.hugePagesMib - guest.heldMib), 0);
|
|
@@ -37704,21 +37788,22 @@ function buildHugePoolAdmission(input) {
|
|
|
37704
37788
|
const totalMib = input.pool?.totalMib ?? 0;
|
|
37705
37789
|
const freeMib = input.pool?.freeMib ?? 0;
|
|
37706
37790
|
const promisedMib = growthMib + pendingMib;
|
|
37707
|
-
|
|
37791
|
+
const wantedMib = promisedMib + input.keptMib + input.requestedMib;
|
|
37792
|
+
if (wantedMib <= freeMib) {
|
|
37708
37793
|
return { admitted: true };
|
|
37709
37794
|
}
|
|
37710
37795
|
return {
|
|
37711
37796
|
admitted: false,
|
|
37712
37797
|
totalMib,
|
|
37713
37798
|
committedMib: totalMib - freeMib + promisedMib,
|
|
37714
|
-
neededMib:
|
|
37799
|
+
neededMib: wantedMib - freeMib
|
|
37715
37800
|
};
|
|
37716
37801
|
}
|
|
37717
37802
|
|
|
37718
37803
|
// packages/daemon/src/imp-ops/governor/make-check-huge-pool.ts
|
|
37719
37804
|
function makeCheckHugePool(deps) {
|
|
37720
37805
|
const pendingHugePages = deps.pendingHugePages;
|
|
37721
|
-
return async (request, hugePagesMib) => {
|
|
37806
|
+
return async (request, hugePagesMib, kept) => {
|
|
37722
37807
|
const listed = await deps.listAwake();
|
|
37723
37808
|
const awake = listed.filter((imp) => imp.id !== request.id);
|
|
37724
37809
|
for (const imp of awake) {
|
|
@@ -37734,7 +37819,8 @@ function makeCheckHugePool(deps) {
|
|
|
37734
37819
|
heldMib: held[index] ?? 0
|
|
37735
37820
|
})),
|
|
37736
37821
|
pendingMib: [...pendingHugePages].filter(([id]) => id !== request.id).map(([, mib]) => mib),
|
|
37737
|
-
requestedMib: hugePagesMib
|
|
37822
|
+
requestedMib: hugePagesMib,
|
|
37823
|
+
keptMib: kept?.mib ?? 0
|
|
37738
37824
|
});
|
|
37739
37825
|
if (poolAdmission.admitted) {
|
|
37740
37826
|
return;
|
|
@@ -37750,7 +37836,8 @@ function makeCheckHugePool(deps) {
|
|
|
37750
37836
|
throw buildHugePoolError({
|
|
37751
37837
|
totalMib: poolAdmission.totalMib,
|
|
37752
37838
|
committedMib: poolAdmission.committedMib,
|
|
37753
|
-
requestedMib: hugePagesMib
|
|
37839
|
+
requestedMib: hugePagesMib,
|
|
37840
|
+
...kept !== null && { kept }
|
|
37754
37841
|
});
|
|
37755
37842
|
};
|
|
37756
37843
|
}
|
|
@@ -37775,6 +37862,17 @@ function pickShortfall(used, budgets, requestedMib) {
|
|
|
37775
37862
|
} : ram;
|
|
37776
37863
|
}
|
|
37777
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
|
+
|
|
37778
37876
|
// packages/daemon/src/imp-ops/governor/pick-sleep-victims.ts
|
|
37779
37877
|
function pickSleepVictims(candidates, needMib) {
|
|
37780
37878
|
if (needMib <= 0) {
|
|
@@ -37802,8 +37900,12 @@ function createRamGovernor(deps) {
|
|
|
37802
37900
|
const admission = createPrioritySemaphore(1);
|
|
37803
37901
|
const reservations = new Map;
|
|
37804
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 };
|
|
37805
37906
|
let stuckOver = false;
|
|
37806
37907
|
const pendingHugePages = new Map;
|
|
37908
|
+
const admittedPriority = new Set;
|
|
37807
37909
|
const emitDecision = (decision) => {
|
|
37808
37910
|
deps.events?.publish({
|
|
37809
37911
|
v: EVENT_VERSION,
|
|
@@ -37834,6 +37936,11 @@ function createRamGovernor(deps) {
|
|
|
37834
37936
|
if ((request.hugePagesMib ?? 0) > 0) {
|
|
37835
37937
|
pendingHugePages.set(request.id, request.hugePagesMib ?? 0);
|
|
37836
37938
|
}
|
|
37939
|
+
if (isPriority(request.name)) {
|
|
37940
|
+
admittedPriority.add(request.id);
|
|
37941
|
+
} else {
|
|
37942
|
+
admittedPriority.delete(request.id);
|
|
37943
|
+
}
|
|
37837
37944
|
emitAdmission(request, "admitted", usedMib);
|
|
37838
37945
|
};
|
|
37839
37946
|
const readReservation = (id) => {
|
|
@@ -37864,45 +37971,27 @@ function createRamGovernor(deps) {
|
|
|
37864
37971
|
const readEffectiveUsage = async (excludeId, readVm = deps.readVmRam) => {
|
|
37865
37972
|
const listed = await deps.listAwake();
|
|
37866
37973
|
const awake = listed.filter((imp) => imp.id !== excludeId);
|
|
37867
|
-
const byImp = new Map;
|
|
37868
37974
|
const lockedMib = deps.readLockedMib?.() ?? 0;
|
|
37869
|
-
let usedMib = lockedMib;
|
|
37870
|
-
let effectiveMib = lockedMib;
|
|
37871
|
-
let ordinaryUsedMib = lockedMib;
|
|
37872
|
-
let ordinaryEffectiveMib = lockedMib;
|
|
37873
37975
|
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
|
-
}
|
|
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) }));
|
|
37893
37983
|
const headroom = deps.readHeadroomMib?.(awake.map((imp) => imp.pid)) ?? Promise.resolve(0);
|
|
37894
37984
|
const headroomMib = await headroom;
|
|
37895
|
-
|
|
37896
|
-
|
|
37897
|
-
|
|
37898
|
-
|
|
37899
|
-
|
|
37900
|
-
|
|
37901
|
-
|
|
37902
|
-
|
|
37903
|
-
|
|
37904
|
-
|
|
37905
|
-
};
|
|
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 };
|
|
37906
37995
|
};
|
|
37907
37996
|
const readFreeRoom = async (excludeId, findShortfall) => {
|
|
37908
37997
|
const usage = await readEffectiveUsage(excludeId);
|
|
@@ -37930,7 +38019,7 @@ function createRamGovernor(deps) {
|
|
|
37930
38019
|
const shortfall = options.findShortfall(usage);
|
|
37931
38020
|
const missingMib = shortfall.missingMib;
|
|
37932
38021
|
const isOrdinary = shortfall.budget === "ordinary";
|
|
37933
|
-
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));
|
|
37934
38023
|
if (missingMib <= 0) {
|
|
37935
38024
|
return {
|
|
37936
38025
|
fits: true,
|
|
@@ -38015,20 +38104,23 @@ function createRamGovernor(deps) {
|
|
|
38015
38104
|
requestedMib: request.memoryMib
|
|
38016
38105
|
});
|
|
38017
38106
|
}
|
|
38107
|
+
const bound = await readEffectiveUsage(request.id, deps.readResidentRam);
|
|
38108
|
+
const isPriorityRequest = isPriority(request.name);
|
|
38018
38109
|
if (hugePagesMib > 0) {
|
|
38019
|
-
|
|
38110
|
+
const kept = isPriorityRequest ? null : buildKept(bound.kept.poolMib);
|
|
38111
|
+
await checkHugePool(request, hugePagesMib, kept);
|
|
38020
38112
|
}
|
|
38021
|
-
const
|
|
38022
|
-
if (
|
|
38113
|
+
const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib, isPriorityRequest);
|
|
38114
|
+
if (findReserveShortfall(bound).missingMib <= 0) {
|
|
38023
38115
|
recordAdmission(request, bound.effectiveMib);
|
|
38024
38116
|
return;
|
|
38025
38117
|
}
|
|
38026
|
-
const findReserveShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.reserveMib);
|
|
38027
38118
|
const room = request.maySleepImps === false ? await readFreeRoom(request.id, findReserveShortfall) : await makeRoom({
|
|
38028
38119
|
excludeId: request.id,
|
|
38029
38120
|
reason: `to make room for ${request.name ?? request.id}`,
|
|
38030
38121
|
whenShort: "giveUp",
|
|
38031
|
-
findShortfall: findReserveShortfall
|
|
38122
|
+
findShortfall: findReserveShortfall,
|
|
38123
|
+
sparesPriority: !isPriorityRequest
|
|
38032
38124
|
});
|
|
38033
38125
|
const diskFull = room.diskFull ? deps.readDiskFullError?.() ?? null : null;
|
|
38034
38126
|
if (!room.fits && diskFull !== null) {
|
|
@@ -38038,8 +38130,9 @@ function createRamGovernor(deps) {
|
|
|
38038
38130
|
const usage = await readEffectiveUsage(request.id);
|
|
38039
38131
|
const shortfall = findReserveShortfall(usage);
|
|
38040
38132
|
const isOrdinary = shortfall.budget === "ordinary";
|
|
38133
|
+
const kept = buildKept(shortfall.keptMib);
|
|
38041
38134
|
emitAdmission(request, "refused", shortfall.usedMib, {
|
|
38042
|
-
neededMib:
|
|
38135
|
+
neededMib: Math.max(0, shortfall.missingMib),
|
|
38043
38136
|
protectedCount: room.protected.length,
|
|
38044
38137
|
...isOrdinary && { trigger: ORDINARY_TRIGGER }
|
|
38045
38138
|
});
|
|
@@ -38047,7 +38140,8 @@ function createRamGovernor(deps) {
|
|
|
38047
38140
|
budgetMib: shortfall.budgetMib,
|
|
38048
38141
|
usedMib: shortfall.usedMib,
|
|
38049
38142
|
requestedMib: request.reserveMib,
|
|
38050
|
-
protected: room.protected
|
|
38143
|
+
protected: room.protected,
|
|
38144
|
+
...kept !== null && { kept }
|
|
38051
38145
|
};
|
|
38052
38146
|
throw isOrdinary ? buildOrdinaryBudgetError(refusal) : buildRamBudgetError(refusal);
|
|
38053
38147
|
}
|
|
@@ -38056,6 +38150,7 @@ function createRamGovernor(deps) {
|
|
|
38056
38150
|
release: (id) => {
|
|
38057
38151
|
reservations.delete(id);
|
|
38058
38152
|
pendingHugePages.delete(id);
|
|
38153
|
+
admittedPriority.delete(id);
|
|
38059
38154
|
},
|
|
38060
38155
|
admitGrow: (request) => admission.run("background", async () => {
|
|
38061
38156
|
const listed = await deps.listAwake();
|
|
@@ -38063,15 +38158,17 @@ function createRamGovernor(deps) {
|
|
|
38063
38158
|
return false;
|
|
38064
38159
|
}
|
|
38065
38160
|
const budgets = readBudgets();
|
|
38161
|
+
const findGrowShortfall = (usage) => pickEffectiveShortfall(usage, budgets, request.mib, isPriority(request.name));
|
|
38066
38162
|
const room = await makeRoom({
|
|
38067
38163
|
excludeId: null,
|
|
38068
38164
|
reason: `to grow ${request.name}`,
|
|
38069
38165
|
whenShort: "giveUp",
|
|
38070
|
-
findShortfall:
|
|
38071
|
-
protectedId: request.id
|
|
38166
|
+
findShortfall: findGrowShortfall,
|
|
38167
|
+
protectedId: request.id,
|
|
38168
|
+
sparesPriority: !isPriority(request.name)
|
|
38072
38169
|
});
|
|
38073
38170
|
const usage = await readEffectiveUsage(null);
|
|
38074
|
-
const shortfall =
|
|
38171
|
+
const shortfall = findGrowShortfall(usage);
|
|
38075
38172
|
const fits = room.fits && usage.awake.some((imp) => imp.id === request.id);
|
|
38076
38173
|
emitDecision({
|
|
38077
38174
|
decision: fits ? "admitted" : "refused",
|
|
@@ -38120,7 +38217,8 @@ function createRamGovernor(deps) {
|
|
|
38120
38217
|
excludeId: null,
|
|
38121
38218
|
reason: ENFORCE_TRIGGER,
|
|
38122
38219
|
whenShort: "sleepAll",
|
|
38123
|
-
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
|
|
38124
38222
|
});
|
|
38125
38223
|
const over = `over budget by ${String(room.missingMib)} MiB`;
|
|
38126
38224
|
if (room.fits) {
|
|
@@ -38138,9 +38236,6 @@ function createRamGovernor(deps) {
|
|
|
38138
38236
|
})
|
|
38139
38237
|
};
|
|
38140
38238
|
}
|
|
38141
|
-
function pickEffectiveShortfall(usage, budgets, requestedMib) {
|
|
38142
|
-
return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
|
|
38143
|
-
}
|
|
38144
38239
|
function toTaskPriority(request) {
|
|
38145
38240
|
return request.isBackground === true ? "background" : "foreground";
|
|
38146
38241
|
}
|
|
@@ -45638,6 +45733,7 @@ function createImpdImps(config, deps, parts) {
|
|
|
45638
45733
|
});
|
|
45639
45734
|
const governor = createRamGovernor({
|
|
45640
45735
|
budgetMib: config.ramBudgetMib,
|
|
45736
|
+
priority: config.priorityRam,
|
|
45641
45737
|
listAwake: async () => {
|
|
45642
45738
|
const listed = await listImps(deps.db);
|
|
45643
45739
|
return listed.flatMap((imp) => {
|