@namzu/sandbox 1.1.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/CHANGELOG.md +474 -0
- package/LICENSE.md +110 -0
- package/README.md +148 -0
- package/dist/backends/aci-standby-pool/index.d.ts +104 -0
- package/dist/backends/aci-standby-pool/index.d.ts.map +1 -0
- package/dist/backends/aci-standby-pool/index.js +425 -0
- package/dist/backends/aci-standby-pool/index.js.map +1 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts +40 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts.map +1 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js +157 -0
- package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js.map +1 -0
- package/dist/backends/docker/index.d.ts +118 -0
- package/dist/backends/docker/index.d.ts.map +1 -0
- package/dist/backends/docker/index.js +645 -0
- package/dist/backends/docker/index.js.map +1 -0
- package/dist/backends/firecracker/__tests__/backend.test.d.ts +13 -0
- package/dist/backends/firecracker/__tests__/backend.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/backend.test.js +353 -0
- package/dist/backends/firecracker/__tests__/backend.test.js.map +1 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts +19 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js +201 -0
- package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js.map +1 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts +39 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js +149 -0
- package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js.map +1 -0
- package/dist/backends/firecracker/__tests__/protocol.test.d.ts +6 -0
- package/dist/backends/firecracker/__tests__/protocol.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/protocol.test.js +77 -0
- package/dist/backends/firecracker/__tests__/protocol.test.js.map +1 -0
- package/dist/backends/firecracker/__tests__/transport.test.d.ts +20 -0
- package/dist/backends/firecracker/__tests__/transport.test.d.ts.map +1 -0
- package/dist/backends/firecracker/__tests__/transport.test.js +449 -0
- package/dist/backends/firecracker/__tests__/transport.test.js.map +1 -0
- package/dist/backends/firecracker/index.d.ts +124 -0
- package/dist/backends/firecracker/index.d.ts.map +1 -0
- package/dist/backends/firecracker/index.js +334 -0
- package/dist/backends/firecracker/index.js.map +1 -0
- package/dist/backends/firecracker/protocol.d.ts +132 -0
- package/dist/backends/firecracker/protocol.d.ts.map +1 -0
- package/dist/backends/firecracker/protocol.js +112 -0
- package/dist/backends/firecracker/protocol.js.map +1 -0
- package/dist/backends/firecracker/transport.d.ts +251 -0
- package/dist/backends/firecracker/transport.d.ts.map +1 -0
- package/dist/backends/firecracker/transport.js +524 -0
- package/dist/backends/firecracker/transport.js.map +1 -0
- package/dist/index.d.ts +611 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +376 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +28 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +670 -0
- package/dist/index.test.js.map +1 -0
- package/package.json +54 -0
- package/src/backends/aci-standby-pool/index.ts +602 -0
- package/src/backends/docker/__tests__/leaf-permissions.smoke.test.ts +169 -0
- package/src/backends/docker/index.ts +826 -0
- package/src/backends/firecracker/__tests__/backend.test.ts +418 -0
- package/src/backends/firecracker/__tests__/control-plane-mtls.test.ts +253 -0
- package/src/backends/firecracker/__tests__/fixtures/mtls-pki.ts +166 -0
- package/src/backends/firecracker/__tests__/protocol.test.ts +90 -0
- package/src/backends/firecracker/__tests__/transport.test.ts +526 -0
- package/src/backends/firecracker/index.ts +528 -0
- package/src/backends/firecracker/protocol.ts +191 -0
- package/src/backends/firecracker/transport.ts +667 -0
- package/src/index.test.ts +731 -0
- package/src/index.ts +930 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side vsock transport + dialer for the Firecracker in-VM agent.
|
|
3
|
+
*
|
|
4
|
+
* This is the NEW code the §2.2 decision calls for. The docker/ACI
|
|
5
|
+
* backends reach the agent over HTTP with `fetch`; **Node `fetch`
|
|
6
|
+
* cannot dial `AF_VSOCK`**, and across an FC snapshot resume a TCP
|
|
7
|
+
* control channel is dead-on-arrival (FC `snapshot-support.md`: TCP
|
|
8
|
+
* connection state does not survive a resume; the **vsock LISTEN
|
|
9
|
+
* socket** does). So the FC control channel is a framed stream over
|
|
10
|
+
* vsock, and that framing + the resume-survival hardening live here.
|
|
11
|
+
*
|
|
12
|
+
* ## One wire, two transports
|
|
13
|
+
* The message FORMAT (NDJSON exec events + base64 file-IO) is shared
|
|
14
|
+
* with the HTTP backends via `protocol.ts`. This module owns only the
|
|
15
|
+
* TRANSPORT: how a request crosses the wire and how a response is
|
|
16
|
+
* framed back.
|
|
17
|
+
*
|
|
18
|
+
* Framing: a length-prefixed envelope per message —
|
|
19
|
+
* `<8-hex-digit big-endian byte length>\n<utf8 JSON payload>`
|
|
20
|
+
* The newline after the hex length lets a reader find the boundary
|
|
21
|
+
* without a fixed header struct, and the explicit length means a
|
|
22
|
+
* payload that itself contains newlines (NDJSON exec output) is read
|
|
23
|
+
* whole, not split. Exec replies are a SEQUENCE of framed NDJSON
|
|
24
|
+
* lines terminated by a zero-length frame; file-IO replies are a
|
|
25
|
+
* single framed JSON object.
|
|
26
|
+
*
|
|
27
|
+
* ## How vsock is actually dialed from Node
|
|
28
|
+
* Node has no `AF_VSOCK` socket family. The production path therefore
|
|
29
|
+
* follows exactly what the in-situ bench already proved: the guest
|
|
30
|
+
* agent's vsock stream is bridged to a **host-side unix-domain
|
|
31
|
+
* socket** (the bench relays guest `AF_VSOCK` → host CID:port → a host
|
|
32
|
+
* unix socket; FC's own vsock device exposes a host-side unix socket
|
|
33
|
+
* rendezvous, `UDS + "CONNECT <port>"`). So the host dialer ALWAYS
|
|
34
|
+
* terminates on a `net.connect({ path })` unix socket:
|
|
35
|
+
* - `kind: 'unix'` — connect directly to `path` (local/dev + tests).
|
|
36
|
+
* - `kind: 'vsock'` — connect to the FC vsock device's host unix
|
|
37
|
+
* socket `udsPath`, then send the firecracker hybrid-vsock
|
|
38
|
+
* handshake line `CONNECT <port>\n` and await the `OK <hostport>`
|
|
39
|
+
* ack before framing application traffic.
|
|
40
|
+
* Both land on the same `net.Socket`, so the unix-socket stand-in in
|
|
41
|
+
* the tests exercises the identical framing/heartbeat/reconnect code
|
|
42
|
+
* the vsock path runs — the only delta is the one-line CONNECT
|
|
43
|
+
* handshake, which is covered by its own assertion.
|
|
44
|
+
*
|
|
45
|
+
* ## Resume survival (the hard invariant, FC #4713 / loopholelabs)
|
|
46
|
+
* On resume the guest vsock driver closes all existing connections and
|
|
47
|
+
* the `TRANSPORT_RESET` event may NOT be delivered, so a host read can
|
|
48
|
+
* hang. The agent re-LISTENs after every resume; the host dialer
|
|
49
|
+
* carries a per-attempt connect/handshake **timeout + retry budget**
|
|
50
|
+
* so a dropped reset cannot wedge first-exec — the dialer simply
|
|
51
|
+
* re-dials. Every request opens a fresh connection (no long-lived
|
|
52
|
+
* socket to be silently severed by a resume), which makes the
|
|
53
|
+
* transport resume-survivable by construction.
|
|
54
|
+
*/
|
|
55
|
+
import net from 'node:net';
|
|
56
|
+
import tls from 'node:tls';
|
|
57
|
+
import { ExecResultAccumulator, parseExecLine, } from './protocol.js';
|
|
58
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 5_000;
|
|
59
|
+
const DEFAULT_CONNECT_RETRY_BUDGET_MS = 30_000;
|
|
60
|
+
const DEFAULT_CONNECT_RETRY_INTERVAL_MS = 100;
|
|
61
|
+
const DEFAULT_READ_IDLE_TIMEOUT_MS = 60_000;
|
|
62
|
+
/** Framing: 8 hex digits of payload byte length, then `\n`, then payload. */
|
|
63
|
+
const LENGTH_PREFIX_HEX = 8;
|
|
64
|
+
function frame(payload) {
|
|
65
|
+
const body = Buffer.from(payload, 'utf8');
|
|
66
|
+
const header = Buffer.from(`${body.length.toString(16).padStart(LENGTH_PREFIX_HEX, '0')}\n`, 'ascii');
|
|
67
|
+
return Buffer.concat([header, body]);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Incremental frame reader. Feed it socket chunks; it yields complete
|
|
71
|
+
* payloads. A zero-length frame is the exec stream terminator and is
|
|
72
|
+
* surfaced as an empty string so the caller can stop.
|
|
73
|
+
*/
|
|
74
|
+
class FrameReader {
|
|
75
|
+
buf = Buffer.alloc(0);
|
|
76
|
+
push(chunk) {
|
|
77
|
+
this.buf = this.buf.length === 0 ? Buffer.from(chunk) : Buffer.concat([this.buf, chunk]);
|
|
78
|
+
const out = [];
|
|
79
|
+
for (;;) {
|
|
80
|
+
const nl = this.buf.indexOf(0x0a); // '\n'
|
|
81
|
+
if (nl < 0 || nl < LENGTH_PREFIX_HEX) {
|
|
82
|
+
// Need at least the hex header + newline.
|
|
83
|
+
if (nl >= 0 && nl < LENGTH_PREFIX_HEX) {
|
|
84
|
+
throw new Error(`vsock transport: malformed frame header (newline at ${nl})`);
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
const header = this.buf.subarray(0, nl).toString('ascii');
|
|
89
|
+
const len = Number.parseInt(header, 16);
|
|
90
|
+
if (!Number.isInteger(len) || len < 0) {
|
|
91
|
+
throw new Error(`vsock transport: invalid frame length header ${JSON.stringify(header)}`);
|
|
92
|
+
}
|
|
93
|
+
const start = nl + 1;
|
|
94
|
+
if (this.buf.length < start + len)
|
|
95
|
+
break; // incomplete payload
|
|
96
|
+
const payload = this.buf.subarray(start, start + len).toString('utf8');
|
|
97
|
+
this.buf = this.buf.subarray(start + len);
|
|
98
|
+
out.push(payload);
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The transport. One instance per sandbox handle; every request opens
|
|
105
|
+
* a fresh connection (resume-survivable — no socket lingers across a
|
|
106
|
+
* resume to be silently severed). All four ops + the heartbeat go
|
|
107
|
+
* through {@link request} / {@link execute}.
|
|
108
|
+
*/
|
|
109
|
+
export class VsockAgentTransport {
|
|
110
|
+
handle;
|
|
111
|
+
connectTimeoutMs;
|
|
112
|
+
connectRetryBudgetMs;
|
|
113
|
+
connectRetryIntervalMs;
|
|
114
|
+
readIdleTimeoutMs;
|
|
115
|
+
constructor(handle, options = {}) {
|
|
116
|
+
this.handle = handle;
|
|
117
|
+
this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
118
|
+
this.connectRetryBudgetMs = options.connectRetryBudgetMs ?? DEFAULT_CONNECT_RETRY_BUDGET_MS;
|
|
119
|
+
this.connectRetryIntervalMs =
|
|
120
|
+
options.connectRetryIntervalMs ?? DEFAULT_CONNECT_RETRY_INTERVAL_MS;
|
|
121
|
+
this.readIdleTimeoutMs = options.readIdleTimeoutMs ?? DEFAULT_READ_IDLE_TIMEOUT_MS;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Dial the agent with the resume-survival retry budget. Resolves a
|
|
125
|
+
* connected, post-handshake socket. Retries connect/handshake
|
|
126
|
+
* failures (ECONNREFUSED while the agent re-listens after a resume,
|
|
127
|
+
* a dropped CONNECT ack) until the budget is exhausted.
|
|
128
|
+
*/
|
|
129
|
+
async dial() {
|
|
130
|
+
const deadline = Date.now() + this.connectRetryBudgetMs;
|
|
131
|
+
let lastErr;
|
|
132
|
+
for (;;) {
|
|
133
|
+
try {
|
|
134
|
+
return await this.connectOnce();
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
lastErr = err;
|
|
138
|
+
if (Date.now() >= deadline)
|
|
139
|
+
break;
|
|
140
|
+
await delay(this.connectRetryIntervalMs);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`vsock transport: could not connect to agent within ${this.connectRetryBudgetMs}ms (handle=${describeHandle(this.handle)}): ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`, { cause: lastErr });
|
|
144
|
+
}
|
|
145
|
+
connectOnce() {
|
|
146
|
+
const handle = this.handle;
|
|
147
|
+
if (handle.kind === 'mtls')
|
|
148
|
+
return this.connectOnceMtls(handle);
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
const path = handle.kind === 'unix' ? handle.path : handle.udsPath;
|
|
151
|
+
const socket = net.connect({ path });
|
|
152
|
+
let settled = false;
|
|
153
|
+
const timer = setTimeout(() => {
|
|
154
|
+
if (settled)
|
|
155
|
+
return;
|
|
156
|
+
settled = true;
|
|
157
|
+
socket.destroy();
|
|
158
|
+
reject(new Error(`connect/handshake timed out after ${this.connectTimeoutMs}ms`));
|
|
159
|
+
}, this.connectTimeoutMs);
|
|
160
|
+
timer.unref();
|
|
161
|
+
const fail = (err) => {
|
|
162
|
+
if (settled)
|
|
163
|
+
return;
|
|
164
|
+
settled = true;
|
|
165
|
+
clearTimeout(timer);
|
|
166
|
+
socket.destroy();
|
|
167
|
+
reject(err);
|
|
168
|
+
};
|
|
169
|
+
socket.once('error', fail);
|
|
170
|
+
socket.once('connect', () => {
|
|
171
|
+
if (handle.kind === 'unix') {
|
|
172
|
+
if (settled)
|
|
173
|
+
return;
|
|
174
|
+
settled = true;
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
socket.removeListener('error', fail);
|
|
177
|
+
resolve(socket);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// vsock: issue the firecracker hybrid-vsock CONNECT handshake
|
|
181
|
+
// and wait for the `OK <hostport>` ack line before handing the
|
|
182
|
+
// socket up for framed traffic.
|
|
183
|
+
const port = handle.port;
|
|
184
|
+
socket.write(`CONNECT ${port}\n`);
|
|
185
|
+
const ackReader = new LineReader();
|
|
186
|
+
const onData = (chunk) => {
|
|
187
|
+
const line = ackReader.push(chunk);
|
|
188
|
+
if (line === undefined)
|
|
189
|
+
return;
|
|
190
|
+
socket.removeListener('data', onData);
|
|
191
|
+
if (!/^OK\b/.test(line)) {
|
|
192
|
+
fail(new Error(`vsock CONNECT ${port} rejected: ${JSON.stringify(line)}`));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (settled)
|
|
196
|
+
return;
|
|
197
|
+
settled = true;
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
socket.removeListener('error', fail);
|
|
200
|
+
// Any bytes the ackReader over-read after the ack line are
|
|
201
|
+
// application framing; replay them into the caller.
|
|
202
|
+
const leftover = ackReader.takeRemainder();
|
|
203
|
+
if (leftover.length > 0)
|
|
204
|
+
socket.unshift(leftover);
|
|
205
|
+
resolve(socket);
|
|
206
|
+
};
|
|
207
|
+
socket.on('data', onData);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Dial the per-FC-host mTLS relay for an `mtls` handle.
|
|
213
|
+
*
|
|
214
|
+
* This is a pure TRANSPORT substitution for the `net.connect` arms:
|
|
215
|
+
* it `tls.connect`s the relay (presenting the fleet client cert and
|
|
216
|
+
* verifying the relay's server cert, `rejectUnauthorized: true`),
|
|
217
|
+
* asserts `socket.authorized`, then writes the single routing
|
|
218
|
+
* preamble line `SANDBOX <sandboxId>\n`. It does NOT write the guest
|
|
219
|
+
* `CONNECT 1024` line — the relay issues that host-side toward the
|
|
220
|
+
* jailed `v.sock`. The relay does NOT send an ack line: after the
|
|
221
|
+
* preamble it is a verbatim byte pump, so the caller hands the
|
|
222
|
+
* post-preamble socket straight up to the IDENTICAL framing loop the
|
|
223
|
+
* unix/vsock arms use (a `tls.TLSSocket` IS a `net.Socket`). The
|
|
224
|
+
* connect-retry budget, idle timeout, and the "fresh connection per
|
|
225
|
+
* request" resume-survival invariant are inherited unchanged.
|
|
226
|
+
*
|
|
227
|
+
* INTEGRATE CONTRACT (must match the relay, Track B): NO ack line.
|
|
228
|
+
* The relay reads `SANDBOX <id>\n`, then bridges; it writes nothing
|
|
229
|
+
* back until the agent does. If the relay is ever changed to emit an
|
|
230
|
+
* `OK` ack first, this arm must await that line (mirroring the vsock
|
|
231
|
+
* CONNECT-ack path) before resolving — today it does not.
|
|
232
|
+
*/
|
|
233
|
+
connectOnceMtls(handle) {
|
|
234
|
+
return new Promise((resolve, reject) => {
|
|
235
|
+
const socket = tls.connect({
|
|
236
|
+
host: handle.host,
|
|
237
|
+
port: handle.port,
|
|
238
|
+
ca: handle.tls.ca,
|
|
239
|
+
cert: handle.tls.cert,
|
|
240
|
+
key: handle.tls.key,
|
|
241
|
+
servername: handle.tls.servername,
|
|
242
|
+
rejectUnauthorized: true,
|
|
243
|
+
minVersion: 'TLSv1.3',
|
|
244
|
+
});
|
|
245
|
+
let settled = false;
|
|
246
|
+
const timer = setTimeout(() => {
|
|
247
|
+
if (settled)
|
|
248
|
+
return;
|
|
249
|
+
settled = true;
|
|
250
|
+
socket.destroy();
|
|
251
|
+
reject(new Error(`connect/handshake timed out after ${this.connectTimeoutMs}ms`));
|
|
252
|
+
}, this.connectTimeoutMs);
|
|
253
|
+
timer.unref();
|
|
254
|
+
const fail = (err) => {
|
|
255
|
+
if (settled)
|
|
256
|
+
return;
|
|
257
|
+
settled = true;
|
|
258
|
+
clearTimeout(timer);
|
|
259
|
+
socket.destroy();
|
|
260
|
+
reject(err);
|
|
261
|
+
};
|
|
262
|
+
socket.once('error', fail);
|
|
263
|
+
// `secureConnect` fires only after the cert chain is verified
|
|
264
|
+
// (rejectUnauthorized rejects a bad/missing-CA server via 'error'
|
|
265
|
+
// before this). Belt-and-suspenders: assert `authorized` too.
|
|
266
|
+
socket.once('secureConnect', () => {
|
|
267
|
+
if (settled)
|
|
268
|
+
return;
|
|
269
|
+
if (!socket.authorized) {
|
|
270
|
+
fail(new Error(`mtls transport: relay server cert not authorized: ${socket.authorizationError ?? 'unknown'}`));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
settled = true;
|
|
274
|
+
clearTimeout(timer);
|
|
275
|
+
socket.removeListener('error', fail);
|
|
276
|
+
// Routing preamble — the host-relay analogue of the vsock
|
|
277
|
+
// `CONNECT <port>` line. The relay consumes it, resolves the
|
|
278
|
+
// jailed v.sock, and issues the guest CONNECT itself; the
|
|
279
|
+
// caller writes NOTHING further until the framing loop.
|
|
280
|
+
socket.write(`SANDBOX ${handle.sandboxId}\n`);
|
|
281
|
+
resolve(socket);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Send one framed request and read one framed JSON reply (file-IO +
|
|
287
|
+
* healthz). Applies the read-idle timeout so a post-resume hung read
|
|
288
|
+
* is torn down rather than wedging the caller.
|
|
289
|
+
*/
|
|
290
|
+
async request(req) {
|
|
291
|
+
const socket = await this.dial();
|
|
292
|
+
return await new Promise((resolve, reject) => {
|
|
293
|
+
const reader = new FrameReader();
|
|
294
|
+
let settled = false;
|
|
295
|
+
const idle = new IdleTimer(this.readIdleTimeoutMs, () => {
|
|
296
|
+
if (settled)
|
|
297
|
+
return;
|
|
298
|
+
settled = true;
|
|
299
|
+
socket.destroy();
|
|
300
|
+
reject(new Error(`vsock transport: read idle timeout after ${this.readIdleTimeoutMs}ms`));
|
|
301
|
+
});
|
|
302
|
+
const finish = (err, value) => {
|
|
303
|
+
if (settled)
|
|
304
|
+
return;
|
|
305
|
+
settled = true;
|
|
306
|
+
idle.clear();
|
|
307
|
+
socket.destroy();
|
|
308
|
+
if (err)
|
|
309
|
+
reject(err);
|
|
310
|
+
else
|
|
311
|
+
resolve(value);
|
|
312
|
+
};
|
|
313
|
+
socket.on('data', (chunk) => {
|
|
314
|
+
idle.bump();
|
|
315
|
+
let frames;
|
|
316
|
+
try {
|
|
317
|
+
frames = reader.push(chunk);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
finish(err instanceof Error ? err : new Error(String(err)));
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const first = frames[0];
|
|
324
|
+
if (first !== undefined) {
|
|
325
|
+
try {
|
|
326
|
+
finish(null, JSON.parse(first));
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
finish(err instanceof Error ? err : new Error(String(err)));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
socket.once('error', (err) => finish(err));
|
|
334
|
+
socket.once('close', () => finish(new Error('vsock transport: socket closed before reply')));
|
|
335
|
+
idle.bump();
|
|
336
|
+
socket.write(frame(JSON.stringify(req)));
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Send an `/execute` and accumulate the streamed NDJSON frames into a
|
|
341
|
+
* {@link SandboxExecResult} via the shared {@link ExecResultAccumulator}.
|
|
342
|
+
* The agent terminates the stream with a zero-length frame.
|
|
343
|
+
*/
|
|
344
|
+
async execute(body) {
|
|
345
|
+
const socket = await this.dial();
|
|
346
|
+
const start = Date.now();
|
|
347
|
+
return await new Promise((resolve, reject) => {
|
|
348
|
+
const reader = new FrameReader();
|
|
349
|
+
const acc = new ExecResultAccumulator(start);
|
|
350
|
+
let settled = false;
|
|
351
|
+
const idle = new IdleTimer(this.readIdleTimeoutMs, () => {
|
|
352
|
+
if (settled)
|
|
353
|
+
return;
|
|
354
|
+
settled = true;
|
|
355
|
+
socket.destroy();
|
|
356
|
+
reject(new Error(`vsock transport: exec read idle timeout after ${this.readIdleTimeoutMs}ms`));
|
|
357
|
+
});
|
|
358
|
+
const finish = (err, value) => {
|
|
359
|
+
if (settled)
|
|
360
|
+
return;
|
|
361
|
+
settled = true;
|
|
362
|
+
idle.clear();
|
|
363
|
+
socket.destroy();
|
|
364
|
+
if (err)
|
|
365
|
+
reject(err);
|
|
366
|
+
else
|
|
367
|
+
resolve(value);
|
|
368
|
+
};
|
|
369
|
+
socket.on('data', (chunk) => {
|
|
370
|
+
idle.bump();
|
|
371
|
+
let frames;
|
|
372
|
+
try {
|
|
373
|
+
frames = reader.push(chunk);
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
finish(err instanceof Error ? err : new Error(String(err)));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
for (const payload of frames) {
|
|
380
|
+
if (payload.length === 0) {
|
|
381
|
+
// Zero-length terminator. If a result was seen, we are
|
|
382
|
+
// done; otherwise the stream ended without a result.
|
|
383
|
+
finish(acc.done ? null : new Error('exec stream ended without a result event'), acc.finish());
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const event = parseExecLine(payload);
|
|
387
|
+
if (!event)
|
|
388
|
+
continue; // malformed line — swallow (docker parity)
|
|
389
|
+
try {
|
|
390
|
+
if (acc.push(event)) {
|
|
391
|
+
// Terminal result seen; wait for terminator but we can
|
|
392
|
+
// resolve now — the agent closes after the terminator.
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
finish(err instanceof Error ? err : new Error(String(err)));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
socket.once('error', (err) => finish(err));
|
|
402
|
+
socket.once('close', () => {
|
|
403
|
+
// Stream closed. If a result arrived, deliver it (some agents
|
|
404
|
+
// close right after the terminator without a separate event);
|
|
405
|
+
// otherwise it is a truncated stream.
|
|
406
|
+
finish(acc.done ? null : new Error('vsock transport: socket closed before exec result'), acc.finish());
|
|
407
|
+
});
|
|
408
|
+
idle.bump();
|
|
409
|
+
socket.write(frame(JSON.stringify({ op: 'execute', body })));
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/** Liveness probe. Returns true on an `{ ok: true }` healthz reply. */
|
|
413
|
+
async healthz() {
|
|
414
|
+
try {
|
|
415
|
+
const res = await this.request({ op: 'healthz' });
|
|
416
|
+
return res.ok === true;
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Poll the agent until a healthz succeeds or the timeout elapses.
|
|
424
|
+
* Mirrors the HTTP `waitForWorkerReady`, but over the vsock dialer
|
|
425
|
+
* (which already carries connect retry) — used by the backend's
|
|
426
|
+
* post-create readiness fence.
|
|
427
|
+
*/
|
|
428
|
+
async waitForReady(timeoutMs, pollIntervalMs) {
|
|
429
|
+
const deadline = Date.now() + timeoutMs;
|
|
430
|
+
let lastErr;
|
|
431
|
+
while (Date.now() < deadline) {
|
|
432
|
+
try {
|
|
433
|
+
if (await this.healthz())
|
|
434
|
+
return;
|
|
435
|
+
lastErr = new Error('healthz returned not-ok');
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
lastErr = err;
|
|
439
|
+
}
|
|
440
|
+
await delay(pollIntervalMs);
|
|
441
|
+
}
|
|
442
|
+
throw new Error(`vsock transport: agent did not become ready within ${timeoutMs}ms: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
|
|
443
|
+
}
|
|
444
|
+
async writeFile(path, content) {
|
|
445
|
+
const res = await this.request({
|
|
446
|
+
op: 'write-file',
|
|
447
|
+
body: { path, content: content.toString('base64'), encoding: 'base64' },
|
|
448
|
+
});
|
|
449
|
+
if (!res.ok) {
|
|
450
|
+
throw new Error(res.error ?? 'write-file failed');
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async readFile(path) {
|
|
454
|
+
const res = await this.request({
|
|
455
|
+
op: 'read-file',
|
|
456
|
+
body: { path, encoding: 'base64' },
|
|
457
|
+
});
|
|
458
|
+
if (!res.ok || typeof res.content !== 'string') {
|
|
459
|
+
throw new Error(res.error ?? 'read-file: no content');
|
|
460
|
+
}
|
|
461
|
+
return Buffer.from(res.content, 'base64');
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// ---------------------------------------------------------------------------
|
|
465
|
+
// Small helpers
|
|
466
|
+
// ---------------------------------------------------------------------------
|
|
467
|
+
/** Reads exactly one `\n`-terminated line (used for the CONNECT ack). */
|
|
468
|
+
class LineReader {
|
|
469
|
+
buf = Buffer.alloc(0);
|
|
470
|
+
remainder = Buffer.alloc(0);
|
|
471
|
+
push(chunk) {
|
|
472
|
+
this.buf = Buffer.concat([this.buf, chunk]);
|
|
473
|
+
const nl = this.buf.indexOf(0x0a);
|
|
474
|
+
if (nl < 0)
|
|
475
|
+
return undefined;
|
|
476
|
+
const line = this.buf.subarray(0, nl).toString('utf8');
|
|
477
|
+
this.remainder = Buffer.from(this.buf.subarray(nl + 1));
|
|
478
|
+
return line;
|
|
479
|
+
}
|
|
480
|
+
takeRemainder() {
|
|
481
|
+
const r = this.remainder;
|
|
482
|
+
this.remainder = Buffer.alloc(0);
|
|
483
|
+
return r;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/** Resets a timer on every byte; fires `onIdle` after `ms` of silence. */
|
|
487
|
+
class IdleTimer {
|
|
488
|
+
ms;
|
|
489
|
+
onIdle;
|
|
490
|
+
timer;
|
|
491
|
+
constructor(ms, onIdle) {
|
|
492
|
+
this.ms = ms;
|
|
493
|
+
this.onIdle = onIdle;
|
|
494
|
+
}
|
|
495
|
+
bump() {
|
|
496
|
+
if (this.ms <= 0)
|
|
497
|
+
return;
|
|
498
|
+
this.clear();
|
|
499
|
+
this.timer = setTimeout(this.onIdle, this.ms);
|
|
500
|
+
this.timer.unref();
|
|
501
|
+
}
|
|
502
|
+
clear() {
|
|
503
|
+
if (this.timer)
|
|
504
|
+
clearTimeout(this.timer);
|
|
505
|
+
this.timer = undefined;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function delay(ms) {
|
|
509
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
510
|
+
}
|
|
511
|
+
function describeHandle(handle) {
|
|
512
|
+
switch (handle.kind) {
|
|
513
|
+
case 'unix':
|
|
514
|
+
return `unix:${handle.path}`;
|
|
515
|
+
case 'vsock':
|
|
516
|
+
return `vsock:${handle.udsPath}#${handle.port}`;
|
|
517
|
+
case 'mtls':
|
|
518
|
+
return `mtls:${handle.host}:${handle.port}/${handle.sandboxId}`;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
// Internal framing helpers exported for the transport unit tests so the
|
|
522
|
+
// agent stand-in and the round-trip assertions share one framing impl.
|
|
523
|
+
export const __framing = { frame, FrameReader };
|
|
524
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../src/backends/firecracker/transport.ts"],"names":[],"mappings":"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"}
|