userland.rundocs70Try the demo
Reference

Networking

NanoVM has no TCP/IP stack to the internet. Outbound HTTP(S) goes through the host's fetch() via the /dev/__net__ device; inbound requests to servers in the VM come from the service worker. Browser rules (CORS) apply to every outbound request.

Direction
Mechanism
Docs
guest → internet
/dev/__net__: the guest writes <METHOD> <URL>, the host performs fetch() and frames the HTTP/1.1 response back
this page
guest → guest
loopback: servers and clients inside the same VM or kernel
browser → guest
service worker + injectConnection() into a listening socket

The fetch bridge: /dev/__net__

A program opens /dev/__net__ read-write, writes a request head (GET https://…, optional headers, a blank line, an optional body) and reads back HTTP/1.1 <status> with headers and body until EOF. The nfetch catalog app is a tiny client for it.

catalog app

Fetch JSON from the guest

nfetch runs on the RISC-V runner; the bytes come from the npm registry through the page's fetch().

bash
nfetch https://registry.npmjs.org/left-pad/latest | grep -o '"version":"[^"]*"'
nfetch -i https://registry.npmjs.org/left-pad/latest | head -c 15
loading…
Expected output
installed nfetch@0.1.0 (1 file, verified)
$ nfetch https://registry.npmjs.org/left-pad/latest | grep -o '"version":"[^"]*"'
"version":"1.3.0"
$ nfetch -i https://registry.npmjs.org/left-pad/latest | head -c 15
HTTP/1.1 200
Node.js

The same device from Node.js

On the host engine, fs routes /dev/__net__ to the same bridge.

javascript
const fs = require("fs");
const fd = fs.openSync("/dev/__net__", "r+");
fs.writeSync(fd, "GET https://registry.npmjs.org/left-pad/latest\n\n");
const buf = Buffer.alloc(65536);
let raw = "";
for (let n; (n = fs.readSync(fd, buf, 0, buf.length, null)) > 0; ) raw += buf.toString("utf8", 0, n);
const body = JSON.parse(raw.slice(raw.indexOf("\r\n\r\n") + 4));
console.log(raw.split("\r\n")[0].trim(), "|", body.name, body.version);
loading…
Expected output
HTTP/1.1 200 | left-pad 1.3.0

CORS

The bridge is the browser's fetch(), so the browser's rules apply: a target must send Access-Control-Allow-Origin for your page, or the request fails. The guest then sees HTTP/1.1 502 Bad Gateway. If a request works with fetch() from your page, it works from the guest.

The CORS proxy

For CORS-blocked targets (git hosts, most APIs) the bridge can retry through a proxy you run. The catalog repository ships one as a Cloudflare Worker (catalog/cors-proxy). It fails closed: it answers only browser origins listed in ALLOWED_ORIGINS and target hosts in its ALLOW list, https:// only, GET/HEAD (plus POST to git smart-HTTP endpoints), and forwards a fixed header allow-list (Authorization only with FORWARD_AUTHORIZATION=true).

bash
cd catalog/cors-proxy
npx wrangler deploy   # → https://nano-cors-proxy.<account>.workers.dev

// then, in your page
nano.raw.setNetwork({ corsProxyUrl: "https://nano-cors-proxy.<account>.workers.dev" });

The bridge always tries a direct fetch(url) first and only on a CORS or network failure retries fetch(corsProxyUrl + "?apiurl=" + encodeURIComponent(url)). Never point it at a proxy you don't operate.

Turning the network off

SDK · JavaScript

setNetwork({ disabled: true })

With the device disabled, opening /dev/__net__ fails with EACCES; setNetwork({}) turns it back on (without a proxy).

javascript
await nano.installApp("nfetch");
nano.raw.setNetwork({ disabled: true });
print((await nano.shExec("nfetch https://registry.npmjs.org/left-pad/latest")).stdout.trim());
nano.raw.setNetwork({});
print((await nano.shExec("nfetch https://registry.npmjs.org/left-pad/latest | head -c 1")).stdout);
loading…
Expected output
nfetch: cannot open /dev/__net__: Permission denied (os error 13)
{

What doesn't work

  • Ordinary sockets to the internet: there is no route out of the VM except /dev/__net__. BusyBox wget reports bad address, and Node.js fetch/https on the RISC-V runner fail with getaddrinfo ECONNREFUSED.
  • On the host engine, Node's global fetch() and https.get() don't reach the internet in SDK 0.2.10 (the process ends before fetch settles; https.get connects to a local port). Use the /dev/__net__ device as above.
  • Raw TCP/UDP, ICMP (ping), and DNS lookups: browsers don't expose them.

Inbound traffic (previewing a server in an iframe) is covered on Virtual server and Serve mode.