@ai-ecoverse/slicc-kernel 1.154.3 → 1.156.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/README.md +2 -1
- package/dist/kernel-worker.js +70 -11
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +9 -2
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +70 -11
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +9 -2
- package/dist/process-worker.js.map +2 -2
- package/dist/types/kernel/net/tls-client.d.ts +2 -0
- package/dist/types/kernel/socket-syscalls.d.ts +3 -0
- package/dist/types/kernel/socket.d.ts +6 -0
- package/dist/types/process/js/js-net.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -453,7 +453,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
453
453
|
- `wait(pid, { untraced })` resolves with `{ status }` once a child ends, or `{ status: 128 + signal, signal }` when a signal ended it, without blocking the program; with `untraced: true` it also resolves when the child stops, with `{ status: 128 + signal, signal, stopped: true }`, as `waitpid` with `WUNTRACED` (a child's `wait({ untraced })` is the same), and options that aren't an object are `EINVAL`; `kill(pid, signal)` signals any process, by number or as `'SIGTERM'` (the default);
|
|
454
454
|
- `sudo(args)`, what the [`sudo`](#sudo) command calls: `args` are its arguments after `sudo`, and it resolves with `{ pid }`, the command the kernel started as the program's child, to `wait` for; `{ run }`, an argv to run as the caller (with `users: 'off'`); `{ list }`, the text of `sudo -l`; or `{ error, errno }`, the message to print;
|
|
455
455
|
- `crontab({ action, user?, text? })`, what the [`crontab`](#cron) command calls: `action` is `'list'`, `'remove'` or `'install'` (with the table as `text`), on the caller's own table or, for an effective uid of 0, on `user`'s. It resolves with `{ text }` (the table for `list`, `''` otherwise) or `{ error }`, the message to print. A malformed call is `EINVAL`;
|
|
456
|
-
- `net`: `listen({ port, host, backlog })` and `connect({ host, port, signal })` (`host` defaults to `127.0.0.1`) on the kernel's loopback, the same sockets WASIX programs use, so a JS server is reached by other processes, by the page as `<port>.kernel.localhost` (`loopbackFetch`, `dial`) and by attached clients. A listener has its `port` (as bound, so port `0` picks a free one), `accept({ signal })`, `close()` and `for await` over its connections. A connection has `local` and `remote` addresses, `readable` and `writable` web streams (closing the writable shuts down the sending side, so the peer reads the end), and `
|
|
456
|
+
- `net`: `listen({ port, host, backlog })` and `connect({ host, port, signal })` (`host` defaults to `127.0.0.1`) on the kernel's loopback, the same sockets WASIX programs use, so a JS server is reached by other processes, by the page as `<port>.kernel.localhost` (`loopbackFetch`, `dial`) and by attached clients. A listener has its `port` (as bound, so port `0` picks a free one), `accept({ signal })`, `close()` and `for await` over its connections. A connection has `local` and `remote` addresses, `readable` and `writable` web streams (closing the writable shuts down the sending side, so the peer reads the end), `close()`, and `reset()`, which closes it with an RST as Node's `resetAndDestroy()` does ([#1530](https://github.com/ai-ecoverse/slicc-kernel/issues/1530)): on the kernel's loopback, the peer's pending and later reads and its writes fail with `ECONNRESET` instead of seeing the end, and bytes still in flight to it are dropped. A TLS connection (`secure()`, a `tls` connect on the loopback) resets the same way. On an uplink connection `reset()` closes at once with a FIN, as the transport can't send an RST: bytes still buffered for the peer are discarded instead of sent. The loopback is IPv4 (`127.0.0.1` by default, or `0.0.0.0`), so an IPv6 host such as `::1` fails with `EADDRNOTAVAIL` for both `listen` and `connect`, and binding and connecting fail as they would for a WASIX program (`EADDRINUSE`, `ECONNREFUSED`, `EADDRNOTAVAIL`). `sync.net.listen` takes the same options and gives the same listener, bound and listening when it returns, so its `port` is known at once, as Node's `listen()` needs ([#1398](https://github.com/ai-ecoverse/slicc-kernel/issues/1398)); it needs the sync lane. A blocked accept, read or write waits without holding anything in the kernel, so aborting it or closing the socket leaves nothing behind. A write to a connection the peer closed fails with `EPIPE` and no `SIGPIPE`;
|
|
457
457
|
- `net.connect({ host, port, tls: { servername, alpn, rejectUnauthorized, ca, path, cert, key, passphrase, sni, checkHostname, keylog } })` takes one of two paths. By default it's **A1, TLS through the transport** ([#914](https://github.com/ai-ecoverse/slicc-kernel/issues/914)), described first below. For a destination the uplink routes it can also be **A2, a TLS handshake in the kernel** ([#922](https://github.com/ai-ecoverse/slicc-kernel/issues/922)), described after it. `conn.tls.path` (`'transport'` or `'kernel'`) and `conn.tls.substitutes` say which one was taken.
|
|
458
458
|
- **What it does:** there's no handshake in the kernel. The connection carries plain HTTP/1.1 (and WebSocket upgrades) to the realm proxy's HTTP path for the origin `https://<servername or host>[:port]`, exactly as a `CONNECT` through `127.0.0.1:3128` does after the kernel terminates its TLS. The transport (the page's `fetch` or WebSocket, or the uplink's) makes the real TLS connection and verifies it. So `https.request` over a socket, keep-alive agents, `agent-base` and `wss://` clients work with the transport's semantics. That includes sealed placeholders in headers, which reach the transport exactly as from `ctx.fetch`.
|
|
459
459
|
- **The `tls` property:** the connection's `tls` is `{ path: 'transport', substitutes: true, protocol: 'transport', alpnProtocol: 'http/1.1', authorized: true, servername }`. `authorized` means the transport verified the server. There's no peer certificate chain and no TLS version of the kernel's own, so don't read it as a real handshake's result.
|
|
@@ -501,6 +501,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
501
501
|
- Before dialling, `cert` without `key` (or the reverse) is `EINVAL`. A key that doesn't match the leaf is `ERR_OSSL_X509_KEY_VALUES_MISMATCH`, a wrong passphrase `ERR_OSSL_BAD_DECRYPT`, a missing one `ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED`, and a key the engine can't read `ERR_OSSL_UNSUPPORTED`.
|
|
502
502
|
- `pfx` is `ENOTSUP`: pass `cert` and `key`.
|
|
503
503
|
- A server that requires a certificate and gets none refuses with an alert. On TLS 1.2 that fails the connect (`ERR_SSL_HANDSHAKE_FAILURE`). On TLS 1.3 the client's side of the handshake is already done, so the next read or write fails with `ERR_SSL_FATAL_ALERT`, as Node's fails after `secureConnect`.
|
|
504
|
+
- **A reply that isn't TLS** ([#1548](https://github.com/ai-ecoverse/slicc-kernel/issues/1548)) is told apart by its first record's header, in OpenSSL's order, so a program gets Node's code: an SSLv2 version is `ERR_SSL_WRONG_VERSION_NUMBER`; a length over 16704 bytes `ERR_SSL_PACKET_LENGTH_TOO_LONG` (most plain-text replies, `HTTP/1.1 …` among them); a major version other than 3 `ERR_SSL_HTTP_REQUEST` for `GET ` or `PUT ` and `ERR_SSL_WRONG_VERSION_NUMBER` otherwise (`220 …`, `SSH-2.0…`, `+OK …`); a record over 16384 bytes `ERR_SSL_DATA_LENGTH_TOO_LONG`; and a handshake record that isn't a ServerHello `ERR_SSL_UNEXPECTED_MESSAGE`. Anything else stays `ERR_SSL_HANDSHAKE_FAILURE`, or `ECONNRESET` for a server that closes.
|
|
504
505
|
- **A fatal alert from the server**, at the handshake or after it, is in the error: its `detail` ends in ` (alert <n>)`, and a JS program's error has `alert: n`, the alert's description number (116 certificate_required, 40 handshake_failure).
|
|
505
506
|
- **`keylog: true`** ([#1504](https://github.com/ai-ecoverse/slicc-kernel/issues/1504), tls-engine 4.2.0-10) gives the session's NSS key log lines as `conn.tls.keylog`, a frozen list of strings: `CLIENT_RANDOM …` on TLS 1.2, the four `…_TRAFFIC_SECRET…` lines on TLS 1.3, the lines Node's `keylog` event and `SSLKEYLOGFILE` carry. **They are the session's secrets: anyone holding them and a capture of the traffic can read it.** So a keylogged session is never sealed (`substitutes: false`; a placeholder goes out as written), and `keylog` on the transport path (A1, where the page makes the TLS) is `ENOTSUP`. Without `keylog` the engine collects nothing. The kernel wipes its copies once the lines are in the reply (the engine zeroes its buffer as it hands them out); the lines reach the program as JS strings, which can't be wiped, so they live as long as the program keeps them, as with its own `key`. A TLS server takes `keylog` too (`listen({ tls })`, `secure(conn, { server: true })`), for every connection it serves.
|
|
506
507
|
- **`sni: false`** sends no server name. The leaf is still checked against `servername` exactly as with SNI.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -6097,8 +6097,8 @@ function link(a, b) {
|
|
|
6097
6097
|
pipe.openRead();
|
|
6098
6098
|
pipe.openWrite();
|
|
6099
6099
|
}
|
|
6100
|
-
a.attach({ rx: ba, tx: ab });
|
|
6101
|
-
b.attach({ rx: ab, tx: ba });
|
|
6100
|
+
a.attach({ rx: ba, tx: ab, peer: b });
|
|
6101
|
+
b.attach({ rx: ab, tx: ba, peer: a });
|
|
6102
6102
|
}
|
|
6103
6103
|
var SERVE_CHUNK = 64 * 1024;
|
|
6104
6104
|
var NEVER_ABORTED = new AbortController().signal;
|
|
@@ -6110,6 +6110,8 @@ var KernelSocket = class _KernelSocket {
|
|
|
6110
6110
|
dialing;
|
|
6111
6111
|
failed;
|
|
6112
6112
|
reset;
|
|
6113
|
+
abortive = false;
|
|
6114
|
+
refused;
|
|
6113
6115
|
local;
|
|
6114
6116
|
peer;
|
|
6115
6117
|
peerCred;
|
|
@@ -6146,20 +6148,25 @@ var KernelSocket = class _KernelSocket {
|
|
|
6146
6148
|
}
|
|
6147
6149
|
async read(max, signal) {
|
|
6148
6150
|
const { rx } = this.connection();
|
|
6151
|
+
if (this.refused) throw this.refused;
|
|
6149
6152
|
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
6150
6153
|
const bytes = await pipeCall(() => rx.read(max, signal));
|
|
6154
|
+
if (this.refused) throw this.refused;
|
|
6151
6155
|
if (bytes.length === 0 && this.reset) throw this.reset;
|
|
6152
6156
|
return bytes;
|
|
6153
6157
|
}
|
|
6154
6158
|
async peek(max, signal) {
|
|
6155
6159
|
const { rx } = this.connection();
|
|
6160
|
+
if (this.refused) throw this.refused;
|
|
6156
6161
|
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
6157
6162
|
const bytes = await pipeCall(() => rx.peek(max, signal));
|
|
6163
|
+
if (this.refused) throw this.refused;
|
|
6158
6164
|
if (bytes.length === 0 && this.reset) throw this.reset;
|
|
6159
6165
|
return bytes;
|
|
6160
6166
|
}
|
|
6161
6167
|
async write(bytes, signal) {
|
|
6162
6168
|
const { tx } = this.connection();
|
|
6169
|
+
if (this.refused) throw this.refused;
|
|
6163
6170
|
if (this.writeShut) throw new KernelError("EPIPE");
|
|
6164
6171
|
if (this.reset?.code === "EPROTO") throw this.reset;
|
|
6165
6172
|
return pipeCall(() => tx.write(bytes, signal));
|
|
@@ -6346,10 +6353,11 @@ var KernelSocket = class _KernelSocket {
|
|
|
6346
6353
|
handshaking = false;
|
|
6347
6354
|
signal.removeEventListener("abort", abandon);
|
|
6348
6355
|
}
|
|
6349
|
-
|
|
6356
|
+
const plain3 = uplinkLink(made2.conn, (err) => {
|
|
6350
6357
|
this.reset = err instanceof KernelError && err.code === "EPROTO" ? err : new KernelError("ECONNRESET");
|
|
6351
6358
|
this.changes.wake();
|
|
6352
6359
|
});
|
|
6360
|
+
this.link = { ...plain3, ...raw2.peer ? { peer: raw2.peer } : {} };
|
|
6353
6361
|
this.changes.wake();
|
|
6354
6362
|
return made2.info;
|
|
6355
6363
|
}
|
|
@@ -6467,8 +6475,22 @@ var KernelSocket = class _KernelSocket {
|
|
|
6467
6475
|
setOption(level, name2, value2) {
|
|
6468
6476
|
this.options.set(`${level}:${name2}`, value2);
|
|
6469
6477
|
}
|
|
6478
|
+
resetOnClose() {
|
|
6479
|
+
this.connection();
|
|
6480
|
+
this.abortive = true;
|
|
6481
|
+
}
|
|
6482
|
+
abort() {
|
|
6483
|
+
const link2 = this.link;
|
|
6484
|
+
if (link2.peer) link2.peer.resetBy();
|
|
6485
|
+
else link2.tx.closeRead();
|
|
6486
|
+
}
|
|
6487
|
+
resetBy() {
|
|
6488
|
+
this.refused ??= new KernelError("ECONNRESET");
|
|
6489
|
+
this.changes.wake();
|
|
6490
|
+
}
|
|
6470
6491
|
close() {
|
|
6471
6492
|
const was = this.state;
|
|
6493
|
+
if (this.abortive && was === "connected") this.abort();
|
|
6472
6494
|
this.state = "closed";
|
|
6473
6495
|
this.dialing?.abort(new KernelError("ECONNABORTED"));
|
|
6474
6496
|
this.dialing = void 0;
|
|
@@ -8090,7 +8112,7 @@ function satisfies(version, range2) {
|
|
|
8090
8112
|
}
|
|
8091
8113
|
|
|
8092
8114
|
// src/version.ts
|
|
8093
|
-
var KERNEL_VERSION = true ? "1.
|
|
8115
|
+
var KERNEL_VERSION = true ? "1.156.0" : "0.0.0-dev";
|
|
8094
8116
|
var OS_RELEASE = "6.0.0-slicc";
|
|
8095
8117
|
function procVersion() {
|
|
8096
8118
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -19445,6 +19467,7 @@ function openSession(o) {
|
|
|
19445
19467
|
var Ciphertext = class {
|
|
19446
19468
|
queue = [];
|
|
19447
19469
|
ended = false;
|
|
19470
|
+
opening = new Uint8Array(0);
|
|
19448
19471
|
conn;
|
|
19449
19472
|
session;
|
|
19450
19473
|
constructor(conn, session) {
|
|
@@ -19453,11 +19476,19 @@ var Ciphertext = class {
|
|
|
19453
19476
|
}
|
|
19454
19477
|
hand(bytes) {
|
|
19455
19478
|
if (bytes.length > 0) this.queue.push(bytes);
|
|
19479
|
+
this.note(bytes);
|
|
19480
|
+
}
|
|
19481
|
+
note(bytes) {
|
|
19482
|
+
if (this.opening.length >= OPENING) return;
|
|
19483
|
+
const next3 = new Uint8Array(Math.min(OPENING, this.opening.length + bytes.length));
|
|
19484
|
+
next3.set(this.opening);
|
|
19485
|
+
next3.set(bytes.subarray(0, next3.length - this.opening.length), this.opening.length);
|
|
19486
|
+
this.opening = next3;
|
|
19456
19487
|
}
|
|
19457
19488
|
async more() {
|
|
19458
19489
|
if (this.queue.length === 0 && !this.ended) {
|
|
19459
19490
|
const bytes = await this.conn.read();
|
|
19460
|
-
if (bytes) this.
|
|
19491
|
+
if (bytes) this.hand(bytes);
|
|
19461
19492
|
else this.ended = true;
|
|
19462
19493
|
}
|
|
19463
19494
|
if (this.queue.length === 0) {
|
|
@@ -19487,11 +19518,38 @@ async function send(conn, session) {
|
|
|
19487
19518
|
}
|
|
19488
19519
|
}
|
|
19489
19520
|
}
|
|
19490
|
-
|
|
19521
|
+
var OPENING = 6;
|
|
19522
|
+
var PLAIN_MAX = 16384;
|
|
19523
|
+
var ENCRYPTED_MAX = PLAIN_MAX + 320;
|
|
19524
|
+
var HTTP_METHODS = ["GET ", "POST ", "HEAD ", "PUT ", "PATCH", "OPTIO", "DELET", "TRACE"];
|
|
19525
|
+
function headerClass(head) {
|
|
19526
|
+
const [type, major, minor, hi, lo] = head;
|
|
19527
|
+
if (major === 0 && minor === 2) return ["ERR_SSL_WRONG_VERSION_NUMBER", "wrong version number"];
|
|
19528
|
+
const length2 = hi << 8 | lo;
|
|
19529
|
+
if (length2 > ENCRYPTED_MAX) return ["ERR_SSL_PACKET_LENGTH_TOO_LONG", "packet length too long"];
|
|
19530
|
+
if (major !== 3) {
|
|
19531
|
+
const text = new TextDecoder("latin1").decode(head.subarray(0, 5));
|
|
19532
|
+
if (HTTP_METHODS.some((m) => text.startsWith(m)))
|
|
19533
|
+
return ["ERR_SSL_HTTP_REQUEST", "http request"];
|
|
19534
|
+
return ["ERR_SSL_WRONG_VERSION_NUMBER", "wrong version number"];
|
|
19535
|
+
}
|
|
19536
|
+
if (length2 > PLAIN_MAX) return ["ERR_SSL_DATA_LENGTH_TOO_LONG", "data length too long"];
|
|
19537
|
+
if (type === 22 && head.length > 5 && head[5] !== 2)
|
|
19538
|
+
return ["ERR_SSL_UNEXPECTED_MESSAGE", "unexpected message"];
|
|
19539
|
+
return void 0;
|
|
19540
|
+
}
|
|
19541
|
+
function recordRefusal(opening2) {
|
|
19542
|
+
if (opening2.length < 5) return void 0;
|
|
19543
|
+
const found = headerClass(opening2);
|
|
19544
|
+
return found && new KernelError("EPROTO", `${found[0]}: ${found[1]}`);
|
|
19545
|
+
}
|
|
19546
|
+
function refused2(engine, session, err, opening2) {
|
|
19491
19547
|
const flags2 = session.verifyResult;
|
|
19492
19548
|
const code = verifyCode(flags2);
|
|
19493
19549
|
if (code && flags2 !== -1)
|
|
19494
19550
|
return new KernelError("EPROTO", `${code}: ${engine.verifyInfo(flags2).trim()}`);
|
|
19551
|
+
const record2 = recordRefusal(opening2);
|
|
19552
|
+
if (record2) return record2;
|
|
19495
19553
|
const text = err instanceof TlsError ? err.message : String(err);
|
|
19496
19554
|
return new KernelError("EPROTO", `ERR_SSL_HANDSHAKE_FAILURE: ${text}${alertOf(session)}`);
|
|
19497
19555
|
}
|
|
@@ -19506,15 +19564,12 @@ async function handshake(conn, o, session, inbox) {
|
|
|
19506
19564
|
done5 = session.handshake();
|
|
19507
19565
|
} catch (err) {
|
|
19508
19566
|
await send(conn, session).catch(() => void 0);
|
|
19509
|
-
throw refused2(o.engine, session, err);
|
|
19567
|
+
throw refused2(o.engine, session, err, inbox.opening);
|
|
19510
19568
|
}
|
|
19511
19569
|
await send(conn, session);
|
|
19512
19570
|
if (done5) return;
|
|
19513
19571
|
if (!await inbox.more()) {
|
|
19514
|
-
throw new KernelError(
|
|
19515
|
-
"ECONNRESET",
|
|
19516
|
-
"the server closed the connection during the TLS handshake"
|
|
19517
|
-
);
|
|
19572
|
+
throw recordRefusal(inbox.opening) ?? new KernelError("ECONNRESET", "the server closed the connection during the TLS handshake");
|
|
19518
19573
|
}
|
|
19519
19574
|
}
|
|
19520
19575
|
}
|
|
@@ -23272,6 +23327,7 @@ var SOCKET_OPS = [
|
|
|
23272
23327
|
"sock-accept",
|
|
23273
23328
|
"sock-connect",
|
|
23274
23329
|
"sock-shutdown",
|
|
23330
|
+
"sock-reset",
|
|
23275
23331
|
"sock-name",
|
|
23276
23332
|
"sock-getopt",
|
|
23277
23333
|
"sock-setopt",
|
|
@@ -23339,6 +23395,9 @@ async function socketSyscall(req, proc) {
|
|
|
23339
23395
|
case "sock-shutdown":
|
|
23340
23396
|
anySocketAt(fds, req.fd).shutdown(req.how);
|
|
23341
23397
|
return ok3();
|
|
23398
|
+
case "sock-reset":
|
|
23399
|
+
socketAt(fds, req.fd).resetOnClose();
|
|
23400
|
+
return ok3();
|
|
23342
23401
|
case "sock-name": {
|
|
23343
23402
|
const socket = anySocketAt(fds, req.fd);
|
|
23344
23403
|
const addr = req.peer ? socket.peer : socket.local;
|