Skip to content

Commit 8ac8dc9

Browse files
authored
wasi-sockets: Allow implicit binds in 0.3 (#12225)
* Allow TCP `listen` to be called without an explicit `bind` on P3. * Allow UDP `connect` to be called without an explicit `bind` on P3. * Add test to verify that `receive` requires the socket to be bound. * Allow UDP `send` to be called on an unbound socket on P3. * Add feature flags * Add docs * Resolve "unused imports" warning
1 parent 4563107 commit 8ac8dc9

File tree

16 files changed

+304
-57
lines changed

16 files changed

+304
-57
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use test_programs::sockets::supports_ipv6;
2+
use test_programs::wasi::sockets::network::{ErrorCode, IpAddressFamily};
3+
use test_programs::wasi::sockets::tcp::TcpSocket;
4+
5+
/// Socket must be explicitly bound before listening.
6+
fn test_tcp_listen_without_bind(family: IpAddressFamily) {
7+
let sock = TcpSocket::new(family).unwrap();
8+
9+
assert!(matches!(
10+
sock.blocking_listen(),
11+
Err(ErrorCode::InvalidState)
12+
));
13+
}
14+
15+
fn main() {
16+
test_tcp_listen_without_bind(IpAddressFamily::Ipv4);
17+
18+
if supports_ipv6() {
19+
test_tcp_listen_without_bind(IpAddressFamily::Ipv6);
20+
}
21+
}

crates/test-programs/src/bin/p2_udp_connect.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ fn test_udp_connect_wrong_family(net: &Network, family: IpAddressFamily) {
7676
));
7777
}
7878

79+
/// Socket must be explicitly bound before connecting.
80+
fn test_udp_connect_without_bind(family: IpAddressFamily) {
81+
let remote_addr = IpSocketAddress::new(IpAddress::new_loopback(family), SOME_PORT);
82+
83+
let sock = UdpSocket::new(family).unwrap();
84+
85+
assert!(matches!(
86+
sock.stream(Some(remote_addr)),
87+
Err(ErrorCode::InvalidState)
88+
));
89+
}
90+
7991
fn test_udp_connect_dual_stack(net: &Network) {
8092
// Set-up:
8193
let v4_server = UdpSocket::new(IpAddressFamily::Ipv4).unwrap();
@@ -120,5 +132,8 @@ fn main() {
120132
test_udp_connect_wrong_family(&net, IpAddressFamily::Ipv4);
121133
test_udp_connect_wrong_family(&net, IpAddressFamily::Ipv6);
122134

135+
test_udp_connect_without_bind(IpAddressFamily::Ipv4);
136+
test_udp_connect_without_bind(IpAddressFamily::Ipv6);
137+
123138
test_udp_connect_dual_stack(&net);
124139
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use test_programs::p3::wasi::sockets::types::{
2+
IpAddress, IpAddressFamily, IpSocketAddress, TcpSocket,
3+
};
4+
use test_programs::sockets::supports_ipv6;
5+
6+
struct Component;
7+
8+
test_programs::p3::export!(Component);
9+
10+
/// Listen should perform implicit bind.
11+
fn test_tcp_listen_without_bind(family: IpAddressFamily) {
12+
let sock = TcpSocket::create(family).unwrap();
13+
14+
assert!(matches!(sock.get_local_address(), Err(_)));
15+
assert!(matches!(sock.listen(), Ok(_)));
16+
assert!(matches!(sock.get_local_address(), Ok(_)));
17+
}
18+
19+
/// Listen should work in combination with an explicit bind.
20+
fn test_tcp_listen_with_bind(family: IpAddressFamily) {
21+
let bind_addr = IpSocketAddress::new(IpAddress::new_unspecified(family), 0);
22+
let sock = TcpSocket::create(family).unwrap();
23+
24+
sock.bind(bind_addr).unwrap();
25+
let local_addr = sock.get_local_address().unwrap();
26+
27+
assert!(matches!(sock.listen(), Ok(_)));
28+
assert_eq!(sock.get_local_address(), Ok(local_addr));
29+
}
30+
31+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
32+
async fn run() -> Result<(), ()> {
33+
test_tcp_listen_without_bind(IpAddressFamily::Ipv4);
34+
test_tcp_listen_with_bind(IpAddressFamily::Ipv4);
35+
36+
if supports_ipv6() {
37+
test_tcp_listen_without_bind(IpAddressFamily::Ipv6);
38+
test_tcp_listen_with_bind(IpAddressFamily::Ipv6);
39+
}
40+
41+
Ok(())
42+
}
43+
}
44+
45+
fn main() {}

crates/test-programs/src/bin/p3_sockets_udp_connect.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ test_programs::p3::export!(Component);
99
const SOME_PORT: u16 = 47; // If the tests pass, this will never actually be connected to.
1010

1111
fn test_udp_connect_disconnect_reconnect(family: IpAddressFamily) {
12-
let unspecified_addr = IpSocketAddress::new(IpAddress::new_unspecified(family), 0);
1312
let remote1 = IpSocketAddress::new(IpAddress::new_loopback(family), 4321);
1413
let remote2 = IpSocketAddress::new(IpAddress::new_loopback(family), 4320);
1514

1615
let client = UdpSocket::create(family).unwrap();
17-
client.bind(unspecified_addr).unwrap();
1816

1917
assert_eq!(client.disconnect(), Err(ErrorCode::InvalidState));
2018
assert_eq!(client.get_remote_address(), Err(ErrorCode::InvalidState));
@@ -43,7 +41,6 @@ fn test_udp_connect_unspec(family: IpAddressFamily) {
4341
let ip = IpAddress::new_unspecified(family);
4442
let addr = IpSocketAddress::new(ip, SOME_PORT);
4543
let sock = UdpSocket::create(family).unwrap();
46-
sock.bind_unspecified().unwrap();
4744

4845
assert!(matches!(
4946
sock.connect(addr),
@@ -55,7 +52,6 @@ fn test_udp_connect_unspec(family: IpAddressFamily) {
5552
fn test_udp_connect_port_0(family: IpAddressFamily) {
5653
let addr = IpSocketAddress::new(IpAddress::new_loopback(family), 0);
5754
let sock = UdpSocket::create(family).unwrap();
58-
sock.bind_unspecified().unwrap();
5955

6056
assert!(matches!(
6157
sock.connect(addr),
@@ -72,14 +68,37 @@ fn test_udp_connect_wrong_family(family: IpAddressFamily) {
7268
let remote_addr = IpSocketAddress::new(wrong_ip, SOME_PORT);
7369

7470
let sock = UdpSocket::create(family).unwrap();
75-
sock.bind_unspecified().unwrap();
7671

7772
assert!(matches!(
7873
sock.connect(remote_addr),
7974
Err(ErrorCode::InvalidArgument)
8075
));
8176
}
8277

78+
/// Connect should perform implicit bind.
79+
fn test_udp_connect_without_bind(family: IpAddressFamily) {
80+
let remote_addr = IpSocketAddress::new(IpAddress::new_loopback(family), SOME_PORT);
81+
82+
let sock = UdpSocket::create(family).unwrap();
83+
84+
assert!(matches!(sock.get_local_address(), Err(_)));
85+
assert!(matches!(sock.connect(remote_addr), Ok(_)));
86+
assert!(matches!(sock.get_local_address(), Ok(_)));
87+
}
88+
89+
/// Connect should work in combination with an explicit bind.
90+
fn test_udp_connect_with_bind(family: IpAddressFamily) {
91+
let remote_addr = IpSocketAddress::new(IpAddress::new_loopback(family), SOME_PORT);
92+
93+
let sock = UdpSocket::create(family).unwrap();
94+
95+
sock.bind_unspecified().unwrap();
96+
97+
assert!(matches!(sock.get_local_address(), Ok(_)));
98+
assert!(matches!(sock.connect(remote_addr), Ok(_)));
99+
assert!(matches!(sock.get_local_address(), Ok(_)));
100+
}
101+
83102
fn test_udp_connect_dual_stack() {
84103
// Set-up:
85104
let v4_server = UdpSocket::create(IpAddressFamily::Ipv4).unwrap();
@@ -123,6 +142,12 @@ impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
123142
test_udp_connect_wrong_family(IpAddressFamily::Ipv4);
124143
test_udp_connect_wrong_family(IpAddressFamily::Ipv6);
125144

145+
test_udp_connect_without_bind(IpAddressFamily::Ipv4);
146+
test_udp_connect_without_bind(IpAddressFamily::Ipv6);
147+
148+
test_udp_connect_with_bind(IpAddressFamily::Ipv4);
149+
test_udp_connect_with_bind(IpAddressFamily::Ipv6);
150+
126151
test_udp_connect_dual_stack();
127152
Ok(())
128153
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use test_programs::p3::wasi::sockets::types::{ErrorCode, IpAddressFamily, UdpSocket};
2+
3+
struct Component;
4+
5+
test_programs::p3::export!(Component);
6+
7+
// Receive requires the socket to be bound.
8+
async fn test_udp_receive_without_bind_or_connect(family: IpAddressFamily) {
9+
let sock = UdpSocket::create(family).unwrap();
10+
11+
assert!(matches!(sock.get_local_address(), Err(_)));
12+
13+
assert!(matches!(sock.receive().await, Err(ErrorCode::InvalidState)));
14+
15+
assert!(matches!(sock.get_local_address(), Err(_)));
16+
assert!(matches!(sock.get_remote_address(), Err(_)));
17+
}
18+
19+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
20+
async fn run() -> Result<(), ()> {
21+
test_udp_receive_without_bind_or_connect(IpAddressFamily::Ipv4).await;
22+
test_udp_receive_without_bind_or_connect(IpAddressFamily::Ipv6).await;
23+
24+
Ok(())
25+
}
26+
}
27+
28+
fn main() {}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use test_programs::p3::wasi::sockets::types::{
2+
IpAddress, IpAddressFamily, IpSocketAddress, UdpSocket,
3+
};
4+
5+
struct Component;
6+
7+
test_programs::p3::export!(Component);
8+
9+
// Send without prior `bind` or `connect` performs an implicit bind.
10+
async fn test_udp_send_without_bind_or_connect(family: IpAddressFamily) {
11+
let message = b"Hello, world!";
12+
let remote_addr = IpSocketAddress::new(IpAddress::new_loopback(family), 42);
13+
14+
let sock = UdpSocket::create(family).unwrap();
15+
16+
assert!(matches!(sock.get_local_address(), Err(_)));
17+
18+
assert!(matches!(
19+
sock.send(message.to_vec(), Some(remote_addr)).await,
20+
Ok(_)
21+
));
22+
23+
assert!(matches!(sock.get_local_address(), Ok(_)));
24+
assert!(matches!(sock.get_remote_address(), Err(_)));
25+
}
26+
27+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
28+
async fn run() -> Result<(), ()> {
29+
test_udp_send_without_bind_or_connect(IpAddressFamily::Ipv4).await;
30+
test_udp_send_without_bind_or_connect(IpAddressFamily::Ipv6).await;
31+
32+
Ok(())
33+
}
34+
}
35+
36+
fn main() {}

crates/test-programs/src/bin/p3_sockets_udp_states.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ test_programs::p3::export!(Component);
1010
async fn test_udp_unbound_state_invariants(family: IpAddressFamily) {
1111
let sock = UdpSocket::create(family).unwrap();
1212

13-
// Skipping: udp::start_bind
13+
// Skipping: udp::bind
1414

1515
assert_eq!(
1616
sock.send(b"test".into(), None).await,
@@ -38,7 +38,7 @@ fn test_udp_bound_state_invariants(family: IpAddressFamily) {
3838
sock.bind(bind_address),
3939
Err(ErrorCode::InvalidState)
4040
));
41-
// Skipping: udp::stream
41+
// Skipping: udp::connect
4242

4343
assert!(matches!(sock.get_local_address(), Ok(_)));
4444
assert!(matches!(
@@ -59,14 +59,13 @@ fn test_udp_connected_state_invariants(family: IpAddressFamily) {
5959
let bind_address = IpSocketAddress::new(IpAddress::new_loopback(family), 0);
6060
let connect_address = IpSocketAddress::new(IpAddress::new_loopback(family), 54321);
6161
let sock = UdpSocket::create(family).unwrap();
62-
sock.bind(bind_address).unwrap();
6362
sock.connect(connect_address).unwrap();
6463

6564
assert!(matches!(
6665
sock.bind(bind_address),
6766
Err(ErrorCode::InvalidState)
6867
));
69-
// Skipping: udp::stream
68+
// Skipping: udp::connect
7069

7170
assert!(matches!(sock.get_local_address(), Ok(_)));
7271
assert!(matches!(sock.get_remote_address(), Ok(_)));

crates/wasi/src/p2/host/tcp.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ impl crate::p2::host::tcp::tcp::HostTcpSocket for WasiSocketsCtxView<'_> {
8383
fn start_listen(&mut self, this: Resource<TcpSocket>) -> SocketResult<()> {
8484
let socket = self.table.get_mut(&this)?;
8585

86-
socket.start_listen()?;
86+
socket.start_listen_p2()?;
8787
Ok(())
8888
}
8989

9090
fn finish_listen(&mut self, this: Resource<TcpSocket>) -> SocketResult<()> {
9191
let socket = self.table.get_mut(&this)?;
92-
socket.finish_listen()?;
92+
socket.finish_listen_p2()?;
9393
Ok(())
9494
}
9595

crates/wasi/src/p2/host/udp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ impl udp::HostUdpSocket for WasiSocketsCtxView<'_> {
7979
return Err(ErrorCode::InvalidState.into());
8080
};
8181
check.check(connect_addr, SocketAddrUse::UdpConnect).await?;
82-
socket.connect(connect_addr)?;
82+
socket.connect_p2(connect_addr)?;
8383
}
8484

8585
let incoming_stream = IncomingDatagramStream {

crates/wasi/src/p3/sockets/host/types/tcp.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,7 @@ impl HostTcpSocketWithStore for WasiSockets {
267267
) -> SocketResult<StreamReader<Resource<TcpSocket>>> {
268268
let getter = store.getter();
269269
let socket = get_socket_mut(store.get().table, &socket)?;
270-
socket.start_listen()?;
271-
socket.finish_listen()?;
270+
socket.listen_p3()?;
272271
let listener = socket.tcp_listener_arc().unwrap().clone();
273272
let family = socket.address_family();
274273
let options = socket.non_inherited_options().clone();

0 commit comments

Comments
 (0)