# Node network protocol This page is generated from the protocol specification, `services/nodes/spec.py`, the same data served at [`/nodes/spec.json`](/nodes/spec.json). The server, the clients, the tests and this page all read that one source, so the numbers below cannot drift from the code. **Status today:** the specification, `/nodes/spec.json`, device registration, the socket ticket, the node socket and the consent gate are implemented on the server and are switched off by default. The job queue, scheduler, tunnel bridge and result checks run on the server, a native Go client and a browser extension speak this protocol, and social search and DeepSearch can use the network. The tunnel path has been run end to end against a real client; how well it works from residential lines is not measured yet. Nothing on this page is enabled for any account until an administrator switches the network on. ## Rules that never change - **Not an open proxy.** `open_proxy` is `{{ nodes_spec.open_proxy|lower }}`. A node only ever contacts hosts on the allowlist below. Changing that is a protocol change, not a setting. - **Web ports only.** Ports {% for port in nodes_spec.allowed_ports %}`{{ port }}`{% if not loop.last %} and {% endif %}{% endfor %} and nothing else. - **The donor enforces it too.** A client refuses a request that breaks these rules even when the server asks for it, so a compromised server cannot aim a donor's machine at their own network. The refusal codes are listed below. - **Opt-in.** A node never connects unless the account holder granted the consent. Withdrawing it disconnects every node of that account. - **No cookies, no accounts.** A node never attaches the donor's cookies or logins to a request. ## Version Protocol version `{{ nodes_spec.version }}`, oldest supported `{{ nodes_spec.min_supported }}`. A client announces its version in `hello`; an unsupported version is closed with code `4400`. ## Transports | Mode | Used by | Who can read the content | Trust | |------|---------|--------------------------|-------| | `tunnel` | native clients | nobody on the way: TLS runs end to end between DevPlace and the destination, through the donor's socket | tampering is impossible | | `fetch` | browser extension | the donor can read and could forge the response | verified by structure checks and sampled redundancy, labelled `fetch` in results | ## Frames Every frame is one JSON object with a `type`. An unknown type, a missing required field, an unexpected field or an oversize frame closes the socket with a reason. Direction `c2s` is client to server, `s2c` is server to client. | Type | Direction | Max bytes | Fields | |------|-----------|-----------|--------| {% for frame in nodes_spec.frames %}| `{{ frame.type }}` | {{ frame.direction }} | {{ frame.max_bytes }} | {% for item in frame.fields %}`{{ item.name }}`: {{ item.type }}{% if not item.required %} (optional){% endif %}{% if not loop.last %}, {% endif %}{% else %}none{% endfor %} | {% endfor %} ## Close codes | Code | Meaning | |------|---------| | `4013` | this worker does not own the node socket, reconnect immediately | | `4029` | the network is at its connection capacity | | `4400` | protocol error or unsupported version | | `4403` | kicked: consent withdrawn, device revoked or suspended | | `1008` | policy: not authenticated | | `1009` | frame too large | ## Limits Every number is a hard ceiling. Nothing is unbounded and nothing autotunes. | Limit | Value | |-------|-------| {% for name, value in nodes_spec.limits.items() %}| `{{ name }}` | {{ value }} | {% endfor %} ## Destination allowlist | Host suffix | Adapter | Methods | Ports | Modes | Max requests per second | |-------------|---------|---------|-------|-------|-------------------------| {% for item in nodes_spec.destinations %}| `{{ item.suffix }}` | {{ item.adapter }} | {{ item.methods|join(", ") }} | {{ item.ports|join(", ") }} | {{ item.modes|join(", ") }} | {{ item.max_rps }} | {% endfor %} A host matches a suffix when it equals it or ends with `.` plus the suffix. `evilreddit.com` and `reddit.com.evil.net` do not match `reddit.com`. ## Refusal codes A client reports one of these in `job_error` when it declines a job. | Code | Meaning | |------|---------| {% for item in nodes_spec.refusals %}| `{{ item.code }}` | {{ item.meaning }} | {% endfor %} ## Donor-side checks Before any connection a client resolves the host itself, refuses private, loopback, link-local, CGNAT and multicast addresses, then connects to the address it resolved. It follows no redirect that leaves the allowlist. It applies the donor's own bandwidth, concurrency and schedule limits locally, even when the server sends larger ones. ## Related - [Community nodes API](/docs/nodes.html) - [Privacy](/docs/privacy.html)