@namzu/sandbox 11.0.0 → 12.0.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 +15 -0
- package/README.md +47 -7
- package/dist/backends/aci-standby-pool/index.d.ts.map +1 -1
- package/dist/backends/aci-standby-pool/index.js +3 -1
- package/dist/backends/aci-standby-pool/index.js.map +1 -1
- package/dist/backends/docker/index.d.ts.map +1 -1
- package/dist/backends/docker/index.js +3 -1
- package/dist/backends/docker/index.js.map +1 -1
- package/dist/backends/firecracker/index.d.ts +15 -14
- package/dist/backends/firecracker/index.d.ts.map +1 -1
- package/dist/backends/firecracker/index.js +36 -28
- package/dist/backends/firecracker/index.js.map +1 -1
- package/dist/backends/firecracker/protocol.d.ts +58 -0
- package/dist/backends/firecracker/protocol.d.ts.map +1 -1
- package/dist/backends/firecracker/protocol.js.map +1 -1
- package/dist/backends/firecracker/transport.d.ts +23 -3
- package/dist/backends/firecracker/transport.d.ts.map +1 -1
- package/dist/backends/firecracker/transport.js +305 -5
- package/dist/backends/firecracker/transport.js.map +1 -1
- package/dist/backends/http-worker-client.d.ts +3 -7
- package/dist/backends/http-worker-client.d.ts.map +1 -1
- package/dist/backends/http-worker-client.js +5 -9
- package/dist/backends/http-worker-client.js.map +1 -1
- package/dist/backends/readiness.d.ts.map +1 -1
- package/dist/backends/readiness.js +27 -6
- package/dist/backends/readiness.js.map +1 -1
- package/dist/backends/remote-execution-controller.d.ts +7 -10
- package/dist/backends/remote-execution-controller.d.ts.map +1 -1
- package/dist/backends/remote-execution-controller.js +7 -33
- package/dist/backends/remote-execution-controller.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/backends/aci-standby-pool/index.ts +5 -1
- package/src/backends/docker/index.ts +5 -1
- package/src/backends/firecracker/index.ts +52 -34
- package/src/backends/firecracker/protocol.ts +51 -0
- package/src/backends/firecracker/transport.ts +339 -8
- package/src/backends/http-worker-client.ts +5 -10
- package/src/backends/readiness.ts +38 -6
- package/src/backends/remote-execution-controller.ts +9 -55
- package/src/index.ts +1 -0
|
@@ -74,6 +74,64 @@ export interface ReadFileRequest {
|
|
|
74
74
|
readonly path: string;
|
|
75
75
|
readonly encoding: 'base64';
|
|
76
76
|
}
|
|
77
|
+
/** Initial request for one interactive terminal process in the guest. */
|
|
78
|
+
export interface TerminalOpenRequest {
|
|
79
|
+
readonly command?: string;
|
|
80
|
+
readonly args?: readonly string[];
|
|
81
|
+
readonly cwd?: string;
|
|
82
|
+
readonly env?: Record<string, string>;
|
|
83
|
+
readonly cols: number;
|
|
84
|
+
readonly rows: number;
|
|
85
|
+
}
|
|
86
|
+
/** Host → guest messages after the terminal stream reports ready. */
|
|
87
|
+
export type TerminalInputEvent = {
|
|
88
|
+
readonly type: 'input';
|
|
89
|
+
readonly data: string;
|
|
90
|
+
} | {
|
|
91
|
+
readonly type: 'resize';
|
|
92
|
+
readonly cols: number;
|
|
93
|
+
readonly rows: number;
|
|
94
|
+
} | {
|
|
95
|
+
readonly type: 'kill';
|
|
96
|
+
readonly signal?: string;
|
|
97
|
+
};
|
|
98
|
+
/** Guest → host events carried for the lifetime of the terminal stream. */
|
|
99
|
+
export type TerminalOutputEvent = {
|
|
100
|
+
readonly type: 'ready';
|
|
101
|
+
} | {
|
|
102
|
+
readonly type: 'data';
|
|
103
|
+
readonly data: string;
|
|
104
|
+
} | {
|
|
105
|
+
readonly type: 'exit';
|
|
106
|
+
readonly exitCode: number;
|
|
107
|
+
readonly signal?: number;
|
|
108
|
+
} | {
|
|
109
|
+
readonly type: 'error';
|
|
110
|
+
readonly error: string;
|
|
111
|
+
};
|
|
112
|
+
export interface TcpConnectRequest {
|
|
113
|
+
readonly host: '127.0.0.1' | '::1';
|
|
114
|
+
readonly port: number;
|
|
115
|
+
}
|
|
116
|
+
export type TcpInputEvent = {
|
|
117
|
+
readonly type: 'data';
|
|
118
|
+
readonly data: string;
|
|
119
|
+
} | {
|
|
120
|
+
readonly type: 'end';
|
|
121
|
+
} | {
|
|
122
|
+
readonly type: 'destroy';
|
|
123
|
+
};
|
|
124
|
+
export type TcpOutputEvent = {
|
|
125
|
+
readonly type: 'ready';
|
|
126
|
+
} | {
|
|
127
|
+
readonly type: 'data';
|
|
128
|
+
readonly data: string;
|
|
129
|
+
} | {
|
|
130
|
+
readonly type: 'end';
|
|
131
|
+
} | {
|
|
132
|
+
readonly type: 'error';
|
|
133
|
+
readonly error: string;
|
|
134
|
+
};
|
|
77
135
|
/** `/read-file` response. `content` is base64 on success. */
|
|
78
136
|
export interface ReadFileResponse {
|
|
79
137
|
readonly ok: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAQvE;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAChC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxD;IACA,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;IAClC,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CACjC,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAMrD,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAC3B;AAED,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,iCAAiC;AACjC,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAC3B;AAED,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACvB;AAOD;;;;;;;;;GASG;AACH,qBAAa,qBAAqB;IACjC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;gBAE7C,KAAK,GAAE,MAAmB,EAAE,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC;IAKjF;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO;IA4B/B,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,sEAAsE;IACtE,MAAM,IAAI,iBAAiB;CAe3B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAkCjE"}
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
* socket to be silently severed by a resume), which makes the
|
|
53
53
|
* transport resume-survivable by construction.
|
|
54
54
|
*/
|
|
55
|
-
import type { SandboxExecOptions, SandboxExecResult } from '@namzu/sdk';
|
|
56
|
-
import { type ExecRequest, type ReadFileRequest, type WriteFileRequest } from './protocol.js';
|
|
55
|
+
import type { OpenTerminalOptions, SandboxExecOptions, SandboxExecResult, SandboxTcpConnectOptions, SandboxTcpConnection, TerminalSession } from '@namzu/sdk';
|
|
56
|
+
import { type ExecRequest, type ReadFileRequest, type TcpConnectRequest, type TerminalOpenRequest, type WriteFileRequest } from './protocol.js';
|
|
57
57
|
/**
|
|
58
58
|
* An addressable agent endpoint. The orchestrator hands one of these
|
|
59
59
|
* back per sandbox (`create()` response → `vsock endpoint`).
|
|
@@ -153,6 +153,12 @@ export type AgentRequest = {
|
|
|
153
153
|
} | {
|
|
154
154
|
readonly op: 'write-file';
|
|
155
155
|
readonly body: WriteFileRequest;
|
|
156
|
+
} | {
|
|
157
|
+
readonly op: 'terminal';
|
|
158
|
+
readonly body: TerminalOpenRequest;
|
|
159
|
+
} | {
|
|
160
|
+
readonly op: 'tcp-connect';
|
|
161
|
+
readonly body: TcpConnectRequest;
|
|
156
162
|
} | {
|
|
157
163
|
readonly op: 'healthz';
|
|
158
164
|
};
|
|
@@ -172,6 +178,8 @@ export interface VsockTransportOptions {
|
|
|
172
178
|
*/
|
|
173
179
|
readonly readIdleTimeoutMs?: number;
|
|
174
180
|
}
|
|
181
|
+
/** Exact guest wire version accepted by this Firecracker transport. */
|
|
182
|
+
export declare const FIRECRACKER_AGENT_PROTOCOL_VERSION: 2;
|
|
175
183
|
declare function frame(payload: string): Buffer;
|
|
176
184
|
/**
|
|
177
185
|
* Incremental frame reader. Feed it socket chunks; it yields complete
|
|
@@ -250,7 +258,7 @@ export declare class VsockAgentTransport {
|
|
|
250
258
|
exec(command: string, argv?: string[], opts?: SandboxExecOptions): Promise<SandboxExecResult>;
|
|
251
259
|
private reserveExecution;
|
|
252
260
|
private cancelExecution;
|
|
253
|
-
/**
|
|
261
|
+
/** Readiness probe. A healthy guest must also speak the exact host protocol. */
|
|
254
262
|
healthz(signal?: AbortSignal): Promise<boolean>;
|
|
255
263
|
/**
|
|
256
264
|
* Poll the agent until a healthz succeeds or the timeout elapses.
|
|
@@ -261,6 +269,18 @@ export declare class VsockAgentTransport {
|
|
|
261
269
|
waitForReady(timeoutMs: number, pollIntervalMs: number, signal?: AbortSignal): Promise<void>;
|
|
262
270
|
writeFile(path: string, content: Buffer): Promise<void>;
|
|
263
271
|
readFile(path: string): Promise<Buffer>;
|
|
272
|
+
/**
|
|
273
|
+
* Open a real PTY owned by the in-VM agent.
|
|
274
|
+
*
|
|
275
|
+
* Unlike `execute`, this keeps one framed connection open for the complete
|
|
276
|
+
* interactive lifetime: guest output and exit events flow toward the host,
|
|
277
|
+
* while input/resize/kill events flow back on the same ordered stream. The
|
|
278
|
+
* browser never reaches this transport directly; the runtime gateway owns
|
|
279
|
+
* the session and its authenticated WebSocket attachment.
|
|
280
|
+
*/
|
|
281
|
+
openTerminal(options: OpenTerminalOptions): Promise<TerminalSession>;
|
|
282
|
+
/** Open one TCP stream to a service listening on guest loopback. */
|
|
283
|
+
openTcpConnection(options: SandboxTcpConnectOptions): Promise<SandboxTcpConnection>;
|
|
264
284
|
}
|
|
265
285
|
export declare const __framing: {
|
|
266
286
|
frame: typeof frame;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/backends/firecracker/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAKH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/backends/firecracker/transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
import net from 'node:net';
|
|
56
56
|
import tls from 'node:tls';
|
|
57
57
|
import { OperationDeadline, OperationDeadlineExpired } from '../readiness.js';
|
|
58
|
-
import {
|
|
58
|
+
import { REMOTE_EXECUTION_PROTOCOL_VERSION, RemoteCancellationUnknownError, RemoteExecutionController, RemoteProtocolError, } from '../remote-execution-controller.js';
|
|
59
59
|
import { ExecResultAccumulator, parseExecLine, } from './protocol.js';
|
|
60
60
|
const DEFAULT_CONNECT_TIMEOUT_MS = 5_000;
|
|
61
61
|
const DEFAULT_CONNECT_RETRY_BUDGET_MS = 30_000;
|
|
@@ -69,6 +69,8 @@ const DEFAULT_EXECUTION_TIMEOUT_MS = 5 * 60_000;
|
|
|
69
69
|
const EXECUTION_TRANSPORT_GRACE_MS = 10_000;
|
|
70
70
|
const POST_RESPONSE_CLOSE_TIMEOUT_MS = 1_000;
|
|
71
71
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
72
|
+
/** Exact guest wire version accepted by this Firecracker transport. */
|
|
73
|
+
export const FIRECRACKER_AGENT_PROTOCOL_VERSION = REMOTE_EXECUTION_PROTOCOL_VERSION;
|
|
72
74
|
/** Framing: 8 hex digits of payload byte length, then `\n`, then payload. */
|
|
73
75
|
const LENGTH_PREFIX_HEX = 8;
|
|
74
76
|
function frame(payload) {
|
|
@@ -530,7 +532,7 @@ export class VsockAgentTransport {
|
|
|
530
532
|
if (response.ok === false &&
|
|
531
533
|
typeof response.error === 'string' &&
|
|
532
534
|
response.error.startsWith('unknown_op:')) {
|
|
533
|
-
throw new
|
|
535
|
+
throw new RemoteProtocolError(`The microVM guest does not implement Firecracker agent protocol ${FIRECRACKER_AGENT_PROTOCOL_VERSION}. Rebuild the golden image from the same Namzu release before admitting commands.`);
|
|
534
536
|
}
|
|
535
537
|
if (response.ok === false && response.error === 'agent_retiring') {
|
|
536
538
|
throw new RemoteCancellationUnknownError('The microVM agent has fenced itself because an earlier process-group shutdown could not be confirmed; the sandbox must be retired.');
|
|
@@ -540,15 +542,23 @@ export class VsockAgentTransport {
|
|
|
540
542
|
async cancelExecution(executionId, signal) {
|
|
541
543
|
return await this.request({ op: 'cancel-execution', body: { executionId } }, signal);
|
|
542
544
|
}
|
|
543
|
-
/**
|
|
545
|
+
/** Readiness probe. A healthy guest must also speak the exact host protocol. */
|
|
544
546
|
async healthz(signal) {
|
|
545
547
|
try {
|
|
546
548
|
const res = await this.request({ op: 'healthz' }, signal);
|
|
547
|
-
|
|
549
|
+
if (res.ok !== true)
|
|
550
|
+
return false;
|
|
551
|
+
if (res.protocolVersion !== FIRECRACKER_AGENT_PROTOCOL_VERSION) {
|
|
552
|
+
const actual = res.protocolVersion === undefined ? 'missing' : JSON.stringify(res.protocolVersion);
|
|
553
|
+
throw new RemoteProtocolError(`Firecracker guest protocol version mismatch: expected ${FIRECRACKER_AGENT_PROTOCOL_VERSION}, received ${actual}. Rebuild the golden image from the same Namzu release.`);
|
|
554
|
+
}
|
|
555
|
+
return true;
|
|
548
556
|
}
|
|
549
|
-
catch {
|
|
557
|
+
catch (error) {
|
|
550
558
|
if (signal?.aborted)
|
|
551
559
|
throw signal.reason;
|
|
560
|
+
if (error instanceof RemoteProtocolError)
|
|
561
|
+
throw error;
|
|
552
562
|
return false;
|
|
553
563
|
}
|
|
554
564
|
}
|
|
@@ -569,6 +579,8 @@ export class VsockAgentTransport {
|
|
|
569
579
|
}
|
|
570
580
|
catch (err) {
|
|
571
581
|
lastErr = err;
|
|
582
|
+
if (err instanceof RemoteProtocolError)
|
|
583
|
+
throw err;
|
|
572
584
|
if (err instanceof OperationDeadlineExpired)
|
|
573
585
|
break;
|
|
574
586
|
}
|
|
@@ -602,6 +614,294 @@ export class VsockAgentTransport {
|
|
|
602
614
|
}
|
|
603
615
|
return Buffer.from(res.content, 'base64');
|
|
604
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* Open a real PTY owned by the in-VM agent.
|
|
619
|
+
*
|
|
620
|
+
* Unlike `execute`, this keeps one framed connection open for the complete
|
|
621
|
+
* interactive lifetime: guest output and exit events flow toward the host,
|
|
622
|
+
* while input/resize/kill events flow back on the same ordered stream. The
|
|
623
|
+
* browser never reaches this transport directly; the runtime gateway owns
|
|
624
|
+
* the session and its authenticated WebSocket attachment.
|
|
625
|
+
*/
|
|
626
|
+
async openTerminal(options) {
|
|
627
|
+
const socket = await this.dial();
|
|
628
|
+
const request = {
|
|
629
|
+
...(options.command !== undefined ? { command: options.command } : {}),
|
|
630
|
+
...(options.args !== undefined ? { args: options.args } : {}),
|
|
631
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
632
|
+
...(options.env !== undefined ? { env: { ...options.env } } : {}),
|
|
633
|
+
cols: options.size.cols,
|
|
634
|
+
rows: options.size.rows,
|
|
635
|
+
};
|
|
636
|
+
return await new Promise((resolve, reject) => {
|
|
637
|
+
const KILL_GRACE_MS = 5_000;
|
|
638
|
+
const reader = new FrameReader();
|
|
639
|
+
const listeners = new Set();
|
|
640
|
+
const buffered = [];
|
|
641
|
+
let bufferedBytes = 0;
|
|
642
|
+
let ready = false;
|
|
643
|
+
let settled = false;
|
|
644
|
+
let killTimer;
|
|
645
|
+
let resolveExit;
|
|
646
|
+
const exited = new Promise((done) => {
|
|
647
|
+
resolveExit = done;
|
|
648
|
+
});
|
|
649
|
+
const idle = new IdleTimer(this.readIdleTimeoutMs, () => {
|
|
650
|
+
finish(new Error(`vsock transport: terminal read idle timeout after ${this.readIdleTimeoutMs}ms`));
|
|
651
|
+
});
|
|
652
|
+
const finish = (error, exit = { exitCode: -1 }) => {
|
|
653
|
+
if (settled)
|
|
654
|
+
return;
|
|
655
|
+
settled = true;
|
|
656
|
+
idle.clear();
|
|
657
|
+
if (killTimer)
|
|
658
|
+
clearTimeout(killTimer);
|
|
659
|
+
socket.destroy();
|
|
660
|
+
listeners.clear();
|
|
661
|
+
resolveExit(exit);
|
|
662
|
+
if (!ready)
|
|
663
|
+
reject(error ?? new Error('terminal exited before readiness'));
|
|
664
|
+
};
|
|
665
|
+
const send = (event) => {
|
|
666
|
+
if (settled)
|
|
667
|
+
return;
|
|
668
|
+
socket.write(frame(JSON.stringify(event)));
|
|
669
|
+
};
|
|
670
|
+
const session = {
|
|
671
|
+
write(data) {
|
|
672
|
+
send({ type: 'input', data });
|
|
673
|
+
},
|
|
674
|
+
resize(size) {
|
|
675
|
+
send({ type: 'resize', cols: size.cols, rows: size.rows });
|
|
676
|
+
},
|
|
677
|
+
onData(listener) {
|
|
678
|
+
listeners.add(listener);
|
|
679
|
+
if (buffered.length > 0) {
|
|
680
|
+
const pending = buffered.splice(0);
|
|
681
|
+
bufferedBytes = 0;
|
|
682
|
+
queueMicrotask(() => {
|
|
683
|
+
if (!listeners.has(listener))
|
|
684
|
+
return;
|
|
685
|
+
for (const chunk of pending)
|
|
686
|
+
listener(chunk);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
return () => listeners.delete(listener);
|
|
690
|
+
},
|
|
691
|
+
exited,
|
|
692
|
+
kill(signal) {
|
|
693
|
+
send({ type: 'kill', ...(signal !== undefined ? { signal } : {}) });
|
|
694
|
+
// A wedged guest must not pin sandbox.destroy() forever. The normal
|
|
695
|
+
// path reports the real exit; the deadline only severs an
|
|
696
|
+
// unresponsive transport so the owning microVM can be reclaimed.
|
|
697
|
+
if (!settled && !killTimer) {
|
|
698
|
+
killTimer = setTimeout(() => finish(null), KILL_GRACE_MS);
|
|
699
|
+
killTimer.unref?.();
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
};
|
|
703
|
+
socket.on('data', (chunk) => {
|
|
704
|
+
if (!ready)
|
|
705
|
+
idle.bump();
|
|
706
|
+
let payloads;
|
|
707
|
+
try {
|
|
708
|
+
payloads = reader.push(chunk);
|
|
709
|
+
}
|
|
710
|
+
catch (err) {
|
|
711
|
+
finish(err instanceof Error ? err : new Error(String(err)));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
for (const payload of payloads) {
|
|
715
|
+
let event;
|
|
716
|
+
try {
|
|
717
|
+
event = JSON.parse(payload);
|
|
718
|
+
}
|
|
719
|
+
catch (err) {
|
|
720
|
+
finish(err instanceof Error ? err : new Error(String(err)));
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (event.type === 'ready') {
|
|
724
|
+
if (!ready) {
|
|
725
|
+
ready = true;
|
|
726
|
+
// Once ready, an interactive shell may legitimately sit silent
|
|
727
|
+
// for hours. Runtime/session TTL owns idle cleanup; a transport
|
|
728
|
+
// read timer would incorrectly kill a healthy quiet terminal.
|
|
729
|
+
idle.clear();
|
|
730
|
+
resolve(session);
|
|
731
|
+
}
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (event.type === 'data') {
|
|
735
|
+
if (listeners.size === 0) {
|
|
736
|
+
buffered.push(event.data);
|
|
737
|
+
bufferedBytes += Buffer.byteLength(event.data);
|
|
738
|
+
while (bufferedBytes > 1024 * 1024 && buffered.length > 1) {
|
|
739
|
+
bufferedBytes -= Buffer.byteLength(buffered.shift() ?? '');
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
for (const listener of listeners)
|
|
744
|
+
listener(event.data);
|
|
745
|
+
}
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (event.type === 'exit') {
|
|
749
|
+
finish(null, {
|
|
750
|
+
exitCode: event.exitCode,
|
|
751
|
+
...(event.signal !== undefined ? { signal: event.signal } : {}),
|
|
752
|
+
});
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
finish(new Error(event.error));
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
socket.once('error', (err) => finish(err));
|
|
760
|
+
socket.once('close', () => finish(new Error('vsock transport: terminal socket closed before exit')));
|
|
761
|
+
idle.bump();
|
|
762
|
+
socket.write(frame(JSON.stringify({
|
|
763
|
+
op: 'terminal',
|
|
764
|
+
body: request,
|
|
765
|
+
})));
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
/** Open one TCP stream to a service listening on guest loopback. */
|
|
769
|
+
async openTcpConnection(options) {
|
|
770
|
+
if (!Number.isInteger(options.port) || options.port < 1 || options.port > 65_535) {
|
|
771
|
+
throw new Error('tcp connection port must be an integer in [1, 65535]');
|
|
772
|
+
}
|
|
773
|
+
const host = options.host ?? '127.0.0.1';
|
|
774
|
+
if (host !== '127.0.0.1' && host !== '::1') {
|
|
775
|
+
throw new Error('firecracker TCP connections are restricted to guest loopback');
|
|
776
|
+
}
|
|
777
|
+
const socket = await this.dial();
|
|
778
|
+
const request = { host, port: options.port };
|
|
779
|
+
return await new Promise((resolve, reject) => {
|
|
780
|
+
const reader = new FrameReader();
|
|
781
|
+
const listeners = new Set();
|
|
782
|
+
const buffered = [];
|
|
783
|
+
let bufferedBytes = 0;
|
|
784
|
+
let ready = false;
|
|
785
|
+
let settled = false;
|
|
786
|
+
let resolveClosed;
|
|
787
|
+
const closed = new Promise((done) => {
|
|
788
|
+
resolveClosed = done;
|
|
789
|
+
});
|
|
790
|
+
const idle = new IdleTimer(this.readIdleTimeoutMs, () => {
|
|
791
|
+
finish(new Error(`vsock transport: TCP connect idle timeout after ${this.readIdleTimeoutMs}ms`));
|
|
792
|
+
});
|
|
793
|
+
const finish = (error) => {
|
|
794
|
+
if (settled)
|
|
795
|
+
return;
|
|
796
|
+
settled = true;
|
|
797
|
+
idle.clear();
|
|
798
|
+
socket.destroy();
|
|
799
|
+
listeners.clear();
|
|
800
|
+
resolveClosed();
|
|
801
|
+
if (!ready)
|
|
802
|
+
reject(error ?? new Error('TCP stream closed before readiness'));
|
|
803
|
+
};
|
|
804
|
+
const send = (event) => {
|
|
805
|
+
return !settled && socket.write(frame(JSON.stringify(event)));
|
|
806
|
+
};
|
|
807
|
+
const connection = {
|
|
808
|
+
write(data) {
|
|
809
|
+
const bytes = typeof data === 'string' ? Buffer.from(data) : Buffer.from(data);
|
|
810
|
+
return send({ type: 'data', data: bytes.toString('base64') });
|
|
811
|
+
},
|
|
812
|
+
end() {
|
|
813
|
+
send({ type: 'end' });
|
|
814
|
+
},
|
|
815
|
+
destroy() {
|
|
816
|
+
send({ type: 'destroy' });
|
|
817
|
+
finish(null);
|
|
818
|
+
},
|
|
819
|
+
pause() {
|
|
820
|
+
socket.pause();
|
|
821
|
+
},
|
|
822
|
+
resume() {
|
|
823
|
+
if (!settled)
|
|
824
|
+
socket.resume();
|
|
825
|
+
},
|
|
826
|
+
onData(listener) {
|
|
827
|
+
listeners.add(listener);
|
|
828
|
+
if (buffered.length > 0) {
|
|
829
|
+
const pending = buffered.splice(0);
|
|
830
|
+
bufferedBytes = 0;
|
|
831
|
+
queueMicrotask(() => {
|
|
832
|
+
if (!listeners.has(listener))
|
|
833
|
+
return;
|
|
834
|
+
for (const chunk of pending)
|
|
835
|
+
listener(chunk);
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
return () => listeners.delete(listener);
|
|
839
|
+
},
|
|
840
|
+
onDrain(listener) {
|
|
841
|
+
socket.on('drain', listener);
|
|
842
|
+
return () => socket.off('drain', listener);
|
|
843
|
+
},
|
|
844
|
+
closed,
|
|
845
|
+
};
|
|
846
|
+
socket.on('data', (chunk) => {
|
|
847
|
+
if (!ready)
|
|
848
|
+
idle.bump();
|
|
849
|
+
let payloads;
|
|
850
|
+
try {
|
|
851
|
+
payloads = reader.push(chunk);
|
|
852
|
+
}
|
|
853
|
+
catch (error) {
|
|
854
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
for (const payload of payloads) {
|
|
858
|
+
let event;
|
|
859
|
+
try {
|
|
860
|
+
event = JSON.parse(payload);
|
|
861
|
+
}
|
|
862
|
+
catch (error) {
|
|
863
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
if (event.type === 'ready') {
|
|
867
|
+
if (!ready) {
|
|
868
|
+
ready = true;
|
|
869
|
+
idle.clear();
|
|
870
|
+
resolve(connection);
|
|
871
|
+
}
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (event.type === 'data') {
|
|
875
|
+
const bytes = Buffer.from(event.data, 'base64');
|
|
876
|
+
if (listeners.size === 0) {
|
|
877
|
+
buffered.push(bytes);
|
|
878
|
+
bufferedBytes += bytes.byteLength;
|
|
879
|
+
while (bufferedBytes > 1024 * 1024 && buffered.length > 1) {
|
|
880
|
+
bufferedBytes -= buffered.shift()?.byteLength ?? 0;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
for (const listener of listeners)
|
|
885
|
+
listener(bytes);
|
|
886
|
+
}
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (event.type === 'end') {
|
|
890
|
+
finish(null);
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
finish(new Error(event.error));
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
socket.once('error', (error) => finish(error));
|
|
897
|
+
socket.once('close', () => finish(ready ? null : new Error('vsock TCP socket closed before readiness')));
|
|
898
|
+
idle.bump();
|
|
899
|
+
socket.write(frame(JSON.stringify({
|
|
900
|
+
op: 'tcp-connect',
|
|
901
|
+
body: request,
|
|
902
|
+
})));
|
|
903
|
+
});
|
|
904
|
+
}
|
|
605
905
|
}
|
|
606
906
|
// ---------------------------------------------------------------------------
|
|
607
907
|
// Small helpers
|