tls: native bidirectional TLS bridge for kernel-adjacent forwarding (TUN/VPN/proxy workloads)
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 30/100
- issue の種類
- 機能追加
- 明瞭さ
- おおむね明確
- 活発さ
- 静か
- 技術スタック
- javascript, node.js
- 領域
- backend, networking, security
調査の方向性
まず src/crypto/crypto_tls.cc の TLSWrap::ClearOut()、EncOut()、DoWrite() の周辺を読み、次に test-tls-onread-static-buffer.js を確認します。この提案には、API、fd の所有権、スレッド処理、ライフサイクルの動作について maintainer の合意が必要です。合意されたテストとベンチマークによって、chunk ごとの JavaScript payload 処理なしに双方向 TLS 転送が実証されれば完了です。
索引モデルが issue の本文から書いたものです。
説明
What is the problem this feature will solve?
Node’s TLSSocket is designed for application-level stream I/O: cleartext is delivered to JavaScript via the streams API ('data', read(), backpressure via pause()/drain). For high-throughput, full-duplex, kernel-adjacent forwarding (TUN/TAP tunnels, VPN bridges, L3 proxies), users must implement a JS pump between a native fd and a TLSSocket.
That pattern degrades badly in practice:
- Per-chunk V8 boundary — TLSWrap::ClearOut() reads via SSL_read, copies into an allocated buffer, and EmitRead() into JS (src/crypto/crypto_tls.cc, kClearOutChunkSize = 16384).
- Extra event-loop turns — sync underlying writes are deferred with SetImmediate before WriteWrap::Done() (EncOut() / empty DoWrite() paths).
- Dual backpressure — the app must coordinate pause/resume between two independent stream endpoints (e.g. TUN poll + TLS socket) in JavaScript.
- No framing help — TLS is a byte stream; L3 frames (e.g. IPv6 packets) often require reassembly across multiple reads in userland.
- Real-world impact: projects doing iOS CoreDevice / CDTunnel-style forwarding (e.g. Appium appium-ios-tuntap) had to leave Node TLS entirely and implement OpenSSL forwarding in a native addon with dedicated blocking I/O threads — duplicating logic that conceptually belongs next to TLSWrap.
The stream API remains correct for HTTP, RPC, etc.; this gap is specifically for payload forwarding where JS should never see the bytes.
What is the feature you are proposing to solve the problem?
Add a first-class native TLS bridge API that pumps cleartext between an encrypted TCP (or pipe) stream and another native I/O endpoint without surfacing payload data to JavaScript.
Proposed API (sketch):
import tls from 'node:tls';
import net from 'node:net';
const tcp = net.connect({ port });
const bridge = tls.createBridge({
socket: tcp, // existing net.Socket / fd
sink: tunFd, // numeric fd or native handle (TUN, pipe, etc.)
credentials: { cert, key }, // or secureContext / PSK options
direction: 'duplex', // 'duplex' | 'encrypt-only' | 'decrypt-only'
onError(err) { /* lifecycle */ },
onClose() { /* cleanup */ },
});
await bridge.handshake(); // or auto on first I/O
bridge.start();
bridge.stop();
Implementation outline (in core, built on existing TLSWrap):
- Reuse TLSWrap + OpenSSL session setup (lockdown cert, TLS-PSK, etc.) — same crypto as tls.connect().
- Run the hot loop in native code (dedicated per-connection thread or integrated read/write cycle in TLSWrap), analogous to ClearIn → ClearOut → EncOut but wired directly to the sink fd instead of EmitRead/DoWrite.
- JavaScript receives only: handshake result, errors, close — not per-packet callbacks.
- Support dup()/fd ownership semantics documented clearly (who closes what).
Success criteria:
- Sustained bidirectional TLS 1.2 forwarding at path-MTU record sizes without per-chunk JS allocation.
p99 latency and CPU use materially better than an equivalent socket.on('data') ↔ tun.write() pump. - Add-on authors (TUN, userspace VPN, transparent proxies) can delete custom OpenSSL pthread forwarders.
What alternatives have you considered?
- Stay on TLSSocket + streams — Correct for apps, insufficient for tunnel workloads; requires JS pump and suffers the costs above. This is what forced native-addon workarounds today.
- socket.on('data') + onread static buffer — Helps TCP; TLSWrap::ClearOut() still copies before JS (test-tls-onread-static-buffer.js covers TCP onread, not a TLS zero-copy path). Does not remove the V8 boundary for cleartext payload.
- Third-party native addons (custom OpenSSL in N-API) — Works (proven in production) but duplicates TLSWrap session management, cert/PSK handling, and security updates. Every VPN/tunnel project reinvents the same forwarder.
- node:quic patterns — QUIC has native stream handling oriented toward protocol I/O; CDTunnel / lockdown TLS 1.2 over TCP is a different stack and not a drop-in substitute.
- Document “don’t use TLS for this” only — Honest but pushes ecosystem fragmentation; a small, targeted tls.createBridge() (or similar) keeps advanced use cases on supported Node APIs.
- 主要言語
- JavaScript
- スター
- 122k
- フォーク
- 37.4k
- 平均マージ
- 4日 3時間
- マージ済み PR(30日)
- 279
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
nodejs/node のほかの issue
-
doc
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
-
build
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
-
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
-
feature request
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
docToolchain/docToolchain#1705 ·
-
Timezone select lists one option per character; duplicate "Failed" reason; shared tracker popover id オープンbug
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
nightscout/nocturne#1414 ·
-
bug v2
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
modelcontextprotocol/inspector#2458 · コメント 1 件 ·
-
Mend: dependency security vulnerability status: needs triage 🕵️♀️
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
carbon-design-system/ibm-products#9907 ·