Kernel UDP host-relay abstraction#530
Open
mho22 wants to merge 5 commits into
Open
Conversation
This was referenced May 20, 2026
Phase B-1 matrix build status —
|
| Package | Arch | Status | Sha |
|---|---|---|---|
| bash | wasm32 | built | d3f62e36 |
| bc | wasm32 | built | 3f9d8b42 |
| bzip2 | wasm32 | built | 00594792 |
| coreutils | wasm32 | built | 2c7d33d1 |
| curl | wasm32 | built | 4146a843 |
| dash | wasm32 | built | 21fd2b13 |
| dinit | wasm32 | built | fef7e94b |
| file | wasm32 | built | 4d577cd7 |
| git | wasm32 | built | adac2e4d |
| grep | wasm32 | built | 3ee0e9c4 |
| gzip | wasm32 | built | c6ac4ceb |
| kandelo-sdk | wasm32 | built | 7d2fa7e1 |
| lamp | wasm32 | built | 192bc0bd |
| less | wasm32 | built | 431586da |
| lsof | wasm32 | built | 2c7ed293 |
| m4 | wasm32 | built | 6dcaf0a4 |
| make | wasm32 | built | b066d56d |
| mariadb-test | wasm32 | built | 07f15fd5 |
| mariadb-vfs | wasm32 | built | 9b1847a5 |
| mariadb-vfs | wasm64 | built | d58f3d27 |
| msmtpd | wasm32 | built | 541a8631 |
| nano | wasm32 | built | 2b4c6f4c |
| nethack-browser-bundle | wasm32 | built | 813f4cad |
| nethack | wasm32 | built | f42afa6b |
| nginx | wasm32 | built | 1aaf2c60 |
| node-vfs | wasm32 | built | 9b3f10d0 |
| node | wasm32 | built | d61cb6e7 |
| php | wasm32 | built | 894abbfb |
| posix-utils-lite | wasm32 | built | 7fea317d |
| rootfs | wasm32 | built | cbbee4f3 |
| sed | wasm32 | built | 06df07c9 |
| shell | wasm32 | built | 48783990 |
| spidermonkey-node | wasm32 | built | 4dc4e333 |
| spidermonkey | wasm32 | built | 98906836 |
| tar | wasm32 | built | fa00ec94 |
| tcl | wasm32 | built | d418f58d |
| unzip | wasm32 | built | ae858c13 |
| vim-browser-bundle | wasm32 | built | 928b3750 |
| vim | wasm32 | built | 097341d8 |
| wget | wasm32 | built | b8a0829b |
| wordpress | wasm32 | built | 63105217 |
| xz | wasm32 | built | 4e40c108 |
| zip | wasm32 | built | d4ca2188 |
| zstd | wasm32 | built | ecf8718d |
Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.
Add the kernel surface needed to route UDP datagrams between user programs and a host-owned transport (in v1, the browser's WebRTC RelayHostShim — wiring lands in subsequent tasks). * HostIO::send_dgram trait method (process.rs) with a default ENETUNREACH impl, overridden only by WasmHostIO. All existing concrete impls (the in-test MockHostIO and others) compile unchanged. * sys_sendto (syscalls.rs) now dispatches non-loopback destinations through host.send_dgram instead of returning ENETUNREACH. Loopback FIFO branch is untouched; test_udp_loopback still passes and grew a one-line guard asserting the host relay stays empty on the loopback path. New test_udp_non_loopback_routes_to_host covers the new branch via a MockHostIO that captures (src_port, dst_ip, dst_port, data) tuples. * wasm_api.rs gains a host_send_dgram import, the WasmHostIO wrapper using the value-returning errno convention, and a new kernel_inject_datagram export. The export scans proc.sockets for a DGRAM in Bound/Connected state with matching bind_port (mirroring sys_sendto's loopback scan) and pushes onto dgram_queue; returns -ESRCH / -ECONNREFUSED on failure. No wakeup call — sys_recvfrom's unconditional-EAGAIN behaviour is a documented follow-up and DOOM tolerates polling. * abi/snapshot.json regenerated. The single added kernel_exports entry (kernel_inject_datagram) is additive under the post-PR-#490 policy in docs/abi-versioning.md — ABI_VERSION stays at 11. scripts/check-abi-version.sh reports "additive-compatible change: added kernel_exports entry kernel_inject_datagram" and "ABI_VERSION may stay unchanged". The new host_send_dgram import is not tracked by the snapshot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the host-side abstraction for the kernel's UDP host-relay hooks: BrowserKernel grows a fire-and-forget `injectDatagram()` and an `onHostSendDgram()` subscription, and the main↔kernel-worker message protocol gains the two new types. * host/src/browser-kernel-protocol.ts — `InjectDatagramMessage` (main → kernel-worker) and `HostSendDgramMessage` (kernel-worker → main). * host/src/browser-kernel-host.ts — `injectDatagram()` and `onHostSendDgram(handler)` accessors on BrowserKernel. The page-side WebRTC relay (added in a follow-up PR) uses these to bridge a RTCDataChannel to the kernel. UDP drops are indistinguishable from wire-level loss, so both APIs are fire-and-forget — no requestId, no result to await (~35 Hz cadence in the consuming demo). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the kernel-worker side of the UDP host-relay hooks from the
previous commit. With this, an outbound `sendto()` from a user program
reaches the page-side host via a postMessage; an inbound datagram
posted via `BrowserKernel.injectDatagram` reaches the bound DGRAM
socket.
* host/src/kernel.ts — `KernelCallbacks` gains optional
`onHostSendDgram`; the wasm imports table binds the kernel's
`host_send_dgram` import to a private `hostSendDgram()` that reads
the data slice out of wasm memory and forwards to the callback.
Returns -101 (-ENETUNREACH) when no callback is registered,
matching the kernel-side `HostIO::send_dgram` trait default.
* host/src/kernel-worker.ts — `CentralizedKernelWorker` registers an
`onHostSendDgram` default that returns -101 (-ENETUNREACH). Inline
comment names the override site so the next reader sees the Node
behavior without grepping.
* host/src/browser-kernel-worker-entry.ts — adds the `RelayHostShim`
class: a thin wrapper that postMessages
`{ type: "host_send_dgram", ... }` up to the main thread.
Unconditional instantiation (idle until called); wired through the
`kw.kernel.callbacks` override block alongside `onNetListen`. Adds
`handleInjectDatagram` + "inject_datagram" message dispatch: copies
payload into kernel scratch, calls `kernel_inject_datagram`.
Dual-host parity (CLAUDE.md):
- Node host: no `host_send_dgram` registration needed — the
kernel-worker default returns -ENETUNREACH (matches kernel-side
trait default). No "inject_datagram" handler in
node-kernel-worker-entry.ts — Node has no RelayChannel and never
sends one.
- Browser host: full wiring via `RelayHostShim` + `handleInjectDatagram`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The kernel_inject_datagram export (cd7620f / earlier branch commit) pushed onto proc.sockets[*].dgram_queue with no cap. Pre-WebRTC, only intra-process loopback fed this queue, so a misbehaving local program was the only threat model. With the WebRTC relay landed, a remote peer over an RTCDataChannel can fill the queue at line rate; a stalled userspace reader then grows the kernel heap without bound. One-line DoS surface for any "play a stranger" link. Fix: • Cap at MAX_INJECT_DGRAM_QUEUE_LEN = 256 per matching socket. Sized to absorb a few seconds of fbDOOM net-tick bursts (~35 Hz × a handful of stalled accepters) while still bounding kernel-heap growth at ~256 datagrams per process. Overflow returns 0 (UDP is best-effort; the upstream host has no recourse for a queue-full signal, and an errno would just propagate noise). • Extract the per-process logic into syscalls::inject_datagram_into so it's unit-testable with a local Process — the wasm export wrapper (kernel_inject_datagram in wasm_api.rs) is the only piece that still touches PROCESS_TABLE. (The wasm_api module only compiles for wasm32/wasm64; tests live in syscalls.rs which compiles for the host target too.) Three new unit tests pin the contract (F3 from session-13 audit): • test_inject_datagram_no_bound_socket → -ECONNREFUSED • test_inject_datagram_pushes_and_recvfrom_delivers — successful push, sys_recvfrom returns the data, src_addr/src_port restored on the wire • test_inject_datagram_caps_queue_and_silently_drops_overflow — 256 pushes succeed, 16 overflow attempts return 0, queue stays at exactly 256 The ESRCH path in kernel_inject_datagram (pid not in PROCESS_TABLE) is the standard match on `table.get_mut(pid)` used by every other kernel_* export; covered by inspection rather than by polluting GLOBAL_PROCESS_TABLE from cargo tests that run in parallel. ABI: kernel_inject_datagram export signature unchanged → ABI snapshot still additive-compatible vs origin/main; ABI_VERSION unchanged. Tested on both hosts: pure-kernel change inside wasm. cargo: 841/0 (+3). libc-test / posix-test boot the rebuilt kernel.wasm and pass. Browser end-to-end (DOOM session) still works via the unchanged RelayHostShim path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Rust kernel side of UDP non-loopback routing is covered by test_udp_non_loopback_routes_to_host (syscalls.rs), but that test overrides host_send_dgram via MockHostIO and never exercises the actual default that ships with the Node kernel-worker (host/src/kernel-worker.ts:1139-1154, returning -101/-ENETUNREACH). This was DA #3 from the 2026-05-20 session-13 audit: > The browser side is the one we actually use the relay through, > and the user's manual two-browser verification covers it. The > Node default — which programs running under NodeKernelHost see — > had zero coverage. CLAUDE.md §"Two hosts" calls out this exact > failure mode (PR #388 / #410 — a Node-only fix breaks the > browser, or vice versa). New test boots a real NodeKernelHost via the runCentralizedProgram helper, runs a tiny C program that bind()s a DGRAM socket and sendto()s to 10.0.0.1:1234 (non-loopback, non-broadcast, non-zero), and asserts: • sendto returns -1 • errno == ENETUNREACH (101) • program exits with status 0 (the C program reports PASS/FAIL) scripts/build-programs.sh auto-picks the new programs/*.c file, so no build-script change is needed. The resolver under local-binaries/ takes precedence over the published-binaries/ tree so a fresh worktree's `scripts/build-programs.sh` run is sufficient. Tested on both hosts: this test is the Node-side coverage; the browser-side path is exercised by the page-level relay-network-backend.test.ts (RelayHostShim → RelayChannel envelope) plus the user's manual two-browser DOOM session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
797bd63 to
0cee0e6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of three split PRs from #60 (
emdash/explore-doom-webrtc-8179f). Adds the kernel surface and host wiring that lets the kernel route non-loopback UDP datagrams through a host-owned transport, then exposes the symmetric inbound path so the host can post datagrams back to a boundSOCK_DGRAM. No WebRTC code here — that lands in the follow-up PR.What changes
Kernel (
crates/kernel/,abi/snapshot.json)HostIO::send_dgramtrait method onprocess.rswith a default-ENETUNREACHimpl. Overridden only byWasmHostIO. Every existing concrete impl (MockHostIO, etc.) compiles unchanged.sys_sendtonow dispatches non-loopback destinations throughhost.send_dgraminstead of returningENETUNREACH. Loopback FIFO branch is untouched.wasm_api.rsgains ahost_send_dgramimport (value-returning errno convention) and a newkernel_inject_datagramexport. The export scansproc.socketsfor a DGRAM inBound/Connectedstate with a matchingbind_portand pushes ontodgram_queue; returns-ESRCH/-ECONNREFUSEDon failure.kernel_inject_datagramis bounded atMAX_INJECT_DGRAM_QUEUE_LEN = 256per matching socket. Sized to absorb a few seconds of net-tick bursts while still bounding kernel-heap growth under a misbehaving remote feeding the queue at line rate.syscalls::inject_datagram_intoso it's unit-testable from aProcessfixture; the wasm export wrapper is the only piece that still touchesPROCESS_TABLE.Host (
host/src/)host/src/browser-kernel-protocol.ts—InjectDatagramMessage(main → kernel-worker) andHostSendDgramMessage(kernel-worker → main).host/src/browser-kernel-host.ts—BrowserKernel.injectDatagram()andonHostSendDgram(handler)accessors. Both are fire-and-forget; UDP drops are indistinguishable from wire loss.host/src/kernel.ts—KernelCallbacksgains optionalonHostSendDgram; the wasm imports table binds the kernel'shost_send_dgramimport to a privatehostSendDgram()that forwards to the callback (returns-ENETUNREACHwhen unset).host/src/kernel-worker.ts—CentralizedKernelWorkerregisters anonHostSendDgramdefault that returns-ENETUNREACH, matching the kernel-side trait default.host/src/browser-kernel-worker-entry.ts— adds theRelayHostShimclass (postMessageshost_send_dgramup to the main thread) andhandleInjectDatagramfor theinject_datagrammessage dispatch.Dual-host parity (CLAUDE.md §"Two hosts")
host_send_dgramregistration needed — the kernel-worker default returns-ENETUNREACH(matches the kernel-side trait default).node-kernel-worker-entry.tshas noinject_datagramhandler (Node has noRTCDataChannel).RelayHostShim+handleInjectDatagram. The page-sideRelayChannelthat consumes these hooks lands in the follow-up PR.ABI
abi/snapshot.jsonregenerated. The single newkernel_exportsentry (kernel_inject_datagram) is additive under the post-Allow additive ABI snapshot changes without version bumps #490 policy —ABI_VERSIONstays at 11.kernel_inject_datagramexport signature unchanged by the DoS cap commit.Tests added
test_udp_non_loopback_routes_to_host(kernel) — covers the newsys_sendtobranch via aMockHostIOthat captures(src_port, dst_ip, dst_port, data).test_inject_datagram_no_bound_socket→-ECONNREFUSED.test_inject_datagram_pushes_and_recvfrom_delivers— successful push,sys_recvfromreturns the data,src_addr/src_portrestored.test_inject_datagram_caps_queue_and_silently_drops_overflow— 256 pushes succeed, overflow attempts return 0, queue stays at exactly 256.host/test/sendto-non-loopback.test.ts— bootsNodeKernelHost+ a tiny C program thatbind() + sendto(10.0.0.1:1234)and assertserrno == ENETUNREACH. Closes the Node-host parity gap (the kernel-side test usesMockHostIOand never exerciseskernel-worker.ts's actual default).Stacked-PR series
This PR is the first of three split from the original branch. The follow-ups depend on this one being merged first:
split/webrtc-data-channel-relay) — standalone WebRTC chat demo + page-sideRelayChannelthat bridgesRTCDataChannelto the abstraction landed here.split/doom-multiplayer-over-webrtc) — chocolate-doom net stack + POSIXi_net+doom-mpdemo page.🤖 Generated with Claude Code