Add Direct Sockets API support for Isolated Web Apps (-sDIRECT_SOCKETS)#26374
Add Direct Sockets API support for Isolated Web Apps (-sDIRECT_SOCKETS)#26374maceip wants to merge 3 commits intoemscripten-core:mainfrom
Conversation
Replaces the WebSocket-to-POSIX-socket proxy with Chrome's Direct Sockets API (TCPSocket, TCPServerSocket, UDPSocket) for real TCP/UDP networking from WASM in Isolated Web Apps. Socket fds are registered in Emscripten's FS using the SOCKFS pattern (FS.createNode + FS.createStream with custom stream_ops), so write(fd) and read(fd) work on socket file descriptors -- required by OpenSSL and other libraries that use write()/read() instead of send()/recv(). New files: src/lib/libdirectsockets.js - all socket syscall implementations Modified files: src/settings.js - adds DIRECT_SOCKETS flag src/modules.mjs - registers libdirectsockets.js when flag is enabled src/lib/libsyscall.js - guards default socket impls when active src/lib/libwasi.js - fd_close path for Direct Socket fds emscripten_syscall_stubs.c - comments out conflicting setsockopt stub Usage: emcc -sDIRECT_SOCKETS -sJSPI -sPROXY_TO_PTHREAD -pthread app.c -o app.js Tested with Tor (unmodified upstream C) compiled to WASM, bootstrapping 100% in ~15 seconds in a Chrome IWA, and a QUIC stack (ngtcp2 + wolfSSL + nghttp3) achieving 90% of native throughput on UDP.
There was a problem hiding this comment.
Pull request overview
Adds a new -sDIRECT_SOCKETS build mode that routes POSIX socket syscalls to Chrome’s Direct Sockets API (TCPSocket/TCPServerSocket/UDPSocket) for real TCP/UDP networking in Isolated Web Apps, using an FS stream-backed “SOCKFS pattern” so read(fd)/write(fd) work for libraries like OpenSSL.
Changes:
- Introduces
src/lib/libdirectsockets.jsimplementing a Direct Sockets-backed syscall layer (socket/connect/bind/listen/accept/send/recv, poll, pipe2/socketpair, fcntl/ioctl, DoH name lookup). - Adds a new
DIRECT_SOCKETSsetting and wires the new library intocalculateLibraries(). - Adjusts existing syscall/WASI plumbing to avoid conflicting socket implementations when
DIRECT_SOCKETSis enabled, and modifies a syscall stub.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
src/lib/libdirectsockets.js |
New Direct Sockets syscall backend + FS stream integration + DoH name lookup. |
src/settings.js |
Adds DIRECT_SOCKETS build setting. |
src/modules.mjs |
Adds libdirectsockets.js to the JS library list when enabled. |
src/lib/libsyscall.js |
Disables SOCKFS socket syscalls when DIRECT_SOCKETS is active. |
src/lib/libwasi.js |
Adds a fd_close branch for Direct Socket fds in no-filesystem WASI path. |
system/lib/libc/emscripten_syscall_stubs.c |
Comments out the __syscall_setsockopt stub to avoid intercepting the JS implementation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… errno propagation, state preservation, DNS fixes, revert stubs.c
|
Can you mention this new networking method in site/source/docs/porting/networking.rst? |
| createSocketState(family, type, protocol) { | ||
| #if ASSERTIONS | ||
| if (typeof globalThis.TCPSocket === 'undefined' && | ||
| typeof globalThis.UDPSocket === 'undefined') { |
There was a problem hiding this comment.
I think you can just do if (!globalThis.TCPSocket && !globalThis.UDPSocket)
| var IP_MULTICAST_TTL = 33, IP_MULTICAST_LOOP = 34; | ||
| var IP_ADD_MEMBERSHIP = 35, IP_DROP_MEMBERSHIP = 36; | ||
| var IPV6_MULTICAST_LOOP = 18, IPV6_MULTICAST_HOPS = 19; | ||
| var IPV6_JOIN_GROUP = 20, IPV6_LEAVE_GROUP = 21; |
There was a problem hiding this comment.
You can add these to src/struct_info.json to make them available via cDefs (just run ./tools/gen_struct_info.py after ending).
| return revents; | ||
| }, | ||
|
|
||
| // Async DNS resolution via DNS-over-HTTPS (DoH) |
There was a problem hiding this comment.
This Async DNS resolution via DNS-over-HTTPS thing seems like a separate feature to the rest of direct sockets.
Perhaps split it out into its own PR?
| * (https://wicg.github.io/direct-sockets/) to provide real TCP/UDP networking | ||
| * in Isolated Web Apps without needing a proxy server. | ||
| */ | ||
|
|
There was a problem hiding this comment.
Perhaps add #error here if ASYNCIFY is not defined (or error our somewhere else earlier).
|
apologies for not getting into this sooner im in between jobs and tokens |
|
done - added direct sockets section to site/source/docs/porting/networking.rst w usage info and supported syscall list |
replaces the websocket-to-posix-socket proxy with chromes Direct Sockets API (TCPSocket TCPServerSocket UDPSocket) for real tcp/udp networking from wasm in isolated web apps
context on changes from #26344
rewrite that incorporates all review feedback from #26344 and expands scope to fully support the direct socket api - driven by A) young jedis annoying me about web transport and B) the archive org folks asking about tor-in-wasm this past weekend in berlin - with my janky syscall wiring plus this patch you get unbelievable perf across udp [incl session tickets] and tcp - shout outs to emscripten core devs and blink/v8 devs this shouldnt be possible
feedback addressed from #26344
key architectural change: SOCKFS pattern
the original pr used a private fd allocator (nextFd: 100) - this version registers socket fds in emscriptens FS using FS.createNode() + FS.createStream() with custom stream_ops the same pattern SOCKFS uses for websocket-backed sockets - this means write(fd) and read(fd) route through direct sockets which is reqd by openssl (its socket BIO uses write()/read() not send()/recv())
stream_ops must be synchronous bc theyre called from js to js (FS.write -> stream_ops.write) not wasm to js so JSPI cant suspend:
new syscalls (beyond #26344)
files changed
usage
emcc -sDIRECT_SOCKETS -sASYNCIFY -sPROXY_TO_PTHREAD -pthread app.c -o app.js
notes
testing
https://github.com/maceip/tor.iwa
https://github.com/maceip/socket.iwa
web demo
demo.mp4