Skip to content

Add Direct Sockets API support for Isolated Web Apps (-sDIRECT_SOCKETS)#26374

Open
maceip wants to merge 10 commits intoemscripten-core:mainfrom
maceip:direct-sockets-v2
Open

Add Direct Sockets API support for Isolated Web Apps (-sDIRECT_SOCKETS)#26374
maceip wants to merge 10 commits intoemscripten-core:mainfrom
maceip:direct-sockets-v2

Conversation

@maceip
Copy link

@maceip maceip commented Mar 2, 2026

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

feedback fix
share fd allocator with FS / fd collision at 100 (@sbc100) socket fds now allocated via FS.createStream() using the SOCKFS pattern
use autoAddDeps instead of repeating __deps (@sbc100) added autoAddDeps(DirectSocketsLibrary '$DIRECT_SOCKETS')
use cDefs constants instead of hardcoded numbers (@sbc100) all sock opt constants now in struct_info json and referenced via cDefs
eof vs error distinction in read path (copilot) stream_ops read and readFromSocket check sock error before returning eof
parseSockaddr drops specific errno (copilot) returns {errno: X} all callers propagate the specific error
feature detection for TCPSocket/UDPSocket (copilot) abort() under ASSERTIONS -ENOSYS at runtime
bind-then-connect ignores local endpoint (copilot) connect() passes sock localAddress/localPort to constructor opts
udp bind+connect leaks socket (copilot) connect() closes existing sock udpSocket before creating new one
fd_close async issue (copilot) fire-and-forget via stream_ops close fd freed synchronously
add link to direct sockets spec (@sbc100) in file header

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:

  • write: fire-and-forget via writer.write() returns byte count immediately
  • read: consumes from recvQueue (filled by bg reader) throws EAGAIN if empty
  • poll: checks recvQueue.length for readability

new syscalls (beyond #26344)

syscall notes
setsockopt / getsockopt TCP_NODELAY SO_KEEPALIVE SO_RCVBUF SO_SNDBUF IP_ADD_MEMBERSHIP IP_DROP_MEMBERSHIP IP_MULTICAST_TTL IPV6_JOIN_GROUP IPV6_LEAVE_GROUP
poll via recvQueue length checks + async wait w timeout
pipe2 / socketpair in-memory pipe buffers w FS-backed fds
fcntl64 F_GETFL / F_SETFL for O_NONBLOCK
ioctl FIONBIO FIONREAD
write / read via FS stream_ops (the SOCKFS pattern)
_emscripten_lookup_name uses emscriptens std DNS (inetPton4 packed uint32)

files changed

file change
src/lib/libdirectsockets.js new all syscall impls
src/settings.js add DIRECT_SOCKETS flag
src/modules.mjs register libdirectsockets js when flag enabled
src/lib/libsyscall.js guard default socket impls when DIRECT_SOCKETS active
src/lib/libwasi.js fd_close path for direct socket fds
src/struct_info.json added sock opt constants (SO_REUSEADDR TCP_NODELAY IP_ADD_MEMBERSHIP etc)
site/source/docs/porting/networking.rst added direct sockets docs section

usage

emcc -sDIRECT_SOCKETS -sASYNCIFY -sPROXY_TO_PTHREAD -pthread app.c -o app.js

notes

  • setsockopt stub in emscripten_syscall_stubs.c is declared weak so the js lib impl takes priority automatically no need to modify stubs file
  • requires ASYNCIFY (or JSPI) - compile err if neither enabled
  • doh dns resolution split out to follow-up pr per @sbc100 feedback
  • pselect6 not impl yet - poll() covers most use cases and select() can be routed thru it

testing

web demo

demo.mp4

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants