TCP/IP stack for JS (built on gVisor + WASM)
- Browser/Server: A user-space TCP/IP stack for the browser or server
- Polyfill: Node.js compatible
net
API allowing you to use server-side libraries in the browser - Trusted: Built on top of gVisor's
tcpip
stack - Secure: No root access required when building custom VPN servers
- Tun/Tap: L3 and L2 hooks using
TunInterface
andTapInterface
- WS Tunnel: Tunnel packets to an internet-connected network using WebSockets
- v86: Communicate directly with an in-browser Linux VM via a
NetworkAdapter
NPM
$ npm install --save tcpip
Yarn
$ yarn add tcpip
import { Stack, init } from 'tcpip';
async function run() {
// Initialize the WASM module - call this at entrypoint
await init();
// Create a network stack
const stack = new Stack();
// A stack can have one or more network interfaces
stack.createLoopbackInterface({
ipAddress: '127.0.0.1/8',
});
// Node.js compatible `net` API available
// (option to polyfill - see below)
const { net } = stack;
const server = net.createServer(80);
}
run();
import { Stack, init } from 'tcpip';
async function run() {
await init();
const stack = new Stack();
// Tun interfaces provide hooks for L3 IP packets
const tunInterface = stack.createTunInterface({
ipAddress: '10.2.0.1/24',
});
// Capture outgoing IP packets
tunInterface.on('packet', (packet) => {
console.log(packet);
});
// Inject IP packets into the network stack
tunInterface.injectPacket(myPacket);
}
run();
import { Stack, init } from 'tcpip';
async function run() {
await init();
const stack = new Stack();
// Tap interfaces provide hooks for L2 ethernet frames
const tapInterface = stack.createTapInterface({
ipAddress: '10.1.0.1/24',
macAddress: '0a:0a:0b:0b:0c:0c',
});
// Capture outgoing ethernet frames
tapInterface.on('frame', (frame) => {
console.log(frame);
});
// Inject ethernet frames into the network stack
tapInterface.injectFrame(myFrame);
}
run();
Keep in mind this is all happening in user-space - no kernel-level network interfaces are created. If you want to connect your stack to an outside network, you can use WebSocket tunnels (browser) or any other transport (server).
tcpip.js relies on WebAssembly to run. Loading WASM isn't standardized yet across runtime environments, so some targets may require additional configuration. tcpip.js uses package exports to try to detect your runtime environment and load the appropriate initialize logic.
For all runtimes, you must call init()
or initFrom()
at your entrypoint to load tcpip.js's WASM module.
If you are using a supported environment (eg. Webpack & Node), you can use init()
to initialize the module:
import { init } from 'tcpip';
await init();
Note: Webpack requires additional configuration, see below.
If your environment doesn't support init()
(eg. Deno), you can load the WASM file manually using initFrom()
:
import { Stack, initFrom } from 'https://esm.sh/[email protected]';
import tcpipWasm from 'https://esm.sh/[email protected]/dist/tcpip.wasm?module';
await initFrom(tcpipWasm);
Below are additional configuration instructions required for common runtimes/bundlers:
Add a rule to load .wasm
files as an asset/resource:
webpack.config.ts
import { Configuration } from 'webpack';
const config: Configuration = {
module: {
rules: [
{
test: /\.wasm/,
type: 'asset/resource',
},
],
},
};
export default config;
Install file-loader
:
npm install --save-dev file-loader
or
yarn add -D file-loader
Add a rule to load .wasm
files using the file-loader
:
webpack.config.ts
import { Configuration } from 'webpack';
const config: Configuration = {
module: {
rules: [
{
test: /\.wasm/,
use: [{ loader: 'file-loader' }],
},
],
},
};
export default config;
No additional configuration is required to load in Node.js.
Currently you will need to manually load the WASM file and pass it into initFrom()
.
Using esm.sh:
index.ts
import { Stack, initFrom } from 'https://esm.sh/[email protected]';
import tcpipWasm from 'https://esm.sh/[email protected]/dist/tcpip.wasm?module';
await initFrom(tcpipWasm);
const stack = new Stack();
Then run:
$ deno run --allow-net index.ts
You can polyfill the Node.js net
module in the browser in order to run network requests through tcpip.js. This opens the doors to using server-side network libraries in the browser.
-
Install the polyfill:
$ npm install --save @tcpip/polyfill
or
$ yarn add @tcpip/polyfill
-
Configure your bundler to resolve
net
using the polyfill:webpack.config.ts
import { Configuration } from 'webpack'; const config: Configuration = { // Don't forget to add wasm module rules resolve: { fallback: { net: require.resolve('@tcpip/polyfill/net'), }, }, }; export default config;
Be sure to add the appropriate module rules for your bundler as shown above under Runtimes.
-
Create a network stack and call
polyfill()
to attach that stack to thenet
module:import { polyfill } from '@tcpip/polyfill'; import { Stack, init } from 'tcpip'; async function run() { await init(); const stack = new Stack(); stack.createLoopbackInterface({ ipAddress: '127.0.0.1/8', }); polyfill(stack); } run();
-
Now any server-side library that imports
net
will use the polyfilled API and route packets through your stack.import { createServer } from 'net'; // Creates a TCP server on your tcpip.js stack const server = createServer(80);
Note: This isn't a silver bullet - many server side libraries rely on more than just net
(eg. fs
, crypto
, etc). You will need to polyfill the remaining modules on a case-by-case basis.
tcpip.js is built on gVisor's tcpip
stack written in Go. It compiles to WASM then binds to JS classes and methods.
gVisor is an application kernel written in Go. It implements a large subset of Linux system calls completely in user-space, including a full TCP/IP stack. gVisor is used in production by Google in products like App Engine, Cloud Functions, and Cloud Run. It's also being used in some Kubernetes clusters as an extra security layer between containers.
Code written in Go can be compiled to WASM using the GOARCH=wasm
target. When combined with GOOS=js
, you can use the syscall/js
package to interact with JavaScript from Go.
tcpip.js is implemented using a hybrid language approach: The classes/interfaces are written in TypeScript and their methods are implemented in Go.
-
LoopbackInterface
/TunInterface
/TapInterface
- Basic POC
- More options
- Node.js
net
API (TCP)- Basic POC
- Polyfill
- Complete implementation
- Node.js
dgram
API (UDP)- Basic POC
- Polyfill
- Complete implementation
- Node.js
dns
API- Basic POC
- Polyfill
- Complete implementation
- Node.js
http
API- Basic POC
- Polyfill
- Complete implementation
- v86 adapter
-
NetworkAdapter
package - Example project
-
- WebSocket tunnel
- Basic POC
- Example project
MIT