boringtunjs
v1.0.8
Published
A userspace WireGuard VPN implementation for NodeJS — pure JavaScript
Maintainers
Readme
BoringTunJS
A userspace WireGuard VPN implementation for Node.js, written in pure JavaScript — no native bindings, no Rust, no C++.
⚠️ Research project — not for production
This is an experimental, educational implementation of the WireGuard protocol. Use it to learn, prototype, or research — do not run it in production.
If you need WireGuard for real workloads, use one of:
- The official wireguard-tools with a kernel or userspace driver
- A native Node.js wrapper around
wg/wg-quick - BoringTun directly (Cloudflare's Rust implementation)
Expect lower throughput than any of those — this is plain JS running crypto in a single thread.
Motivation
The goal of this project is study and research:
- Experiment with implementing IP-layer protocols in JavaScript — the IPv4 parser, a TCP state machine with handshake/teardown, UDP flows, and a DNS proxy — all on top of raw bytes.
- Understand the WireGuard protocol end-to-end by implementing it: the Noise_IKpsk2 handshake, session keys, replay window, rekey/keepalive timers.
- As a bonus along the way, write the underlying cryptographic primitives from their specs rather than calling a library — BLAKE2s (RFC 7693), ChaCha20-Poly1305 (RFC 7539), Poly1305 big-integer arithmetic. These were not the target of the project, but are a useful side-effect.
- Have a hackable reference: small enough to read in an afternoon, all in one language, no build toolchain.
If you are learning how IP stacks, VPNs, or the Noise protocol framework actually work, this codebase is meant to be read. If you need to ship WireGuard in production, use one of the options above.
What changed
This project started as N-API bindings to Cloudflare's BoringTun (Rust) plus a C++ wrapper. It has since been rewritten as a pure JavaScript implementation — including BLAKE2s, ChaCha20-Poly1305, Poly1305, and the full Noise_IKpsk2 handshake state machine. The only runtime dependency on Node.js internals is crypto for X25519 key generation and Diffie-Hellman (which OpenSSL provides).
The Noise protocol design and overall architecture are ported from BoringTun. We gratefully acknowledge Cloudflare's work — see the Acknowledgments section.
Install
npm install boringtunjsNo compilation step. No postinstall. Just JavaScript.
Requirements
- Node.js 22+ (for X25519 support in the built-in
cryptomodule)
Quick start
const { Wireguard } = require('boringtunjs')
const wg = new Wireguard({
privateKey: '3RLqvLwIYch6efW7iK7lywzFnZQfSzblDTvRwJ7CAbA=',
listenPort: 51820,
address: '10.8.0.1',
})
wg.addPeer({
publicKey: '3g5U/6myr9DZf/HkNuwSKR+h1lcOJQbnAQfjrZ4q5xg=',
allowedIPs: '10.8.0.2/32',
keepAlive: 25,
endpoint: '1.2.3.4:51820',
})
wg.listen()API
Key management
const { WireguardTunnel } = require('boringtunjs')
// Generate a new key pair
const { privateKey, publicKey } = WireguardTunnel.generateKeyPair()
// Or just a private key
const privateKey = WireguardTunnel.generatePrivateKey()
// Derive public from private
const publicKey = WireguardTunnel.getPublicKeyFrom(privateKey)
// Validate a base64 key
WireguardTunnel.checkValidKey(privateKey) // => true/falseThese are also exported as top-level functions for convenience:
const { generateKeyPair, generatePrivateKey, getPublicKeyFrom, checkValidKey } = require('boringtunjs')Wireguard
The high-level interface. Binds a UDP socket, routes IP packets to peers, and manages the local network stack (TCP/UDP).
const wg = new Wireguard({
privateKey, // base64 X25519 private key
listenPort, // UDP port to bind
address, // interface address (e.g. '10.8.0.1')
logLevel, // optional, 0–4
logger, // optional Logger instance
})
wg.addPeer({
publicKey, // peer's base64 public key
allowedIPs, // 'CIDR,CIDR,...' — which traffic belongs to this peer
keepAlive, // persistent keepalive seconds (optional)
endpoint, // 'ip:port' (optional — can be learned from first handshake)
name, // optional display name
})
wg.removePeer(publicKey) // returns boolean
wg.getPeerByKey(publicKey) // => Peer
wg.getPeers() // => array of endpoint strings
wg.listen() // start UDP server, IP layer, and initiate handshakes
wg.close() // graceful shutdown: peers, IP layer, UDP socket
wg.getStat() // => { publicKey, listenPort, address, peers, connections }getStat() returns:
{
publicKey: string,
listenPort: number,
address: string,
peers: [{
publicKey, name, endpoint,
txBytes, rxBytes,
lastHandshakeRtt, // ms from last completed handshake
lastHandshake, // epoch ms of last session establishment
}],
connections: {
tcp: { activeConnections: number },
udp: { activeClients: number },
},
}WireguardTunnel
The low-level Noise protocol engine. You rarely need it directly — Wireguard uses it internally — but it is exposed for testing and custom transports.
const tunnel = new WireguardTunnel({
privateKey, publicKey, preSharedKey, keepAlive, index,
})
tunnel.write(ipPacket) // encapsulate IP → WireGuard UDP packet
tunnel.read(udpPacket) // decapsulate WireGuard UDP → IP packet
tunnel.tick() // drive timers (rekey, keepalive) — call every ~100ms
tunnel.forceHandshake() // initiate handshake
tunnel.getStats() // => { txBytes, rxBytes, lastHandshakeRtt, lastHandshake }All methods return { type, data } where type is one of:
WireguardTunnel.WIREGUARD_DONEWireguardTunnel.WRITE_TO_NETWORKWireguardTunnel.WRITE_TO_TUNNEL_IPV4WireguardTunnel.WRITE_TO_TUNNEL_IPV6WireguardTunnel.WIREGUARD_ERROR
Logging
Pass a log level (or a custom Logger) to Wireguard — handshake, session, and tunnel traces all flow through the same instance:
const { Wireguard } = require('boringtunjs')
const wg = new Wireguard({
privateKey,
listenPort: 51820,
address: '10.8.0.1',
logLevel: 'debug', // 0 | 1/'error' | 2/'warn' | 3/'info' | 4/'debug'
})Levels are evaluated once at construction: suppressed methods become no-ops and the message thunks are never invoked, so disabled logs cost nothing. For full control, construct a Logger yourself:
const { Wireguard, Logger } = require('boringtunjs')
const logger = new Logger({
logLevel: 'debug',
debug: (msg) => myTransport.write('debug', msg),
warn: (msg) => myTransport.write('warn', msg),
callback: ({ level, log }) => metrics.record(level, log),
})
const wg = new Wireguard({ privateKey, listenPort: 51820, address: '10.8.0.1', logger })Limitations
- Only TCP and UDP traffic exits the tunnel. The built-in IP layer intercepts and forwards TCP streams and UDP datagrams to the host. Any other IP protocol (ICMP, SCTP, GRE, etc.) is silently dropped — Node.js has no raw socket API, so forwarding these from userspace isn't possible without a native module or a privileged helper.
- IPv4 only (for now). IPv6 packets are recognized at the WireGuard layer but not routed through the IP stack. IPv6 support is planned as the next major milestone — the WireGuard
WRITE_TO_TUNNEL_IPV6result type is already wired, just the IP-layer side is missing. - Single-threaded crypto. All ChaCha20-Poly1305, Poly1305, and BLAKE2s operations run in the main JS event loop. Throughput is limited accordingly.
- No kernel TUN integration. This is a fully userspace implementation; it does not create a system-level interface.
- Rate-limiting is not enforced. Cookie replies are parsed but not generated — this library is intended as a client, not a DoS-resistant server.
Roadmap
- IPv6 support — extend the IP layer to parse and emit IPv6 packets alongside IPv4, handling TCP/UDP over both.
- Custom protocol handlers — expose a callback/factory hook (same shape as
addTCPSocketFactory/addUDPSocketFactory) so users can intercept ICMP, SCTP, GRE, or any other IP protocol the built-in IP layer currently drops. The library still can't talk raw sockets itself, but users with a native helper, logging sink, or external daemon can wire it in.
Development
git clone [email protected]:boringjs/boringtun.js.git
cd boringtun.js
npm install
npm test
npm run lintTest layout:
tests/crypto.spec.js— RFC 7539 / RFC 7693 test vectors for ChaCha20-Poly1305 and BLAKE2stests/tunnel.spec.js— end-to-end handshake and data packet teststests/net-protocols.spec.js— TCP/UDP/DNS stack tests
Acknowledgments
This project is heavily inspired by BoringTun, Cloudflare's userspace WireGuard implementation in Rust. The Noise_IKpsk2 state machine, timer constants, session ring buffer, and replay-window design are all ported from BoringTun. We thank Cloudflare and the BoringTun contributors for publishing such a clean reference implementation.
BoringTun is distributed under the BSD 3-Clause License; this project follows the same license.
The port from Rust/C++ to pure JavaScript — including the Noise protocol state machine, BLAKE2s, ChaCha20-Poly1305 with Poly1305 big-integer arithmetic, and the session/timer orchestration — was carried out with significant help from Claude Code. Thanks to Anthropic.
