vmsan 0.1.0-alpha.25 → 0.1.0-alpha.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/_chunks/context.mjs +126 -35
- package/dist/_chunks/exec.mjs +1 -1
- package/dist/_chunks/vm-context.mjs +51 -5
- package/dist/index.d.mts +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -180,7 +180,7 @@ docs/ Documentation site (vmsan.dev)
|
|
|
180
180
|
### How it works
|
|
181
181
|
|
|
182
182
|
1. **vmsan** uses [Firecracker](https://github.com/firecracker-microvm/firecracker) to create lightweight microVMs with a jailer for security isolation
|
|
183
|
-
2. Each VM gets a TAP network device with its own `/30` subnet (`
|
|
183
|
+
2. Each VM gets a TAP network device with its own `/30` subnet (`198.19.{slot}.0/30`)
|
|
184
184
|
3. A Go-based **agent** runs inside the VM, exposing an HTTP API for command execution, file operations, and shell access
|
|
185
185
|
4. The CLI communicates with the agent over the host-guest network
|
|
186
186
|
|
package/dist/_chunks/context.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as vmsanPaths } from "./paths.mjs";
|
|
2
2
|
import { A as vmNotRunningError, B as invalidDomainPatternError, D as snapshotNotFoundError, F as invalidCidrOctetError, G as invalidNetworkPolicyError, H as invalidImageRefEmptyError, I as invalidCidrPrefixError, J as mutuallyExclusiveFlagsError, K as invalidPortError, L as invalidDiskSizeFormatError, P as invalidCidrFormatError, R as invalidDiskSizeRangeError, T as chrootNotFoundError, U as invalidImageRefTagError, W as invalidIntegerFlagError, X as portConflictError, Z as VmsanError, a as cloudflareNotConfiguredError, c as cloudflaredStartFailedError, d as missingBinaryError, f as noExt4RootfsError, h as noRootfsDirError, i as cloudflareNoZoneError, j as vmNotStoppedError, k as vmNotFoundError, m as noKernelError, n as cloudflareConfigNotFoundError, o as cloudflareTunnelNoIdError, p as noKernelDirError, q as invalidRuntimeError, r as cloudflareNoAccountsError, s as cloudflaredNotFoundError, u as SetupError, v as lockTimeoutError, x as defaultInterfaceNotFoundError, y as socketTimeoutError, z as invalidDomainError } from "./errors.mjs";
|
|
3
|
-
import { c as mkdirSecure, d as sleepSync, g as writeSecure, h as toError, n as waitForAgent, o as generateVmId, r as FileVmStateStore, u as safeKill } from "./vm-context.mjs";
|
|
3
|
+
import { S as vmLinkCidrFromIp, _ as SUPPORTED_VM_ADDRESS_BLOCKS, b as vmGuestIp, c as mkdirSecure, d as sleepSync, g as writeSecure, h as toError, n as waitForAgent, o as generateVmId, r as FileVmStateStore, u as safeKill, v as VM_SUBNET_MASK, x as vmHostIp, y as slotFromVmHostIp } from "./vm-context.mjs";
|
|
4
4
|
import { t as FirecrackerClient } from "./firecracker.mjs";
|
|
5
5
|
import { t as spawnTimeoutKiller } from "./timeout-killer.mjs";
|
|
6
6
|
import { t as AgentClient } from "./agent.mjs";
|
|
@@ -101,9 +101,9 @@ var NetworkManager = class NetworkManager {
|
|
|
101
101
|
this.config = {
|
|
102
102
|
slot,
|
|
103
103
|
tapDevice: `fhvm${slot}`,
|
|
104
|
-
hostIp:
|
|
105
|
-
guestIp:
|
|
106
|
-
subnetMask:
|
|
104
|
+
hostIp: vmHostIp(slot),
|
|
105
|
+
guestIp: vmGuestIp(slot),
|
|
106
|
+
subnetMask: VM_SUBNET_MASK,
|
|
107
107
|
macAddress: `AA:FC:00:00:00:${(slot + 1).toString(16).padStart(2, "0").toUpperCase()}`,
|
|
108
108
|
networkPolicy,
|
|
109
109
|
allowedDomains,
|
|
@@ -115,8 +115,8 @@ var NetworkManager = class NetworkManager {
|
|
|
115
115
|
skipDnat
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
|
-
static bootArgs(
|
|
119
|
-
return `console=ttyS0 reboot=k panic=1 pci=off ip
|
|
118
|
+
static bootArgs(config) {
|
|
119
|
+
return `console=ttyS0 reboot=k panic=1 pci=off ip=${config.guestIp}::${config.hostIp}:${config.subnetMask}::eth0:off:${DNS_RESOLVERS[0]}`;
|
|
120
120
|
}
|
|
121
121
|
static fromConfig(config) {
|
|
122
122
|
const mgr = Object.create(NetworkManager.prototype);
|
|
@@ -124,8 +124,12 @@ var NetworkManager = class NetworkManager {
|
|
|
124
124
|
return mgr;
|
|
125
125
|
}
|
|
126
126
|
static fromVmNetwork(network) {
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
let slot;
|
|
128
|
+
try {
|
|
129
|
+
slot = slotFromVmHostIp(network.hostIp);
|
|
130
|
+
} catch {
|
|
131
|
+
throw new Error(`invalid network slot derived from hostIp: ${network.hostIp}`);
|
|
132
|
+
}
|
|
129
133
|
return NetworkManager.fromConfig({
|
|
130
134
|
slot,
|
|
131
135
|
tapDevice: network.tapDevice,
|
|
@@ -149,8 +153,9 @@ var NetworkManager = class NetworkManager {
|
|
|
149
153
|
else sudo(args);
|
|
150
154
|
}
|
|
151
155
|
setupNamespace() {
|
|
152
|
-
const {
|
|
156
|
+
const { guestIp, netnsName } = this.config;
|
|
153
157
|
if (!netnsName) return;
|
|
158
|
+
const slot = this.config.slot;
|
|
154
159
|
const vethHost = `veth-h-${slot}`;
|
|
155
160
|
const vethGuest = `veth-g-${slot}`;
|
|
156
161
|
const transitHostIp = `10.200.${slot}.1`;
|
|
@@ -244,7 +249,7 @@ var NetworkManager = class NetworkManager {
|
|
|
244
249
|
"ip",
|
|
245
250
|
"route",
|
|
246
251
|
"add",
|
|
247
|
-
|
|
252
|
+
vmLinkCidrFromIp(guestIp),
|
|
248
253
|
"via",
|
|
249
254
|
transitGuestIp
|
|
250
255
|
]);
|
|
@@ -255,7 +260,7 @@ var NetworkManager = class NetworkManager {
|
|
|
255
260
|
]);
|
|
256
261
|
}
|
|
257
262
|
teardownNamespace() {
|
|
258
|
-
const {
|
|
263
|
+
const { guestIp, netnsName } = this.config;
|
|
259
264
|
if (!netnsName) return;
|
|
260
265
|
const tryRun = (args) => {
|
|
261
266
|
try {
|
|
@@ -268,7 +273,7 @@ var NetworkManager = class NetworkManager {
|
|
|
268
273
|
"ip",
|
|
269
274
|
"route",
|
|
270
275
|
"del",
|
|
271
|
-
|
|
276
|
+
vmLinkCidrFromIp(guestIp)
|
|
272
277
|
]);
|
|
273
278
|
tryRun([
|
|
274
279
|
"ip",
|
|
@@ -347,10 +352,45 @@ var NetworkManager = class NetworkManager {
|
|
|
347
352
|
}
|
|
348
353
|
}
|
|
349
354
|
setupRules() {
|
|
350
|
-
const { tapDevice,
|
|
355
|
+
const { tapDevice, guestIp, publishedPorts } = this.config;
|
|
351
356
|
const policy = effectivePolicy(this.config);
|
|
357
|
+
const vethGuest = this.config.netnsName ? `veth-g-${this.config.slot}` : void 0;
|
|
352
358
|
const fwd = this.nsRun.bind(this);
|
|
359
|
+
sudo([
|
|
360
|
+
"iptables",
|
|
361
|
+
"-I",
|
|
362
|
+
"OUTPUT",
|
|
363
|
+
"1",
|
|
364
|
+
"-d",
|
|
365
|
+
guestIp,
|
|
366
|
+
"-j",
|
|
367
|
+
"ACCEPT"
|
|
368
|
+
]);
|
|
369
|
+
sudo([
|
|
370
|
+
"iptables",
|
|
371
|
+
"-I",
|
|
372
|
+
"INPUT",
|
|
373
|
+
"1",
|
|
374
|
+
"-s",
|
|
375
|
+
guestIp,
|
|
376
|
+
"-j",
|
|
377
|
+
"ACCEPT"
|
|
378
|
+
]);
|
|
353
379
|
if (policy === "deny-all") {
|
|
380
|
+
if (vethGuest) fwd([
|
|
381
|
+
"iptables",
|
|
382
|
+
"-I",
|
|
383
|
+
"FORWARD",
|
|
384
|
+
"1",
|
|
385
|
+
"-i",
|
|
386
|
+
vethGuest,
|
|
387
|
+
"-o",
|
|
388
|
+
tapDevice,
|
|
389
|
+
"-d",
|
|
390
|
+
guestIp,
|
|
391
|
+
"-j",
|
|
392
|
+
"ACCEPT"
|
|
393
|
+
]);
|
|
354
394
|
fwd([
|
|
355
395
|
"iptables",
|
|
356
396
|
"-I",
|
|
@@ -546,14 +586,14 @@ var NetworkManager = class NetworkManager {
|
|
|
546
586
|
"DROP"
|
|
547
587
|
]);
|
|
548
588
|
}
|
|
549
|
-
fwd([
|
|
589
|
+
for (const vmAddressBlock of SUPPORTED_VM_ADDRESS_BLOCKS) fwd([
|
|
550
590
|
"iptables",
|
|
551
591
|
"-A",
|
|
552
592
|
"FORWARD",
|
|
553
593
|
"-i",
|
|
554
594
|
tapDevice,
|
|
555
595
|
"-d",
|
|
556
|
-
|
|
596
|
+
vmAddressBlock,
|
|
557
597
|
"-j",
|
|
558
598
|
"DROP"
|
|
559
599
|
]);
|
|
@@ -694,14 +734,14 @@ var NetworkManager = class NetworkManager {
|
|
|
694
734
|
"DROP"
|
|
695
735
|
]);
|
|
696
736
|
}
|
|
697
|
-
fwd([
|
|
737
|
+
for (const vmAddressBlock of SUPPORTED_VM_ADDRESS_BLOCKS) fwd([
|
|
698
738
|
"iptables",
|
|
699
739
|
"-A",
|
|
700
740
|
"FORWARD",
|
|
701
741
|
"-i",
|
|
702
742
|
tapDevice,
|
|
703
743
|
"-d",
|
|
704
|
-
|
|
744
|
+
vmAddressBlock,
|
|
705
745
|
"-j",
|
|
706
746
|
"DROP"
|
|
707
747
|
]);
|
|
@@ -761,6 +801,20 @@ var NetworkManager = class NetworkManager {
|
|
|
761
801
|
"ACCEPT"
|
|
762
802
|
]);
|
|
763
803
|
}
|
|
804
|
+
if (vethGuest) fwd([
|
|
805
|
+
"iptables",
|
|
806
|
+
"-I",
|
|
807
|
+
"FORWARD",
|
|
808
|
+
"1",
|
|
809
|
+
"-i",
|
|
810
|
+
vethGuest,
|
|
811
|
+
"-o",
|
|
812
|
+
tapDevice,
|
|
813
|
+
"-d",
|
|
814
|
+
guestIp,
|
|
815
|
+
"-j",
|
|
816
|
+
"ACCEPT"
|
|
817
|
+
]);
|
|
764
818
|
}
|
|
765
819
|
setupThrottle() {
|
|
766
820
|
const { tapDevice, bandwidthMbit } = this.config;
|
|
@@ -799,7 +853,8 @@ var NetworkManager = class NetworkManager {
|
|
|
799
853
|
}
|
|
800
854
|
}
|
|
801
855
|
teardownRules() {
|
|
802
|
-
const { tapDevice,
|
|
856
|
+
const { tapDevice, guestIp, publishedPorts, netnsName } = this.config;
|
|
857
|
+
const vethGuest = netnsName ? `veth-g-${this.config.slot}` : void 0;
|
|
803
858
|
let defaultIface;
|
|
804
859
|
try {
|
|
805
860
|
defaultIface = getDefaultInterface();
|
|
@@ -813,6 +868,24 @@ var NetworkManager = class NetworkManager {
|
|
|
813
868
|
consola.debug(`iptables host cleanup failed (${args.slice(0, 4).join(" ")}): ${toError(err).message}`);
|
|
814
869
|
}
|
|
815
870
|
};
|
|
871
|
+
tryRun([
|
|
872
|
+
"iptables",
|
|
873
|
+
"-D",
|
|
874
|
+
"OUTPUT",
|
|
875
|
+
"-d",
|
|
876
|
+
guestIp,
|
|
877
|
+
"-j",
|
|
878
|
+
"ACCEPT"
|
|
879
|
+
]);
|
|
880
|
+
tryRun([
|
|
881
|
+
"iptables",
|
|
882
|
+
"-D",
|
|
883
|
+
"INPUT",
|
|
884
|
+
"-s",
|
|
885
|
+
guestIp,
|
|
886
|
+
"-j",
|
|
887
|
+
"ACCEPT"
|
|
888
|
+
]);
|
|
816
889
|
const tryFwd = (args) => {
|
|
817
890
|
try {
|
|
818
891
|
this.nsRun(args);
|
|
@@ -992,14 +1065,14 @@ var NetworkManager = class NetworkManager {
|
|
|
992
1065
|
"DROP"
|
|
993
1066
|
]);
|
|
994
1067
|
}
|
|
995
|
-
tryFwd([
|
|
1068
|
+
for (const vmAddressBlock of SUPPORTED_VM_ADDRESS_BLOCKS) tryFwd([
|
|
996
1069
|
"iptables",
|
|
997
1070
|
"-D",
|
|
998
1071
|
"FORWARD",
|
|
999
1072
|
"-i",
|
|
1000
1073
|
tapDevice,
|
|
1001
1074
|
"-d",
|
|
1002
|
-
|
|
1075
|
+
vmAddressBlock,
|
|
1003
1076
|
"-j",
|
|
1004
1077
|
"DROP"
|
|
1005
1078
|
]);
|
|
@@ -1044,6 +1117,19 @@ var NetworkManager = class NetworkManager {
|
|
|
1044
1117
|
"DROP"
|
|
1045
1118
|
]);
|
|
1046
1119
|
}
|
|
1120
|
+
if (vethGuest) tryFwd([
|
|
1121
|
+
"iptables",
|
|
1122
|
+
"-D",
|
|
1123
|
+
"FORWARD",
|
|
1124
|
+
"-i",
|
|
1125
|
+
vethGuest,
|
|
1126
|
+
"-o",
|
|
1127
|
+
tapDevice,
|
|
1128
|
+
"-d",
|
|
1129
|
+
guestIp,
|
|
1130
|
+
"-j",
|
|
1131
|
+
"ACCEPT"
|
|
1132
|
+
]);
|
|
1047
1133
|
if (netnsName && defaultIface) {
|
|
1048
1134
|
const vethHost = `veth-h-${this.config.slot}`;
|
|
1049
1135
|
tryRun([
|
|
@@ -1284,6 +1370,7 @@ var Jailer = class {
|
|
|
1284
1370
|
}
|
|
1285
1371
|
const tmpMount = join(paths.rootDir, "tmp-mount");
|
|
1286
1372
|
mkdirSync(tmpMount, { recursive: true });
|
|
1373
|
+
let prepareError;
|
|
1287
1374
|
try {
|
|
1288
1375
|
execSync(`sudo mount -o loop "${paths.rootfsPath}" "${tmpMount}"`, { stdio: "pipe" });
|
|
1289
1376
|
execSync(`rm -f "${tmpMount}/etc/resolv.conf" && ln -s /proc/net/pnp "${tmpMount}/etc/resolv.conf"`, { stdio: "pipe" });
|
|
@@ -1321,7 +1408,8 @@ var Jailer = class {
|
|
|
1321
1408
|
}
|
|
1322
1409
|
}
|
|
1323
1410
|
execSync(`sudo umount "${tmpMount}"`, { stdio: "pipe" });
|
|
1324
|
-
} catch {
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
prepareError = error;
|
|
1325
1413
|
try {
|
|
1326
1414
|
execSync(`sudo umount "${tmpMount}" 2>/dev/null`, { stdio: "pipe" });
|
|
1327
1415
|
} catch {}
|
|
@@ -1329,6 +1417,7 @@ var Jailer = class {
|
|
|
1329
1417
|
try {
|
|
1330
1418
|
execSync(`rm -rf "${tmpMount}"`, { stdio: "pipe" });
|
|
1331
1419
|
} catch {}
|
|
1420
|
+
if (prepareError) throw prepareError;
|
|
1332
1421
|
if (config.snapshot) {
|
|
1333
1422
|
mkdirSync(paths.snapshotDir, { recursive: true });
|
|
1334
1423
|
copyFileSync(config.snapshot.snapshotFile, join(paths.snapshotDir, "snapshot_file"));
|
|
@@ -1685,18 +1774,11 @@ function buildImageRootfs(imageRef, cacheDir, minimal = false) {
|
|
|
1685
1774
|
const tarSizeOutput = execSync(`stat -c %s "${tmpTar}"`, { encoding: "utf-8" }).trim();
|
|
1686
1775
|
const tarMb = Number(tarSizeOutput) / 1024 / 1024;
|
|
1687
1776
|
const imageSizeMb = Math.max(1024, Math.ceil(tarMb + 512));
|
|
1688
|
-
|
|
1689
|
-
|
|
1777
|
+
const tmpExtract = join(cacheDir, "rootfs-extracted");
|
|
1778
|
+
mkdirSync(tmpExtract, { recursive: true });
|
|
1779
|
+
execSync(`tar -xf "${tmpTar}" -C "${tmpExtract}"`, { stdio: "pipe" });
|
|
1780
|
+
execSync(`mkfs.ext4 -q -d "${tmpExtract}" "${ext4Path}" "${imageSizeMb}M"`, { stdio: "pipe" });
|
|
1690
1781
|
execSync(`tune2fs -m 0 "${ext4Path}"`, { stdio: "pipe" });
|
|
1691
|
-
const tmpMount = join(cacheDir, "mnt");
|
|
1692
|
-
mkdirSync(tmpMount, { recursive: true });
|
|
1693
|
-
execSync(`mount -o loop "${ext4Path}" "${tmpMount}"`, { stdio: "pipe" });
|
|
1694
|
-
try {
|
|
1695
|
-
execSync(`tar -xf "${tmpTar}" -C "${tmpMount}"`, { stdio: "pipe" });
|
|
1696
|
-
} finally {
|
|
1697
|
-
execSync(`umount "${tmpMount}"`, { stdio: "pipe" });
|
|
1698
|
-
execSync(`rm -rf "${tmpMount}"`, { stdio: "pipe" });
|
|
1699
|
-
}
|
|
1700
1782
|
writeFileSync(join(cacheDir, "metadata.json"), JSON.stringify({
|
|
1701
1783
|
image: imageRef.full,
|
|
1702
1784
|
builtAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1706,10 +1788,19 @@ function buildImageRootfs(imageRef, cacheDir, minimal = false) {
|
|
|
1706
1788
|
} finally {
|
|
1707
1789
|
try {
|
|
1708
1790
|
execSync(`docker rm -f "${containerName}" 2>/dev/null`, { stdio: "pipe" });
|
|
1709
|
-
} catch {
|
|
1791
|
+
} catch (err) {
|
|
1792
|
+
consola.debug(`Failed to remove docker container ${containerName}: ${toError(err).message}`);
|
|
1793
|
+
}
|
|
1710
1794
|
try {
|
|
1711
1795
|
execSync(`rm -f "${tmpTar}"`, { stdio: "pipe" });
|
|
1712
|
-
} catch {
|
|
1796
|
+
} catch (err) {
|
|
1797
|
+
consola.debug(`Failed to remove temp tar ${tmpTar}: ${toError(err).message}`);
|
|
1798
|
+
}
|
|
1799
|
+
try {
|
|
1800
|
+
execSync(`rm -rf "${join(cacheDir, "rootfs-extracted")}"`, { stdio: "pipe" });
|
|
1801
|
+
} catch (err) {
|
|
1802
|
+
consola.debug(`Failed to remove extraction dir: ${toError(err).message}`);
|
|
1803
|
+
}
|
|
1713
1804
|
}
|
|
1714
1805
|
}
|
|
1715
1806
|
function resolveImageRootfs(imageRef, registryDir, minimal = false) {
|
|
@@ -2419,7 +2510,7 @@ var VMService = class {
|
|
|
2419
2510
|
}
|
|
2420
2511
|
async bootVm(socketPath, netCfg, vcpus, memMib) {
|
|
2421
2512
|
const vm = new FirecrackerClient(socketPath);
|
|
2422
|
-
const bootArgs = NetworkManager.bootArgs(netCfg
|
|
2513
|
+
const bootArgs = NetworkManager.bootArgs(netCfg);
|
|
2423
2514
|
this.logger.debug(`Boot args: ${bootArgs}`);
|
|
2424
2515
|
await vm.boot("kernel/vmlinux", bootArgs);
|
|
2425
2516
|
await vm.addDrive("rootfs", "rootfs/rootfs.ext4", true, false);
|
package/dist/_chunks/exec.mjs
CHANGED
|
@@ -13,7 +13,7 @@ function shellEscape(s) {
|
|
|
13
13
|
if (/^[a-zA-Z0-9._\-/=:@]+$/.test(s)) return s;
|
|
14
14
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
15
15
|
}
|
|
16
|
-
function parseEnvFlags(
|
|
16
|
+
function parseEnvFlags(_targetCommand) {
|
|
17
17
|
const env = {};
|
|
18
18
|
const argv = process.argv;
|
|
19
19
|
let positionalCount = 0;
|
|
@@ -4,6 +4,50 @@ import { execSync } from "node:child_process";
|
|
|
4
4
|
import { chmodSync, chownSync, existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import { stripAnsi } from "consola/utils";
|
|
7
|
+
const VM_NETWORK_FIRST_OCTET = 198;
|
|
8
|
+
const VM_NETWORK_SECOND_OCTET = 19;
|
|
9
|
+
const VM_SUBNET_MASK = "255.255.255.252";
|
|
10
|
+
const VM_NETWORK_PREFIX = `${VM_NETWORK_FIRST_OCTET}.${VM_NETWORK_SECOND_OCTET}`;
|
|
11
|
+
const SUPPORTED_VM_ADDRESS_BLOCKS = ["198.19.0.0/16", "172.16.0.0/16"];
|
|
12
|
+
function assertValidSlot(slot) {
|
|
13
|
+
if (!Number.isInteger(slot) || slot < 0 || slot > 254) throw new Error(`invalid VM network slot: ${slot}`);
|
|
14
|
+
}
|
|
15
|
+
function parseIpv4OrNull(ip) {
|
|
16
|
+
const octets = ip.split(".");
|
|
17
|
+
if (octets.length !== 4 || octets.some((part) => !/^\d+$/.test(part))) return null;
|
|
18
|
+
const parts = octets.map((part) => Number.parseInt(part, 10));
|
|
19
|
+
if (parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return null;
|
|
20
|
+
return parts;
|
|
21
|
+
}
|
|
22
|
+
function parseIpv4(ip) {
|
|
23
|
+
const parts = parseIpv4OrNull(ip);
|
|
24
|
+
if (!parts) throw new Error(`invalid IPv4 address: ${ip}`);
|
|
25
|
+
return parts;
|
|
26
|
+
}
|
|
27
|
+
function vmHostIp(slot) {
|
|
28
|
+
assertValidSlot(slot);
|
|
29
|
+
return `${VM_NETWORK_PREFIX}.${slot}.1`;
|
|
30
|
+
}
|
|
31
|
+
function vmGuestIp(slot) {
|
|
32
|
+
assertValidSlot(slot);
|
|
33
|
+
return `${VM_NETWORK_PREFIX}.${slot}.2`;
|
|
34
|
+
}
|
|
35
|
+
function slotFromVmHostIpOrNull(hostIp) {
|
|
36
|
+
const parts = parseIpv4OrNull(hostIp);
|
|
37
|
+
if (!parts) return null;
|
|
38
|
+
const slot = parts[2];
|
|
39
|
+
if (!Number.isInteger(slot) || slot < 0 || slot > 254) return null;
|
|
40
|
+
return slot;
|
|
41
|
+
}
|
|
42
|
+
function slotFromVmHostIp(hostIp) {
|
|
43
|
+
const slot = slotFromVmHostIpOrNull(hostIp);
|
|
44
|
+
if (slot === null) throw new Error(`invalid VM host IP: ${hostIp}`);
|
|
45
|
+
return slot;
|
|
46
|
+
}
|
|
47
|
+
function vmLinkCidrFromIp(ip) {
|
|
48
|
+
const [first, second, third] = parseIpv4(ip);
|
|
49
|
+
return `${first}.${second}.${third}.0/30`;
|
|
50
|
+
}
|
|
7
51
|
/**
|
|
8
52
|
* Resolve the UID/GID of the real (non-root) user when running under sudo.
|
|
9
53
|
* Returns null when not running under sudo or when env vars are missing.
|
|
@@ -183,10 +227,12 @@ function table(opts) {
|
|
|
183
227
|
return [`\x1b[1m${titles.map(padded).join(sep)}\x1b[0m`, ...data.map((row) => row.map(padded).join(sep))].join("\n");
|
|
184
228
|
}
|
|
185
229
|
function findFreeNetworkSlot(states) {
|
|
186
|
-
const usedSlots =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
230
|
+
const usedSlots = /* @__PURE__ */ new Set();
|
|
231
|
+
for (const state of states) {
|
|
232
|
+
if (state.status === "error") continue;
|
|
233
|
+
const slot = slotFromVmHostIpOrNull(state.network.hostIp);
|
|
234
|
+
if (slot !== null) usedSlots.add(slot);
|
|
235
|
+
}
|
|
190
236
|
for (const slot of getActiveTapSlots()) usedSlots.add(slot);
|
|
191
237
|
for (let slot = 0; slot <= 254; slot++) if (!usedSlots.has(slot)) return slot;
|
|
192
238
|
throw networkSlotsExhaustedError();
|
|
@@ -270,4 +316,4 @@ async function waitForAgent(guestIp, port, timeoutMs = 6e4) {
|
|
|
270
316
|
}
|
|
271
317
|
throw agentTimeoutError(guestIp, timeoutMs);
|
|
272
318
|
}
|
|
273
|
-
export { getActiveTapSlots as a, mkdirSecure as c, sleepSync as d, table as f, writeSecure as g, toError as h, findFreeNetworkSlot as i, parseDuration as l, timeRemaining as m, waitForAgent as n, generateVmId as o, timeAgo as p, FileVmStateStore as r, isProcessAlive as s, resolveVmState as t, safeKill as u };
|
|
319
|
+
export { vmLinkCidrFromIp as S, SUPPORTED_VM_ADDRESS_BLOCKS as _, getActiveTapSlots as a, vmGuestIp as b, mkdirSecure as c, sleepSync as d, table as f, writeSecure as g, toError as h, findFreeNetworkSlot as i, parseDuration as l, timeRemaining as m, waitForAgent as n, generateVmId as o, timeAgo as p, FileVmStateStore as r, isProcessAlive as s, resolveVmState as t, safeKill as u, VM_SUBNET_MASK as v, vmHostIp as x, slotFromVmHostIp as y };
|
package/dist/index.d.mts
CHANGED
|
@@ -102,7 +102,7 @@ interface NetworkConfig {
|
|
|
102
102
|
declare class NetworkManager {
|
|
103
103
|
config: NetworkConfig;
|
|
104
104
|
constructor(slot: number, networkPolicy: string, allowedDomains: string[], allowedCidrs: string[], deniedCidrs: string[], publishedPorts: number[], bandwidthMbit?: number, netnsName?: string, skipDnat?: boolean);
|
|
105
|
-
static bootArgs(
|
|
105
|
+
static bootArgs(config: Pick<NetworkConfig, "guestIp" | "hostIp" | "subnetMask">): string;
|
|
106
106
|
static fromConfig(config: NetworkConfig): NetworkManager;
|
|
107
107
|
static fromVmNetwork(network: VmNetwork): NetworkManager;
|
|
108
108
|
private nsRun;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vmsan",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.27",
|
|
4
4
|
"description": "Firecracker microVM sandbox toolkit",
|
|
5
5
|
"homepage": "https://github.com/angelorc/vmsan",
|
|
6
6
|
"bugs": "https://github.com/angelorc/vmsan/issues",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "obuild",
|
|
29
29
|
"dev": "obuild --stub",
|
|
30
|
-
"lint": "oxlint . && oxfmt --check '!**/*.md' '!docs/**' .",
|
|
31
|
-
"lint:fix": "oxlint . --fix && oxfmt '!**/*.md' '!docs/**' .",
|
|
32
|
-
"fmt": "oxfmt '!**/*.md' '!docs/**' .",
|
|
33
|
-
"fmt:check": "oxfmt --check '!**/*.md' '!docs/**' .",
|
|
30
|
+
"lint": "bunx oxlint . && bunx oxfmt --check '!**/*.md' '!docs/**' '!.changeset/pre.json' .",
|
|
31
|
+
"lint:fix": "bunx oxlint . --fix && bunx oxfmt '!**/*.md' '!docs/**' '!.changeset/pre.json' .",
|
|
32
|
+
"fmt": "bunx oxfmt '!**/*.md' '!docs/**' '!.changeset/pre.json' .",
|
|
33
|
+
"fmt:check": "bunx oxfmt --check '!**/*.md' '!docs/**' '!.changeset/pre.json' .",
|
|
34
34
|
"test": "vitest run --passWithNoTests",
|
|
35
35
|
"typecheck": "tsc --noEmit",
|
|
36
36
|
"prepack": "bun run build",
|