@ai-ecoverse/slicc-kernel 1.26.5 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.js CHANGED
@@ -1084,7 +1084,7 @@ function serveTransport(peer, transport) {
1084
1084
  }
1085
1085
 
1086
1086
  // src/client/protocol.ts
1087
- var PROTOCOL = [1, 5];
1087
+ var PROTOCOL = [1, 6];
1088
1088
  var FS_METHODS = [
1089
1089
  "readFile",
1090
1090
  "writeFile",
@@ -4491,6 +4491,7 @@ async function hostSyscall(req, ctx) {
4491
4491
 
4492
4492
  // src/kernel/net/loopback-names.ts
4493
4493
  var HOST_LOOPBACK = "host.slicc.internal";
4494
+ var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
4494
4495
  var PAGE_LOOPBACK = "kernel.localhost";
4495
4496
  var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
4496
4497
  function toHostLoopback(href) {
@@ -5687,6 +5688,214 @@ var HttpHandles = class {
5687
5688
  }
5688
5689
  };
5689
5690
 
5691
+ // src/kernel/net/routes.ts
5692
+ var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
5693
+ function ipv4Bytes(text) {
5694
+ const parts = text.split(".");
5695
+ if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
5696
+ const bytes = parts.map(Number);
5697
+ return bytes.every((b) => b <= 255) ? bytes : void 0;
5698
+ }
5699
+ function ipv6Groups(text) {
5700
+ if (!text.includes(":")) return void 0;
5701
+ let canonical;
5702
+ try {
5703
+ canonical = new URL(`http://[${text}]/`).hostname.slice(1, -1);
5704
+ } catch {
5705
+ return void 0;
5706
+ }
5707
+ const [head, tail] = canonical.split("::");
5708
+ const part = (s) => s ? s.split(":").map((h) => Number.parseInt(h, 16)) : [];
5709
+ const front = part(head);
5710
+ const back = part(tail);
5711
+ return tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
5712
+ }
5713
+ function toBits(values, width) {
5714
+ return values.reduce((acc, v) => acc << BigInt(width) | BigInt(v), 0n);
5715
+ }
5716
+ function addressBits(host) {
5717
+ const v4 = ipv4Bytes(host);
5718
+ if (v4) return { v6: false, bits: toBits(v4, 8) };
5719
+ const v6 = ipv6Groups(host);
5720
+ return v6 ? { v6: true, bits: toBits(v6, 16) } : void 0;
5721
+ }
5722
+ function parsePrefix(text) {
5723
+ const parts = text.trim().split("/");
5724
+ if (parts.length > 2) return void 0;
5725
+ const [address = "", size] = parts;
5726
+ const parsed = addressBits(address);
5727
+ if (!parsed) return void 0;
5728
+ const max = parsed.v6 ? 128 : 32;
5729
+ const length2 = size === void 0 ? max : /^\d{1,3}$/.test(size) ? Number(size) : -1;
5730
+ if (length2 < 0 || length2 > max) return void 0;
5731
+ return { ...parsed, length: length2 };
5732
+ }
5733
+ function within3(prefix, addr) {
5734
+ if (prefix.v6 !== addr.v6) return false;
5735
+ const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
5736
+ return addr.bits >> shift === prefix.bits >> shift;
5737
+ }
5738
+ var RESERVED = [
5739
+ "0.0.0.0/8",
5740
+ "127.0.0.0/8",
5741
+ "169.254.0.0/16",
5742
+ "224.0.0.0/4",
5743
+ "240.0.0.0/4",
5744
+ "::/96",
5745
+ "::ffff:0:0/96",
5746
+ "fe80::/10",
5747
+ "ff00::/8"
5748
+ ].map((p) => parsePrefix(p));
5749
+ var Routes = class {
5750
+ table = [];
5751
+ exit = false;
5752
+ ipv6;
5753
+ constructor(ipv62 = false) {
5754
+ this.ipv6 = ipv62;
5755
+ }
5756
+ set(table) {
5757
+ const parsed = (table.prefixes ?? []).map(parsePrefix);
5758
+ const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);
5759
+ if (bad !== void 0) throw new Error(`not an address prefix: ${bad}`);
5760
+ this.table = parsed;
5761
+ this.exit = table.exit === true;
5762
+ }
5763
+ classify(host) {
5764
+ const name2 = host.toLowerCase().replace(/\.$/, "");
5765
+ if (name2 === "localhost" || name2.endsWith(".localhost") || name2 === "0.0.0.0") {
5766
+ return "loopback";
5767
+ }
5768
+ if (name2 === HOST_LOOPBACK || name2 === HOST_ADDRESS) return "host";
5769
+ const addr = addressBits(name2);
5770
+ if (!addr) return "unreachable";
5771
+ if (addr.v6 && addr.bits === 1n) return "loopback";
5772
+ if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
5773
+ if (RESERVED.some((p) => within3(p, addr))) return "reserved";
5774
+ if (addr.v6 && !this.ipv6) return "unreachable";
5775
+ if (this.exit || this.table.some((p) => within3(p, addr))) return "uplink";
5776
+ return "unreachable";
5777
+ }
5778
+ };
5779
+
5780
+ // src/kernel/net/uplink.ts
5781
+ function answerOf(result) {
5782
+ const answer = Array.isArray(result) ? { addresses: result } : result;
5783
+ const addresses = Array.isArray(answer?.addresses) ? answer.addresses.filter((a) => typeof a === "string") : [];
5784
+ return {
5785
+ addresses,
5786
+ ...typeof answer?.ttl === "number" && answer.ttl >= 0 ? { ttl: answer.ttl } : {}
5787
+ };
5788
+ }
5789
+
5790
+ // src/kernel/net/resolver.ts
5791
+ var RESOLVE_TTL_MAX = 60;
5792
+ var RESOLVE_TIMEOUT_MS = 1e4;
5793
+ var RESOLVE_CACHE_MAX = 256;
5794
+ var LOOPBACK_NAMES2 = /* @__PURE__ */ new Set(["localhost", "localhost.localdomain", "ip6-localhost", "::1"]);
5795
+ function hostsEntry(name2) {
5796
+ if (LOOPBACK_NAMES2.has(name2) || name2.endsWith(".localhost")) return "127.0.0.1";
5797
+ if (name2 === HOST_LOOPBACK) return HOST_LOOPBACK_ADDRESS.join(".");
5798
+ return ipv4Bytes(name2) ? name2 : void 0;
5799
+ }
5800
+ var Resolver = class {
5801
+ uplink;
5802
+ routes;
5803
+ now;
5804
+ timeoutMs;
5805
+ log;
5806
+ cache = /* @__PURE__ */ new Map();
5807
+ pending = /* @__PURE__ */ new Map();
5808
+ constructor(options) {
5809
+ this.uplink = options.uplink;
5810
+ this.routes = options.routes;
5811
+ this.now = options.now ?? Date.now;
5812
+ this.timeoutMs = options.timeoutMs ?? RESOLVE_TIMEOUT_MS;
5813
+ this.log = options.log ?? ((message2) => console.warn(message2));
5814
+ }
5815
+ async resolve(name2, family, blocking) {
5816
+ const key = name2.toLowerCase().replace(/\.$/, "");
5817
+ const local = hostsEntry(key);
5818
+ if (ipv6Groups(key)) {
5819
+ const v4 = family !== 6 && local ? [local] : [];
5820
+ return family === 4 ? v4 : [...v4, key];
5821
+ }
5822
+ if (local) {
5823
+ const six = LOOPBACK_NAMES2.has(key) || key.endsWith(".localhost") ? ["::1"] : [];
5824
+ return family === 6 ? six : family === 4 ? [local] : [local, ...six];
5825
+ }
5826
+ if (!this.uplink || key === "") return [];
5827
+ const asked = this.uplink.traits.ipv6 ? family : 4;
5828
+ if (family === 6 && asked !== 6) return [];
5829
+ const id = `${asked}:${key}`;
5830
+ const hit = this.cache.get(id);
5831
+ if (hit && hit.expires > this.now()) return hit.addresses;
5832
+ const signal = blocking?.();
5833
+ if (signal?.aborted) throw new KernelError("EINTR");
5834
+ let lookup = this.pending.get(id);
5835
+ if (!lookup) {
5836
+ const abort = new AbortController();
5837
+ lookup = { work: this.lookup(key, asked, id, abort.signal), abort, waiters: 0 };
5838
+ this.pending.set(id, lookup);
5839
+ void lookup.work.then(() => this.pending.delete(id));
5840
+ }
5841
+ return waitFor(lookup, signal);
5842
+ }
5843
+ async lookup(name2, family, id, abandoned) {
5844
+ const uplink = this.uplink;
5845
+ const timeout = AbortSignal.any([AbortSignal.timeout(this.timeoutMs), abandoned]);
5846
+ let answer;
5847
+ try {
5848
+ answer = answerOf(
5849
+ await Promise.race([
5850
+ uplink.resolve(name2, family, timeout),
5851
+ new Promise(
5852
+ (_, reject) => timeout.addEventListener("abort", () => reject(timeout.reason), { once: true })
5853
+ )
5854
+ ])
5855
+ );
5856
+ } catch {
5857
+ return [];
5858
+ }
5859
+ const addresses = answer.addresses.filter((a) => this.usable(name2, a, family));
5860
+ if (addresses.length > 0) this.remember(id, addresses, answer.ttl);
5861
+ return addresses;
5862
+ }
5863
+ usable(name2, address, family) {
5864
+ const v4 = ipv4Bytes(address) !== void 0;
5865
+ if (!v4 && (family === 4 || !ipv6Groups(address))) return false;
5866
+ if (v4 && family === 6) return false;
5867
+ const kind = this.routes.classify(address);
5868
+ if (kind !== "loopback" && kind !== "host" && kind !== "reserved") return true;
5869
+ this.log(`slicc-kernel: dropped the uplink's answer ${address} for ${name2}: it is ${kind}`);
5870
+ return false;
5871
+ }
5872
+ remember(id, addresses, ttl) {
5873
+ const seconds = Math.min(ttl ?? RESOLVE_TTL_MAX, RESOLVE_TTL_MAX);
5874
+ if (seconds <= 0) return;
5875
+ this.cache.delete(id);
5876
+ this.cache.set(id, { addresses, expires: this.now() + seconds * 1e3 });
5877
+ if (this.cache.size > RESOLVE_CACHE_MAX) {
5878
+ this.cache.delete(this.cache.keys().next().value);
5879
+ }
5880
+ }
5881
+ };
5882
+ function waitFor(lookup, signal) {
5883
+ lookup.waiters++;
5884
+ return new Promise((resolve, reject) => {
5885
+ const abort = () => {
5886
+ if (--lookup.waiters === 0) lookup.abort.abort(new KernelError("EINTR"));
5887
+ reject(new KernelError("EINTR"));
5888
+ };
5889
+ signal?.addEventListener("abort", abort, { once: true });
5890
+ void lookup.work.then((value) => {
5891
+ if (signal?.aborted) return;
5892
+ lookup.waiters--;
5893
+ signal?.removeEventListener("abort", abort);
5894
+ resolve(value);
5895
+ });
5896
+ });
5897
+ }
5898
+
5690
5899
  // src/kernel/tty.ts
5691
5900
  var ICRNL = 256;
5692
5901
  var IUTF8 = 16384;
@@ -6190,7 +6399,8 @@ var SOCKET_OPS = [
6190
6399
  "sock-shutdown",
6191
6400
  "sock-name",
6192
6401
  "sock-getopt",
6193
- "sock-setopt"
6402
+ "sock-setopt",
6403
+ "sock-resolve"
6194
6404
  ];
6195
6405
  function socketAt(fds, fd) {
6196
6406
  const file = fds.get(fd).file;
@@ -6239,6 +6449,11 @@ async function socketSyscall(req, proc) {
6239
6449
  case "sock-setopt":
6240
6450
  socketAt(fds, req.fd).setOption(req.level, req.name, req.value);
6241
6451
  return ok();
6452
+ case "sock-resolve": {
6453
+ const addresses = await proc.resolve(req.name, req.family, () => proc.blocking());
6454
+ if (addresses.length === 0) throw new KernelError("ENOENT");
6455
+ return ok(addresses);
6456
+ }
6242
6457
  }
6243
6458
  }
6244
6459
  function unnamed(domain) {
@@ -6490,7 +6705,7 @@ function isMissing(err) {
6490
6705
  if (err?.code === "ENOENT") return true;
6491
6706
  return err instanceof Error && err.message.startsWith("ENOENT");
6492
6707
  }
6493
- function within3(path, root) {
6708
+ function within4(path, root) {
6494
6709
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
6495
6710
  }
6496
6711
  var VfsNode = class {
@@ -6698,7 +6913,7 @@ var VfsNodes = class {
6698
6913
  return node;
6699
6914
  }
6700
6915
  holds(prefix) {
6701
- for (const path of this.byPath.keys()) if (within3(path, prefix)) return true;
6916
+ for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
6702
6917
  return false;
6703
6918
  }
6704
6919
  closed(node) {
@@ -6706,24 +6921,24 @@ var VfsNodes = class {
6706
6921
  if (node.opens === 0 && this.byPath.get(node.path) === node) this.byPath.delete(node.path);
6707
6922
  }
6708
6923
  async flush(path) {
6709
- for (const [p, node] of this.byPath) if (within3(p, path)) await node.serial(() => node.flush());
6924
+ for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
6710
6925
  }
6711
6926
  async unlinking(path) {
6712
6927
  for (const [held, node] of [...this.byPath]) {
6713
- if (!within3(held, path)) continue;
6928
+ if (!within4(held, path)) continue;
6714
6929
  await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
6715
6930
  }
6716
6931
  }
6717
6932
  revoke(prefix) {
6718
6933
  for (const [path, node] of this.byPath) {
6719
- if (!within3(path, prefix)) continue;
6934
+ if (!within4(path, prefix)) continue;
6720
6935
  node.revoke();
6721
6936
  this.byPath.delete(path);
6722
6937
  }
6723
6938
  }
6724
6939
  unlinked(path) {
6725
6940
  for (const [held, node] of [...this.byPath]) {
6726
- if (!within3(held, path)) continue;
6941
+ if (!within4(held, path)) continue;
6727
6942
  node.orphaned = true;
6728
6943
  this.byPath.delete(held);
6729
6944
  }
@@ -6732,8 +6947,8 @@ var VfsNodes = class {
6732
6947
  if (from === to) return;
6733
6948
  const moved2 = [];
6734
6949
  for (const [p, node] of this.byPath) {
6735
- if (within3(p, from)) moved2.push(node);
6736
- else if (within3(p, to)) {
6950
+ if (within4(p, from)) moved2.push(node);
6951
+ else if (within4(p, to)) {
6737
6952
  node.orphaned = true;
6738
6953
  this.byPath.delete(p);
6739
6954
  }
@@ -6987,6 +7202,7 @@ function isWasmSyscall(req) {
6987
7202
  return typeof op === "string" && SYSCALL_OPS.has(op);
6988
7203
  }
6989
7204
  var MAX_READ = 1024 * 1024;
7205
+ var HOSTS_ONLY = new Resolver({ routes: new Routes() });
6990
7206
  var WasmProcess = class {
6991
7207
  exited = false;
6992
7208
  children;
@@ -7378,10 +7594,12 @@ var WasmProcess = class {
7378
7594
  }
7379
7595
  socketSyscall(req) {
7380
7596
  this.net ??= this.options.net ?? new LoopbackNet();
7597
+ const resolver = this.options.resolver ?? HOSTS_ONLY;
7381
7598
  return socketSyscall(req, {
7382
7599
  fds: this.fds,
7383
7600
  net: this.net,
7384
- blocking: () => this.blockingSignal()
7601
+ blocking: () => this.blockingSignal(),
7602
+ resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking)
7385
7603
  });
7386
7604
  }
7387
7605
  tty(fd) {
@@ -7576,6 +7794,7 @@ function spawnWasmProcess(opts) {
7576
7794
  http: opts.http,
7577
7795
  locks: opts.locks,
7578
7796
  onReap: opts.onReap,
7797
+ resolver: opts.resolver,
7579
7798
  onPending: (sig) => void Atomics.or(header3, SAB_I_SIGNALS, sigbit(sig)),
7580
7799
  onTimer: (which) => void Atomics.or(header3, SAB_I_TIMERS, 1 << which),
7581
7800
  hasPending: () => Atomics.load(header3, SAB_I_SIGNALS) !== 0 || Atomics.load(header3, SAB_I_TIMERS) !== 0,
@@ -9040,6 +9259,7 @@ var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
9040
9259
  var WASM_MAGIC = [0, 97, 115, 109];
9041
9260
  var SHEBANG_MAX = 256;
9042
9261
  var NOT_FOUND = 127;
9262
+ var INIT_PID = 1;
9043
9263
  var SHARED_DIRS = ["/tmp", "/home"];
9044
9264
  var encoder3 = new TextEncoder();
9045
9265
  function baseName(path) {
@@ -9123,6 +9343,9 @@ var Launcher = class {
9123
9343
  cdp;
9124
9344
  ca;
9125
9345
  transport;
9346
+ uplink;
9347
+ routes;
9348
+ resolver;
9126
9349
  watchers = new FsWatchers();
9127
9350
  pnpmHome;
9128
9351
  mounts;
@@ -9162,6 +9385,10 @@ var Launcher = class {
9162
9385
  this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
9163
9386
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
9164
9387
  this.transport = options.transport ?? missingTransport();
9388
+ this.uplink = options.uplink;
9389
+ this.routes = new Routes(options.uplink?.traits.ipv6 === true);
9390
+ if (options.uplink?.routes) this.routes.set(options.uplink.routes);
9391
+ this.resolver = new Resolver({ uplink: options.uplink, routes: this.routes });
9165
9392
  enableNetwork(this.net, {
9166
9393
  transport: this.transport,
9167
9394
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
@@ -9176,6 +9403,9 @@ var Launcher = class {
9176
9403
  this.catalog = void 0;
9177
9404
  });
9178
9405
  }
9406
+ setRoutes(table) {
9407
+ this.routes.set(table);
9408
+ }
9179
9409
  commands() {
9180
9410
  this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
9181
9411
  return this.catalog;
@@ -9355,6 +9585,7 @@ var Launcher = class {
9355
9585
  net: this.net,
9356
9586
  http: new HttpHandles(this.transport),
9357
9587
  locks: this.locks,
9588
+ resolver: this.resolver,
9358
9589
  onReap: (child) => this.reaped(child),
9359
9590
  ...req.fork ? { fork: req.fork } : {},
9360
9591
  ...this.shownIds(req)
@@ -9400,7 +9631,7 @@ var Launcher = class {
9400
9631
  };
9401
9632
  const shown = (pid) => {
9402
9633
  const member = pid === void 0 ? void 0 : members.get(pid);
9403
- return member ? rootOf(member).pid : pid ?? 0;
9634
+ return member ? rootOf(member).pid : INIT_PID;
9404
9635
  };
9405
9636
  const listed = [];
9406
9637
  for (const member of members.values()) {
@@ -10704,6 +10935,7 @@ async function createNodeKernel(options = {}) {
10704
10935
  ...options.modules ? { modules: options.modules } : {},
10705
10936
  ...options.env ? { env: options.env } : {},
10706
10937
  ...transport ? { transport } : {},
10938
+ ...options.network?.uplink ? { uplink: options.network.uplink } : {},
10707
10939
  ...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {},
10708
10940
  ...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {},
10709
10941
  ...options.hostfs ? { hostfs: options.hostfs } : {},
@@ -10771,6 +11003,7 @@ async function createNodeKernel(options = {}) {
10771
11003
  mount: (spec) => guard(() => launcher.mount(spec)),
10772
11004
  umount: (target) => guard(async () => launcher.umount(target)),
10773
11005
  mounts: async () => launcher.mounts.list(),
11006
+ setRoutes: (routes) => guard(async () => launcher.setRoutes(routes)),
10774
11007
  async connect() {
10775
11008
  if (terminated) throw new Error(TERMINATED);
10776
11009
  const { port1, port2 } = new MessageChannel2();