Skip to content

wasi-sockets: Unify the p2 & p3 implementations#13934

Merged
alexcrichton merged 2 commits into
bytecodealliance:mainfrom
badeend:addr-checks
Jul 22, 2026
Merged

wasi-sockets: Unify the p2 & p3 implementations#13934
alexcrichton merged 2 commits into
bytecodealliance:mainfrom
badeend:addr-checks

Conversation

@badeend

@badeend badeend commented Jul 22, 2026

Copy link
Copy Markdown
Member

Currently, the shared socket code (crates/wasi/src/sockets/*) is modeled after the
p2 interface, with p3 bindings papering over the differences. Some
functionality is duplicated between the p2/* and p3/* implementations. This has
already caused bugs in the past.

Since the p3 interface is strictly a superset of p2, I think it makes more sense to invert that relationship. This PR updates the shared code to become the canonical (p3-shaped)
implementation with the p3's bindings now a thin wrapper around it. All
p2-only behavior & state is moved into the p2 bindings layer. Think of: artificial restrictions like disallowing implicit
binds, and the start/finish async bookkeeping.

Other notable changes:

  • Each socket now stores its own copy of the WasiCtx's
    socket_addr_check at creation time, allowing all permission checks to live in
    the shared socket code instead of being split across p2/p3. The p2
    Network resource is now just an unforgeable capability token (its
    permission-checking responsibilities are gone).
  • Removed the Bound state; whether a socket is bound is now determined
    lazily via getsockname/local_addr instead of tracked explicitly.
    This simplifies the implementation of UdpSocket::send, allowing it to return a future that can run to completion independently, without needing to rendezvous back with the socket to update its state.
  • Updated SocketAddrUse:
    • added TcpListen, TcpAccept, UdpReceive checks.
    • replaced UdpConnect with calls to UdpSend and UdpReceive. On UDP sockets, "connecting" is just a local operation that sets the default remote address for future sends and receives. It does not actually do any I/O on its own.
    • Note to reviewer: this is a breaking change.
  • Tcp&UdpSocket::connect now also issue a permission
    check for the implicit bind that the OS performs when connecting an
    unbound socket, matching the check already done for explicit binds. Following through the changes of Consult the host's socket_addr_check for implicit binds  #13677
  • Removed the StdView type introduced in Reduce dependencies on cap-std-family crates #13872 infavour of direct usage of BorrowedFd

Added test coverage for previously-untested behavior:

  • test_udp_disconnect_local_address (p2 & p3): disconnecting a UDP
    socket should keep it bound. Exercises the removal of the explicit Bound
    state in favor of lazily-derived bound status.
  • test_udp_send_receive_multiple (p2): a single send/receive call
    can carry multiple datagrams at once (bulk send/receive over a
    connected stream()).
  • test_udp_receive_after_connect (p2): datagrams from different peers
    queued before a socket is connected are correctly filtered so that
    stream(Some(addr)) only returns datagrams from the provided address.

All-in-all, the goal of this PR is to consolidate the socket code (even further) into a single place and make it less error-prone to maintain. I realize there's quite a bit of unannounced code movement here, so let me know what you think.

The shared socket code (crates/wasi/src/sockets/*) was modeled after
the p2 interface, with p3 bindings papering over the differences, and
some functionality duplicated between p2/* and p3/*. This has already
caused bugs in the past (bytecodealliance#13631).

Since the p3 interface is strictly a superset of p2, invert that
relationship: the shared code becomes the canonical (p3-shaped)
implementation, and p3's bindings are now a thin wrapper around it.
All p2-only behavior & state moves into the p2 bindings layer instead
(artificial restrictions like disallowing implicit binds, and the
start/finish async bookkeeping).

Other notable changes:

- Each socket now stores its own copy of the WasiCtx's
  socket_addr_check at creation time, so all permission checks live in
  the shared socket code instead of being split across p2/p3. The p2
  Network resource is now just an unforgeable capability token.
- Removed the Bound state; whether a socket is bound is now determined
  lazily via getsockname/local_addr instead of tracked explicitly. This
  lets UdpSocket::send return a future that runs to completion
  independently, without needing to rendezvous back with the socket to
  update its state.
- Updated SocketAddrUse: added TcpListen, TcpAccept, UdpReceive;
  replaced UdpConnect with UdpSend/UdpReceive checks, since connecting
  a UDP socket is just a local operation that sets the default remote
  address and does no I/O of its own. connect() now succeeds as long
  as either send or receive is permitted for the address.
- Tcp/UdpSocket::connect now also check the implicit bind the OS
  performs when connecting an unbound socket, matching the check
  already done for explicit binds.

Added test coverage for previously-untested behavior:

- test_udp_disconnect_local_address (p2 & p3): disconnecting a UDP
  socket keeps it bound.
- test_udp_send_receive_multiple (p2): bulk send/receive of multiple
  datagrams in a single call over a connected stream().
- test_udp_receive_after_connect (p2): datagrams from other peers
  queued before connecting are filtered out once connected.
@badeend
badeend requested review from a team as code owners July 22, 2026 14:38
@badeend
badeend requested review from pchickey and removed request for a team July 22, 2026 14:38
@github-actions github-actions Bot added the wasi Issues pertaining to WASI label Jul 22, 2026
@alexcrichton
alexcrichton requested review from alexcrichton and removed request for pchickey July 22, 2026 17:46

@alexcrichton alexcrichton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for this! This all sounds great to me and I think the refactoring's a good idea. Do you think long-term it would be possible to fold the utils.rs module into the tcp/udp modules directly? That seems a vestige of a time where it was a home of common functionality between p2/p3, but nowadays that should just be {Tcp,Udp}Socket. (Fine to happen in a follow-up PR of course)

Comment on lines +174 to +176
if finish {
return Poll::Ready(Ok(StreamResult::Cancelled));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this the handling of finish is a bit subtle, but the basic idea is that this continues to do everything below and then instead of returning Pending at the last second it returns Cancelled instead. That gives sort of a "final chance" to get everything completed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning was that; if the guest cancels the read/write, there's no point in us to querying the OS again for something the guest isn't interested in anymore. Which is why I had the early return.

But I'm fine with giving it one last poll. See latest commit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the intent of the guest is a bit ambiguous because it could mean "I still want to try to finish, but clear out otherwise" vs "I'm totally uninterested in the result just clean up" which is conflated right now

Comment thread crates/wasi/src/p3/sockets/host/types/tcp.rs Outdated
Comment thread crates/wasi/src/p3/sockets/host/types/tcp.rs Outdated
@badeend

badeend commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Do you think long-term it would be possible to fold the utils.rs module into the tcp/udp modules directly?

For the things that are explicitly marked as tcp_* & udp_*; Yes, that should be possible.

There are also a bunch of functions that are shared between tcp & udp, like:

  • is_valid_address_family
  • normalize_get_buffer_size
  • get_ip_ttl
  • unspecified_addr
  • ..

For those , I think the best we can do is move them into mod.rs.

@alexcrichton

Copy link
Copy Markdown
Member

Makes sense yeah, not something worth proactively doing necessarily, just curious!

@alexcrichton
alexcrichton added this pull request to the merge queue Jul 22, 2026
Merged via the queue into bytecodealliance:main with commit 62371d3 Jul 22, 2026
53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

wasi Issues pertaining to WASI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants