Networking

The virtual network stack — interfaces, sockets, DNS, ports — and how in-VM HTTP servers work.

Each box has a NetworkStack — a virtual network with interfaces, sockets, routes, and a DNS resolver — plus a portRegistry on the kernel that maps a port number to a request handler. Together they let programs inside the VM listen, connect, and serve as if they were on a real machine, without any real sockets.

Listening and serving

When a program creates an HTTP server through the http shim and calls listen(port), the server registers a VirtualRequestHandler for that port on the kernel. Anything that can produce a VirtualRequest — the service worker, the SW-free preview, a tunnel, sandbox.fetch(), a host port forward, or an in-VM client — reaches the server by looking up its port.

// inside the VM
const http = require("http");
http.createServer((req, res) => res.end("hi")).listen(3000);

Now port 3000 is in the registry, and a browser preview at `/_sw/<boxId>/3000/` (or a tunnel) can hit it. From the code that owns the box, call it directly:

// on the host
const res = await sandbox.fetch("http://localhost:3000/");
await res.text();   // "hi"

Handlers are called synchronously but may finish asynchronously — Express and Vite return immediately and write the response later. Every transport goes through one shared dispatcher that waits for that completion, so don't invoke a handler by hand; use sandbox.fetch() or dispatchRequest.

Sockets, DNS, and routes

The NetworkStack models the lower layers too:

  • Interfaces & routes — a loopback and virtual interfaces, inspectable with the ifconfig, ip, route, and netstat commands.
  • DNS — a resolver seeded from /etc/hosts (loaded at boot). host queries it; the network layer uses it to resolve names.
  • Socketsnet/tls sockets for programs that speak raw TCP rather than HTTP.

The ports command lists what's currently listening, and forward / unforward manage port forwards.

Reaching the outside world

A browser tab can't make arbitrary cross-origin requests, so outbound HTTP from inside the VM is routed through a CORS proxy for hosts that don't send CORS headers (for example api.expo.dev, which create-expo-app calls). The Node layer injects a proxying fetch into each module and serves require("fetch-nodeshim") from the same stack, so a package's networking is transparently proxied. See Tunnelling for the proxy and relay.

Git over CORS

Git hosts (github.com, gitlab.com, …) don't send CORS headers either, so git clone, git fetch, and git push in the browser route through a separate git CORS proxy — a clone's pack data can be tens of MB, so it's kept off the same-origin proxy used for small API calls. Set LIFO_GIT_CORS_PROXY to point it at a proxy (the playground defaults to the public cors.isomorphic-git.org; self-host one — e.g. a Cloudflare Worker running @isomorphic-git/cors-proxy — for production). In Node there's no CORS wall, so git talks to remotes directly and the setting is ignored.

The curl command

curl is built in and speaks to both in-VM servers and (via the proxy) the outside world — useful for testing a dev server from the shell:

curl http://localhost:3000
curl -H "Accept-Encoding: identity" http://localhost:8081/index.bundle

Note

The virtual network is per-box. Two boxes don't share ports or sockets — which is exactly what you want when running many isolated sandboxes in one process.