37 lines
1.8 KiB
JavaScript
37 lines
1.8 KiB
JavaScript
// Runs only inside Bubblewrap's network/PID namespaces. No host TCP/UDP handle
|
|
// is inherited: the single TCP listener relays bytes to the fixed Unix proxy.
|
|
import { spawn } from 'node:child_process';
|
|
import { createServer, createConnection } from 'node:net';
|
|
import { closeSync, readlinkSync } from 'node:fs';
|
|
import { networkInterfaces } from 'node:os';
|
|
|
|
const [hostNamespace, socket, port, executable, ...args] = process.argv.slice(2);
|
|
if (readlinkSync('/proc/self/ns/net') === hostNamespace ||
|
|
Object.keys(networkInterfaces()).some(name => name !== 'lo')) {
|
|
throw new Error('isolated browser network required');
|
|
}
|
|
const sockets = new Set();
|
|
const track = s => { sockets.add(s); s.once('close', () => sockets.delete(s)); return s; };
|
|
const server = createServer(peer => {
|
|
track(peer);
|
|
const upstream = track(createConnection(socket));
|
|
peer.on('error', () => upstream.destroy()); upstream.on('error', () => peer.destroy());
|
|
peer.once('close', () => upstream.destroy()); upstream.once('close', () => peer.destroy());
|
|
peer.pipe(upstream); upstream.pipe(peer);
|
|
});
|
|
await new Promise((resolve, reject) => {
|
|
server.once('error', reject); server.listen(Number(port), '127.0.0.1', resolve);
|
|
});
|
|
const child = spawn(executable, args, { stdio:['ignore','ignore','ignore',3,4] });
|
|
// Chromium alone owns the CDP pipes. Do not hold its EOF open on startup failure.
|
|
closeSync(3); closeSync(4);
|
|
let closing;
|
|
const close = () => closing ??= (async () => {
|
|
const closed = new Promise(resolve => server.close(resolve));
|
|
for (const s of sockets) s.destroy();
|
|
await closed;
|
|
})();
|
|
child.once('error', () => close().then(() => { process.exitCode = 1; }));
|
|
child.once('exit', (code, signal) => close().then(() => { process.exitCode = signal ? 1 : code; }));
|
|
for (const signal of ['SIGTERM','SIGINT']) process.once(signal, () => child.kill(signal));
|