socketduct 0.1.15
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/.dockerignore +11 -0
- package/.env.example +131 -0
- package/AGENTS.md +168 -0
- package/Dockerfile +30 -0
- package/README.md +134 -0
- package/changelog.d/20260716083144-v2-ack-wire-format.md +3 -0
- package/changelog.d/20260716092044-v2-only-stream-framing.md +1 -0
- package/changelog.d/20260727113000-relay-connect-timeout-destroy.md +1 -0
- package/changelog.d/20260813093000-private-relay-hardening.md +1 -0
- package/changelog.d/20260824183412-initial-target-recovery-https.md +1 -0
- package/changelog.d/20260824230000-tls-relay-session-close.md +1 -0
- package/changelog.d/20260825120000-reconnect-close-target-timeout.md +1 -0
- package/changelog.d/20260829120000-protocol-v3-multiplexing.md +1 -0
- package/changelog.d/20260829160000-v3-review-corrections.md +1 -0
- package/changelog.d/20260830000000-fix-socket-idle-timeout.md +1 -0
- package/changelog.d/20260830090000-multi-gateway-transport-set.md +1 -0
- package/changelog.d/20260830093000-relay-close-settlement.md +1 -0
- package/changelog.d/20260830120000-immutable-gateway-image.md +1 -0
- package/changelog.d/20260902210000-native-reverse-gateway.md +1 -0
- package/changelog.d/20260902230000-relay-resume-terminal-order.md +1 -0
- package/changelog.d/20260902230708-multiplexed-open-ownership.md +1 -0
- package/changelog.d/20260902231541-multiplexed-terminal-close.md +1 -0
- package/changelog.d/20260902231542-reverse-close-position.md +1 -0
- package/changelog.d/20260902231543-reverse-terminal-connect.md +1 -0
- package/changelog.d/20260902233000-reverse-gateway-bounds.md +1 -0
- package/changelog.d/20260903001000-reverse-data-ordering.md +1 -0
- package/changelog.d/20260903003000-reverse-hub-restart-window.md +1 -0
- package/changelog.d/20260903010000-reverse-terminal-lifecycle.md +1 -0
- package/changelog.d/20260904142743-transport-set-readiness-wait.md +1 -0
- package/changelog.d/20260904152855-deferred-open-snapshots-binary-views.md +1 -0
- package/changelog.d/20260904163041-tensorbuzz-container-workflow.md +1 -0
- package/changelog.d/20260904174955-transport-set-terminal-causes.md +1 -0
- package/changelog.d/20260905081000-contain-reverse-auth-write-errors.md +1 -0
- package/changelog.d/20260905100520-reverse-hub-close-race.md +1 -0
- package/changelog.d/20260905120000-persistent-jsonl-logging.md +1 -0
- package/changelog.d/20260908071009-jsonl-partial-append-recovery.md +1 -0
- package/changelog.d/20260908074916-private-log-directory-setup.md +1 -0
- package/changelog.d/20260909093057-reverse-large-write-backpressure.md +1 -0
- package/changelog.d/20260909135830-actionable-error-diagnostics.md +1 -0
- package/changelog.d/20260909150000-transport-set-error-redaction.md +1 -0
- package/docker-compose.dev.yml +19 -0
- package/docker-compose.reverse-gateway.yml +36 -0
- package/docker-compose.yml +73 -0
- package/docs/architecture.md +264 -0
- package/docs/docker-topology.md +164 -0
- package/docs/docker.md +281 -0
- package/docs/logging.md +185 -0
- package/docs/plans/initial-implementation.md +205 -0
- package/docs/plans/socket-docker-integration.md +221 -0
- package/docs/protocol.md +558 -0
- package/docs/releasing.md +18 -0
- package/docs/reverse-gateway.md +186 -0
- package/docs/runbooks/jsonl-integrity-rollout.md +144 -0
- package/docs/running.md +215 -0
- package/docs/security.md +258 -0
- package/docs/session-lifecycle.md +127 -0
- package/docs/socket-api.md +448 -0
- package/eslint.config.js +36 -0
- package/package.json +44 -0
- package/scripts/deploy-gateway.js +194 -0
- package/scripts/test.js +28 -0
- package/src/auth/passwords.js +38 -0
- package/src/auth/static-token-store.js +41 -0
- package/src/auth/user-store.js +139 -0
- package/src/client/connect.js +76 -0
- package/src/client/deferred-virtual-socket.js +312 -0
- package/src/client/http-agent.js +101 -0
- package/src/client/https-agent.js +366 -0
- package/src/client/multiplexed-connection.js +1504 -0
- package/src/client/multiplexed-transport-set.js +917 -0
- package/src/client/multiplexed.js +3 -0
- package/src/client/socketduct-socket.js +1174 -0
- package/src/client/virtual-socket.js +256 -0
- package/src/config/environment.js +400 -0
- package/src/edge/tcp-edge.js +82 -0
- package/src/index.js +101 -0
- package/src/logging/structured-logger.js +745 -0
- package/src/protocol/compression.js +325 -0
- package/src/protocol/frame.js +341 -0
- package/src/protocol/v3-compression.js +44 -0
- package/src/protocol/v3-frame.js +209 -0
- package/src/proxy/multiplexed-relay.js +798 -0
- package/src/proxy/tcp-relay.js +1191 -0
- package/src/relay.js +3 -0
- package/src/reverse/client.js +721 -0
- package/src/reverse/common.js +88 -0
- package/src/reverse/gateway.js +663 -0
- package/src/reverse/hub.js +563 -0
- package/src/reverse/index.js +3 -0
- package/src/reverse/runtime.js +78 -0
- package/src/server/control-handler.js +170 -0
- package/src/server/primary.js +64 -0
- package/src/server/relay-runtime.js +124 -0
- package/src/server/worker.js +39 -0
- package/src/sessions/session.js +212 -0
- package/src/spool/spool-file.js +451 -0
- package/tensorbuzz.yml +36 -0
- package/test/e2e/gateway-process.test.js +262 -0
- package/test/e2e/reverse-gateway-process.test.js +285 -0
- package/test/fixtures/logging/enospc-matrix.mjs +238 -0
- package/test/fixtures/logging/enospc-recovery.mjs +146 -0
- package/test/fixtures/logging/process-writer.mjs +20 -0
- package/test/helpers/tls-certificates.js +115 -0
- package/test/integration/initial-target-recovery.test.js +518 -0
- package/test/integration/logging-recovery.test.js +163 -0
- package/test/integration/multiplexed-compatibility.test.js +254 -0
- package/test/integration/multiplexed-corrections.test.js +551 -0
- package/test/integration/multiplexed-failures.test.js +339 -0
- package/test/integration/multiplexed-transport-set.test.js +456 -0
- package/test/integration/multiplexed-transport.test.js +208 -0
- package/test/integration/reverse-gateway.test.js +1293 -0
- package/test/integration/socketduct-http-agent.test.js +435 -0
- package/test/integration/socketduct-https-agent.test.js +669 -0
- package/test/integration/stream-compression.test.js +1267 -0
- package/test/integration/tcp-forwarding.test.js +2878 -0
- package/test/integration/tls-forwarding.test.js +748 -0
- package/test/unit/auth/auth.test.js +64 -0
- package/test/unit/auth/static-token-store.test.js +14 -0
- package/test/unit/client/connect.test.js +58 -0
- package/test/unit/client/multiplexed-spool-budget.test.js +309 -0
- package/test/unit/client/multiplexed-transport-set.test.js +906 -0
- package/test/unit/client/socketduct-socket.test.js +1124 -0
- package/test/unit/client/virtual-socket.test.js +98 -0
- package/test/unit/config/docker-gateway-files.test.js +274 -0
- package/test/unit/config/environment.test.js +507 -0
- package/test/unit/index.test.js +78 -0
- package/test/unit/logging/structured-logger.test.js +674 -0
- package/test/unit/protocol/compression.test.js +421 -0
- package/test/unit/protocol/frame.test.js +336 -0
- package/test/unit/protocol/v3-compression.test.js +31 -0
- package/test/unit/protocol/v3-frame.test.js +57 -0
- package/test/unit/release-configuration.test.js +21 -0
- package/test/unit/server/control-handler.test.js +291 -0
- package/test/unit/server/primary.test.js +59 -0
- package/test/unit/server/relay-runtime.test.js +434 -0
- package/test/unit/sessions/session.test.js +84 -0
- package/test/unit/spool/spool-file.test.js +295 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {createServer} from "node:net"
|
|
2
|
+
|
|
3
|
+
import {SocketductSocket} from "../client/socketduct-socket.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('../spool/spool-file.js').SessionSpool} SessionSpool
|
|
7
|
+
* @typedef {import('../client/socketduct-socket.js').SocketductTlsOptions} SocketductTlsOptions
|
|
8
|
+
* @typedef {object} TcpEdgeOptions
|
|
9
|
+
* @property {string} [host] - Edge listen host.
|
|
10
|
+
* @property {number} [port] - Edge listen port.
|
|
11
|
+
* @property {string} relayHost - Relay host.
|
|
12
|
+
* @property {number} relayPort - Relay port.
|
|
13
|
+
* @property {string} token - Auth token.
|
|
14
|
+
* @property {string} sessionName - Human-readable session name.
|
|
15
|
+
* @property {{host: string, port: number}} target - Target requested for this session.
|
|
16
|
+
* @property {SessionSpool} spool - Spool storage.
|
|
17
|
+
* @property {number} [reconnectDelayMs] - Reconnect delay.
|
|
18
|
+
* @property {SocketductTlsOptions} [tls] - TLS client options.
|
|
19
|
+
* @typedef {object} TcpEdgeHandle
|
|
20
|
+
* @property {import('node:net').Server} server - Edge server.
|
|
21
|
+
* @property {number} port - Bound edge port.
|
|
22
|
+
* @property {() => Promise<void>} close - Close function.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {TcpEdgeOptions} options - Edge options.
|
|
27
|
+
* @returns {Promise<TcpEdgeHandle>} - Started edge server.
|
|
28
|
+
*/
|
|
29
|
+
export async function startTcpEdge(options) {
|
|
30
|
+
/** @type {Set<SocketductSocket>} */
|
|
31
|
+
const connections = new Set()
|
|
32
|
+
const server = createServer((appSocket) => {
|
|
33
|
+
const socketductSocket = new SocketductSocket(options)
|
|
34
|
+
connections.add(socketductSocket)
|
|
35
|
+
|
|
36
|
+
const closeBoth = () => {
|
|
37
|
+
connections.delete(socketductSocket)
|
|
38
|
+
socketductSocket.destroy()
|
|
39
|
+
appSocket.destroy()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
appSocket.on("close", closeBoth)
|
|
43
|
+
appSocket.on("error", closeBoth)
|
|
44
|
+
socketductSocket.on("close", closeBoth)
|
|
45
|
+
socketductSocket.on("error", closeBoth)
|
|
46
|
+
|
|
47
|
+
appSocket.pipe(socketductSocket).pipe(appSocket)
|
|
48
|
+
socketductSocket.connect()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
await new Promise((resolve, reject) => {
|
|
52
|
+
server.once("error", reject)
|
|
53
|
+
server.listen(options.port ?? 0, options.host ?? "127.0.0.1", () => {
|
|
54
|
+
server.off("error", reject)
|
|
55
|
+
resolve(undefined)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const address = server.address()
|
|
60
|
+
if (address === null || typeof address === "string") {
|
|
61
|
+
throw new Error("Edge did not bind to a TCP port")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
server,
|
|
66
|
+
port: address.port,
|
|
67
|
+
close: async () => {
|
|
68
|
+
for (const connection of connections) {
|
|
69
|
+
connection.destroy()
|
|
70
|
+
}
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
server.close((error) => {
|
|
73
|
+
if (error) {
|
|
74
|
+
reject(error)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
resolve(undefined)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {loadEnvironmentConfig} from "./config/environment.js"
|
|
2
|
+
import {createJsonlFileLogger} from "./logging/structured-logger.js"
|
|
3
|
+
import {startRelayRuntime} from "./server/relay-runtime.js"
|
|
4
|
+
import {startOutboundReverseGatewayRuntime, startReverseHubRuntime} from "./reverse/runtime.js"
|
|
5
|
+
|
|
6
|
+
export {createSocketductConnectionFactory} from "./client/connect.js"
|
|
7
|
+
export {createJsonlFileLogger, createStructuredLogger} from "./logging/structured-logger.js"
|
|
8
|
+
export {SocketductHttpAgent} from "./client/http-agent.js"
|
|
9
|
+
export {connectSocketduct, SocketductRelayError, SocketductSocket} from "./client/socketduct-socket.js"
|
|
10
|
+
export {createMultiplexedSocketductConnection, MultiplexedSocketductConnection} from "./client/multiplexed-connection.js"
|
|
11
|
+
export {createMultiplexedSocketductTransportSet, MultiplexedSocketductTransportSet, SocketductTransportSetLimitError, SocketductTransportSetNotReadyError, SocketductTransportSetTerminalError} from "./client/multiplexed-transport-set.js"
|
|
12
|
+
export {SocketductSessionLostError, SocketductStreamError, VirtualSocket} from "./client/virtual-socket.js"
|
|
13
|
+
export {createOutboundReverseGateway, createReverseGatewayConnection, OutboundReverseGateway, ReverseGatewayConnection, startReverseGatewayHub} from "./reverse/index.js"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} StartupSummary
|
|
17
|
+
* @property {'Socketduct'} name - name value.
|
|
18
|
+
* @property {'node'} runtime - runtime value.
|
|
19
|
+
* @property {'single-worker'} mode - mode value.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** @returns {StartupSummary} - Startup summary. */
|
|
23
|
+
export function getStartupSummary() {
|
|
24
|
+
return {
|
|
25
|
+
name: "Socketduct",
|
|
26
|
+
runtime: "node",
|
|
27
|
+
mode: "single-worker"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @returns {Promise<void>} Startup completion promise.
|
|
33
|
+
*/
|
|
34
|
+
async function main() {
|
|
35
|
+
const config = loadEnvironmentConfig()
|
|
36
|
+
const logger = config.fileLogging === undefined ? undefined : createJsonlFileLogger({...config.fileLogging, role: config.mode})
|
|
37
|
+
if (config.mode === "summary") {
|
|
38
|
+
const summary = getStartupSummary()
|
|
39
|
+
console.log(`${summary.name} starting in ${summary.mode} mode on ${summary.runtime}`)
|
|
40
|
+
logger?.log("process.started", {runtimeMode: config.mode})
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @type {{status: () => object, port?: number}} */
|
|
45
|
+
let service
|
|
46
|
+
/** @type {() => Promise<void>} */
|
|
47
|
+
let closeRuntime
|
|
48
|
+
if (config.mode === "relay") {
|
|
49
|
+
const runtime = await startRelayRuntime(config)
|
|
50
|
+
service = runtime.relay
|
|
51
|
+
closeRuntime = runtime.close
|
|
52
|
+
} else if (config.mode === "reverse-hub") {
|
|
53
|
+
const runtime = await startReverseHubRuntime(config)
|
|
54
|
+
service = runtime.hub
|
|
55
|
+
closeRuntime = runtime.close
|
|
56
|
+
} else {
|
|
57
|
+
const runtime = await startOutboundReverseGatewayRuntime(config, logger)
|
|
58
|
+
service = runtime.gateway
|
|
59
|
+
closeRuntime = runtime.close
|
|
60
|
+
}
|
|
61
|
+
const tlsStatus = config.mode === "relay" && config.relayTls === undefined ? "plaintext" : "TLS"
|
|
62
|
+
console.log(JSON.stringify({
|
|
63
|
+
event: `${config.mode}.started`,
|
|
64
|
+
...(config.mode === "reverse-gateway" ? {gatewayId: config.reverseGatewayId} : {host: config.relayHost, port: service.port}),
|
|
65
|
+
transportSecurity: tlsStatus,
|
|
66
|
+
compression: config.mode.startsWith("reverse-") ? "zstd" : config.compression
|
|
67
|
+
}))
|
|
68
|
+
logger?.log("process.started", {
|
|
69
|
+
runtimeMode: config.mode,
|
|
70
|
+
transportSecurity: tlsStatus,
|
|
71
|
+
compression: config.mode.startsWith("reverse-") ? "zstd" : config.compression
|
|
72
|
+
})
|
|
73
|
+
const statusIntervalMs = config.statusIntervalMs ?? 30_000
|
|
74
|
+
const statusTimer = statusIntervalMs > 0
|
|
75
|
+
? setInterval(() => {
|
|
76
|
+
const status = service.status()
|
|
77
|
+
console.log(JSON.stringify({event: `${config.mode}.status`, ...status}))
|
|
78
|
+
logger?.log("runtime.status", /** @type {Record<string, unknown>} */ (status))
|
|
79
|
+
}, statusIntervalMs)
|
|
80
|
+
: undefined
|
|
81
|
+
statusTimer?.unref()
|
|
82
|
+
|
|
83
|
+
const shutdown = () => {
|
|
84
|
+
if (statusTimer !== undefined) clearInterval(statusTimer)
|
|
85
|
+
closeRuntime()
|
|
86
|
+
.then(() => process.exit(0))
|
|
87
|
+
.catch((error) => {
|
|
88
|
+
console.error(error)
|
|
89
|
+
process.exit(1)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
process.once("SIGINT", shutdown)
|
|
93
|
+
process.once("SIGTERM", shutdown)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
97
|
+
main().catch((error) => {
|
|
98
|
+
console.error(error)
|
|
99
|
+
process.exit(1)
|
|
100
|
+
})
|
|
101
|
+
}
|