|
4 | 4 |
|
5 | 5 | ## Features
|
6 | 6 |
|
7 |
| -- **Portable:** User-space network stack implemented on top of [`lwIP` + WASM](#why-lwip) |
| 7 | +- **Portable:** User-space network stack built on [`lwIP` + WASM](#why-lwip) |
8 | 8 | - **Tun/Tap:** L3 and L2 hooks using virtual [`TunInterface`](#tun-interface) and [`TapInterface`](#tap-interface)
|
| 9 | +- **Bridge:** Create a virtual switch/LAN by [`bridging`](#bridge-interface) multiple interfaces together |
9 | 10 | - **TCP API:** Establish TCP connections over the virtual network stack using [clients](#connecttcp) and [servers](#listentcp)
|
10 | 11 | - **UDP API:** Send and receive UDP datagrams over the virtual network stack using [sockets](#openudp)
|
11 | 12 | - **Cross platform**: Built on web standard APIs (`ReadableStream`, `WritableStream`, etc)
|
@@ -152,19 +153,22 @@ for await (const connection of listener) {
|
152 | 153 |
|
153 | 154 | For more info, see [`TcpListener`](#tcplistener).
|
154 | 155 |
|
| 156 | +The above example connects a single VM to the `NetworkStack`. If you wish to connect multiple VMs together on a shared LAN (ie. a virtual switch), you can create a [`BridgeInterface`](#bridge-interface) to join multiple tap interfaces together. |
| 157 | + |
155 | 158 | ## Network interfaces
|
156 | 159 |
|
157 |
| -3 types of interfaces are available: |
| 160 | +4 types of interfaces are available: |
158 | 161 |
|
159 | 162 | - [Loopback](#loopback-interface): Loop packets back onto itself (ie. `localhost`)
|
160 | 163 | - [Tun](#tun-interface): Hook into IP packets (L3)
|
161 | 164 | - [Tap](#tap-interface): Hook into ethernet frames (L2)
|
| 165 | +- [Bridge](#bridge-interface): Bridge multiple tap interfaces together to create a virtual switch |
162 | 166 |
|
163 |
| -These interfaces are designed to resemble their counterparts in a traditional host network stack. |
| 167 | +These interfaces are designed to resemble their counterparts in a real network stack. |
164 | 168 |
|
165 | 169 | ### Loopback interface
|
166 | 170 |
|
167 |
| -A loopback interface simply forwards packets back on to itself. It's akin to 127.0.0.1 (`localhost`) on a traditional network stack. |
| 171 | +A loopback interface simply forwards packets back on to itself. It's akin to 127.0.0.1 (`localhost`) on a typical network stack. |
168 | 172 |
|
169 | 173 | ```ts
|
170 | 174 | const loopbackInterface = await stack.createLoopbackInterface({
|
@@ -193,8 +197,6 @@ const connection = await stack.connectTcp({
|
193 | 197 | });
|
194 | 198 | ```
|
195 | 199 |
|
196 |
| -You can create as many loopback interfaces as you wish. |
197 |
| - |
198 | 200 | ### Tun interface
|
199 | 201 |
|
200 | 202 | A tun interface hooks into inbound and outbound IP packets (L3).
|
@@ -255,15 +257,12 @@ const connection = await stack.connectTcp({
|
255 | 257 | ...
|
256 | 258 | ```
|
257 | 259 |
|
258 |
| -You can create as many tun interfaces as you wish. |
259 |
| - |
260 | 260 | ### Tap interface
|
261 | 261 |
|
262 | 262 | A tap interface hooks into inbound and outbound ethernet frames (L2).
|
263 | 263 |
|
264 | 264 | ```ts
|
265 | 265 | const tapInterface = await stack.createTapInterface({
|
266 |
| - mac: '01:23:45:67:89:ab', |
267 | 266 | ip: '196.168.1.1/24',
|
268 | 267 | });
|
269 | 268 | ```
|
@@ -326,11 +325,71 @@ const connection = await stack.connectTcp({
|
326 | 325 | ...
|
327 | 326 | ```
|
328 | 327 |
|
329 |
| -You can create as many tap interfaces as you wish. |
| 328 | +Note that `mac` and `ip` are optional parameters for `createTapInterface()`. If you don't provide a MAC address, a random one will be generated. If you don't provide an IP address, the interface will not respond to ARP requests or send ARP requests for unknown IP addresses. Typically you would only omit the IP address if you are using the tap interface as part of a [bridge](#bridge-interface). |
| 329 | + |
| 330 | +### Bridge interface |
| 331 | + |
| 332 | +A bridge interface bridges two or more tap interfaces together into a single logical interface with its own MAC and IP address. It operates at the ethernet level (L2) and will automatically forward frames between the interfaces based on the destination MAC address. |
| 333 | + |
| 334 | +```ts |
| 335 | +const port1 = await stack.createTapInterface(); |
| 336 | +const port2 = await stack.createTapInterface(); |
| 337 | + |
| 338 | +const bridge = await stack.createBridgeInterface({ |
| 339 | + ports: [port1, port2], |
| 340 | + ip: '192.168.1.1/24', |
| 341 | +}); |
| 342 | +``` |
| 343 | + |
| 344 | +A bridge is what you would use to connect multiple VMs together into a virtual LAN. |
| 345 | + |
| 346 | +```ts |
| 347 | +import { createV86NetworkStream } from '@tcpip/v86'; |
| 348 | + |
| 349 | +// ... |
| 350 | + |
| 351 | +const vm1 = new V86(); |
| 352 | +const vm2 = new V86(); |
| 353 | +const vm1Nic = createV86NetworkStream(vm1); |
| 354 | +const vm2Nic = createV86NetworkStream(vm2); |
| 355 | + |
| 356 | +const port1 = await stack.createTapInterface(); |
| 357 | +const port2 = await stack.createTapInterface(); |
| 358 | + |
| 359 | +// Connect port1 to vm1 |
| 360 | +port1.readable.pipeTo(vm1Nic.writable); |
| 361 | +vm1Nic.readable.pipeTo(port1.writable); |
| 362 | + |
| 363 | +// Connect port2 to vm2 |
| 364 | +port2.readable.pipeTo(vm2Nic.writable); |
| 365 | +vm2Nic.readable.pipeTo(port2.writable); |
| 366 | + |
| 367 | +// Bridge the two ports together |
| 368 | +const bridge = await stack.createBridgeInterface({ |
| 369 | + ports: [port1, port2], |
| 370 | + ip: '192.168.1.1/24', |
| 371 | +}); |
| 372 | +``` |
| 373 | + |
| 374 | +In the above example, `vm1` and `vm2` are attached together via a shared LAN. We treat the tcpip.js stack as the virtual router/switch where each VM connects to their own [tap interface](#tap-interface) (`port1` and `port2`) which are then bridged together. The bridge interface has its own MAC and IP address (`192.168.1.1`), representing the address of the virtual router. This follows the exact same bridging pattern that a physical router would in a real network. |
| 375 | + |
| 376 | +Notice that we intentionally don't set IP addresses on the tap interfaces - they are only used to forward ethernet frames to/from the bridge. The bridge interface itself is where we set the IP address that the VMs can communicate with. |
| 377 | + |
| 378 | +This allows you to, for example, host a TCP server on the router itself in order to communicate with the VMs from JavaScript. You would simply create a TCP server on the stack like so: |
| 379 | + |
| 380 | +```ts |
| 381 | +const listener = await stack.listenTcp({ |
| 382 | + port: 80, |
| 383 | +}); |
| 384 | +``` |
| 385 | + |
| 386 | +The server would be accessible to any VM connected to the bridge via `192.168.1.1:80`. For more information on TCP, see the [TCP API](#tcp-api). |
| 387 | + |
| 388 | +Note that `BridgeInterface` does not expose its own `readable` or `writable` stream - instead you would send and receive frames through each `TapInterface` port that is part of the bridge. |
330 | 389 |
|
331 | 390 | ### Other interfaces
|
332 | 391 |
|
333 |
| -Looking for another type of interface? See [Future plans](#future-plans). |
| 392 | +Looking for another type of network interface? See [Future plans](#future-plans). |
334 | 393 |
|
335 | 394 | ### Removing interfaces
|
336 | 395 |
|
@@ -591,7 +650,6 @@ _Background:_ Vite optimizes dependencies during development to improve build ti
|
591 | 650 | - [ ] DNS API
|
592 | 651 | - [ ] mDNS API
|
593 | 652 | - [ ] Hosts file
|
594 |
| -- [ ] Bridge interface |
595 | 653 | - [ ] Experimental Wireguard interface
|
596 | 654 | - [ ] Node.js net polyfill
|
597 | 655 | - [ ] Deno net polyfill
|
|
0 commit comments