@ai-ecoverse/slicc-kernel 1.26.6 → 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,
@@ -9124,6 +9343,9 @@ var Launcher = class {
9124
9343
  cdp;
9125
9344
  ca;
9126
9345
  transport;
9346
+ uplink;
9347
+ routes;
9348
+ resolver;
9127
9349
  watchers = new FsWatchers();
9128
9350
  pnpmHome;
9129
9351
  mounts;
@@ -9163,6 +9385,10 @@ var Launcher = class {
9163
9385
  this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
9164
9386
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
9165
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 });
9166
9392
  enableNetwork(this.net, {
9167
9393
  transport: this.transport,
9168
9394
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
@@ -9177,6 +9403,9 @@ var Launcher = class {
9177
9403
  this.catalog = void 0;
9178
9404
  });
9179
9405
  }
9406
+ setRoutes(table) {
9407
+ this.routes.set(table);
9408
+ }
9180
9409
  commands() {
9181
9410
  this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
9182
9411
  return this.catalog;
@@ -9356,6 +9585,7 @@ var Launcher = class {
9356
9585
  net: this.net,
9357
9586
  http: new HttpHandles(this.transport),
9358
9587
  locks: this.locks,
9588
+ resolver: this.resolver,
9359
9589
  onReap: (child) => this.reaped(child),
9360
9590
  ...req.fork ? { fork: req.fork } : {},
9361
9591
  ...this.shownIds(req)
@@ -10705,6 +10935,7 @@ async function createNodeKernel(options = {}) {
10705
10935
  ...options.modules ? { modules: options.modules } : {},
10706
10936
  ...options.env ? { env: options.env } : {},
10707
10937
  ...transport ? { transport } : {},
10938
+ ...options.network?.uplink ? { uplink: options.network.uplink } : {},
10708
10939
  ...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {},
10709
10940
  ...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {},
10710
10941
  ...options.hostfs ? { hostfs: options.hostfs } : {},
@@ -10772,6 +11003,7 @@ async function createNodeKernel(options = {}) {
10772
11003
  mount: (spec) => guard(() => launcher.mount(spec)),
10773
11004
  umount: (target) => guard(async () => launcher.umount(target)),
10774
11005
  mounts: async () => launcher.mounts.list(),
11006
+ setRoutes: (routes) => guard(async () => launcher.setRoutes(routes)),
10775
11007
  async connect() {
10776
11008
  if (terminated) throw new Error(TERMINATED);
10777
11009
  const { port1, port2 } = new MessageChannel2();