Skip to content

Commit 9ee8fc2

Browse files
authored
Merge pull request #205 from saulecabrera/sockets-bind-tests
[sockets] Add tests for `bind`
2 parents 1edc511 + f9c9c6d commit 9ee8fc2

2 files changed

Lines changed: 271 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"proposals": [
3+
"sockets"
4+
],
5+
"operations": [
6+
{
7+
"type": "run"
8+
},
9+
{
10+
"type": "wait"
11+
}
12+
]
13+
}
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
wit_bindgen::generate!({
2+
inline: r"
3+
package wasi-testsuite:test;
4+
5+
world test {
6+
include wasi:sockets/imports@0.3.0-rc-2026-01-06;
7+
include wasi:cli/command@0.3.0-rc-2026-01-06;
8+
}
9+
",
10+
features:["clocks-timezone"],
11+
additional_derives: [PartialEq, Eq],
12+
generate_all
13+
});
14+
15+
use futures::join;
16+
use wasi::sockets::types::{
17+
ErrorCode, IpAddress, IpAddressFamily, IpSocketAddress, Ipv4SocketAddress, Ipv6SocketAddress,
18+
TcpSocket,
19+
};
20+
21+
struct Component;
22+
23+
export!(Component);
24+
25+
impl IpSocketAddress {
26+
fn ipv4_localhost(port: u16) -> IpSocketAddress {
27+
IpSocketAddress::Ipv4(Ipv4SocketAddress {
28+
port,
29+
address: (127, 0, 0, 1),
30+
})
31+
}
32+
33+
fn ipv6_localhost(port: u16) -> IpSocketAddress {
34+
IpSocketAddress::Ipv6(Ipv6SocketAddress {
35+
port,
36+
address: (0, 0, 0, 0, 0, 0, 0, 1),
37+
flow_info: 0,
38+
scope_id: 0,
39+
})
40+
}
41+
42+
fn ipv6_mapped_localhost(port: u16) -> IpSocketAddress {
43+
IpSocketAddress::Ipv6(Ipv6SocketAddress {
44+
port,
45+
address: (0, 0, 0, 0, 0, 0xFFFF, 0x7F00, 0x0001),
46+
flow_info: 0,
47+
scope_id: 0,
48+
})
49+
}
50+
51+
fn localhost(family: IpAddressFamily, port: u16) -> IpSocketAddress {
52+
match family {
53+
IpAddressFamily::Ipv4 => Self::ipv4_localhost(port),
54+
IpAddressFamily::Ipv6 => Self::ipv6_localhost(port),
55+
}
56+
}
57+
58+
fn new(addr: IpAddress, port: u16) -> IpSocketAddress {
59+
match addr {
60+
IpAddress::Ipv4(addr) => IpSocketAddress::Ipv4(Ipv4SocketAddress {
61+
port,
62+
address: addr,
63+
}),
64+
IpAddress::Ipv6(addr) => IpSocketAddress::Ipv6(Ipv6SocketAddress {
65+
port,
66+
address: addr,
67+
flow_info: 0,
68+
scope_id: 0,
69+
}),
70+
}
71+
}
72+
73+
fn ip_addr(&self) -> IpAddress {
74+
match self {
75+
IpSocketAddress::Ipv6(addr) => IpAddress::Ipv6(addr.address),
76+
IpSocketAddress::Ipv4(addr) => IpAddress::Ipv4(addr.address),
77+
}
78+
}
79+
80+
fn port(&self) -> u16 {
81+
match self {
82+
IpSocketAddress::Ipv6(addr) => addr.port,
83+
IpSocketAddress::Ipv4(addr) => addr.port,
84+
}
85+
}
86+
}
87+
88+
fn test_invalid_address_family(family: IpAddressFamily) {
89+
let sock = TcpSocket::create(family).unwrap();
90+
91+
let addr = match family {
92+
IpAddressFamily::Ipv4 => IpSocketAddress::localhost(IpAddressFamily::Ipv6, 0),
93+
IpAddressFamily::Ipv6 => IpSocketAddress::localhost(IpAddressFamily::Ipv4, 0),
94+
};
95+
96+
let result = sock.bind(addr);
97+
assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
98+
}
99+
100+
fn test_ephemeral_port_assignment(family: IpAddressFamily) {
101+
let sock = TcpSocket::create(family).unwrap();
102+
let addr = IpSocketAddress::localhost(family, 0);
103+
104+
sock.bind(addr).unwrap();
105+
let bound = sock.get_local_address().unwrap();
106+
107+
assert_eq!(addr.ip_addr(), bound.ip_addr());
108+
// Randomly assigned port.
109+
assert_ne!(addr.port(), bound.port());
110+
}
111+
112+
fn test_non_unicast(family: IpAddressFamily) {
113+
let mut non_unicast_addresses = Vec::new();
114+
115+
match family {
116+
IpAddressFamily::Ipv4 => {
117+
// Multicast
118+
for nibble in 224..=239 {
119+
non_unicast_addresses.push(IpAddress::Ipv4((nibble, 0, 0, 1)));
120+
}
121+
// Limited broadcast
122+
non_unicast_addresses.push(IpAddress::Ipv4((255, 255, 255, 255)));
123+
}
124+
IpAddressFamily::Ipv6 => {
125+
// Multicast
126+
for b in 0xff00..=0xffff {
127+
non_unicast_addresses.push(IpAddress::Ipv6((b, 0, 0, 0, 0, 0, 0, 1)));
128+
}
129+
}
130+
};
131+
132+
for addr in non_unicast_addresses {
133+
let sock = TcpSocket::create(family).unwrap();
134+
let socket_addr = IpSocketAddress::new(addr, 0);
135+
let result = sock.bind(socket_addr);
136+
137+
assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
138+
}
139+
}
140+
141+
fn test_reject_dual_stack() {
142+
let sock = TcpSocket::create(IpAddressFamily::Ipv6).unwrap();
143+
let addr = IpSocketAddress::ipv6_mapped_localhost(0);
144+
let result = sock.bind(addr);
145+
146+
assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
147+
}
148+
149+
fn test_bind_addrinuse(family: IpAddressFamily) {
150+
let addr = IpSocketAddress::localhost(family, 0);
151+
152+
let sock1 = TcpSocket::create(family).unwrap();
153+
sock1.bind(addr).unwrap();
154+
sock1.listen().unwrap();
155+
156+
let bound_addr = sock1.get_local_address().unwrap();
157+
let sock2 = TcpSocket::create(family).unwrap();
158+
let result = sock2.bind(bound_addr);
159+
assert_eq!(result, Err(ErrorCode::AddressInUse));
160+
}
161+
162+
fn test_not_bindable(family: IpAddressFamily) {
163+
let mut non_bindable_addresses = Vec::new();
164+
165+
match family {
166+
// https://datatracker.ietf.org/doc/html/rfc5737#section-3
167+
IpAddressFamily::Ipv4 => {
168+
non_bindable_addresses.push(IpAddress::Ipv4((192, 0, 2, 1)));
169+
non_bindable_addresses.push(IpAddress::Ipv4((198, 51, 100, 1)));
170+
non_bindable_addresses.push(IpAddress::Ipv4((203, 0, 113, 1)));
171+
}
172+
IpAddressFamily::Ipv6 => {
173+
non_bindable_addresses.push(IpAddress::Ipv6((0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)));
174+
}
175+
};
176+
177+
for addr in non_bindable_addresses {
178+
let sock = TcpSocket::create(family).unwrap();
179+
let socket_addr = IpSocketAddress::new(addr, 0);
180+
let result = sock.bind(socket_addr);
181+
182+
assert_eq!(result, Err(ErrorCode::AddressNotBindable));
183+
}
184+
}
185+
186+
fn test_already_bound(family: IpAddressFamily) {
187+
let addr = IpSocketAddress::localhost(family, 0);
188+
let sock = TcpSocket::create(family).unwrap();
189+
let result = sock.bind(addr);
190+
assert!(result.is_ok());
191+
let result = sock.bind(addr);
192+
assert_eq!(result, Err(ErrorCode::InvalidState));
193+
}
194+
195+
async fn test_reuseaddr(family: IpAddressFamily) {
196+
let client = TcpSocket::create(family).unwrap();
197+
let local_addr = {
198+
let server = TcpSocket::create(family).unwrap();
199+
let addr = IpSocketAddress::localhost(family, 0);
200+
server.bind(addr).unwrap();
201+
let local_addr = server.get_local_address().unwrap();
202+
let mut accept = server.listen().unwrap();
203+
join!(
204+
// Change the state to connected.
205+
async {
206+
client.connect(local_addr).await.unwrap();
207+
},
208+
async {
209+
let sock = accept.next().await.unwrap();
210+
let (mut send_tx, send_rx) = wit_stream::new();
211+
join!(
212+
async {
213+
sock.send(send_rx).await.unwrap();
214+
},
215+
async {
216+
let remaining = send_tx.write_all(vec![0; 1]).await;
217+
assert!(remaining.is_empty());
218+
drop(send_tx);
219+
}
220+
);
221+
}
222+
);
223+
local_addr
224+
};
225+
226+
// Immediately try to connect to the same after the connection is
227+
// dropped. According to the spec, `SO_REUSEADDR` should be set
228+
// by default, so the next connection should not be affected by
229+
// the `TIME_WAIT` state.
230+
let next = TcpSocket::create(family).unwrap();
231+
next.bind(local_addr).unwrap();
232+
next.listen().unwrap();
233+
}
234+
235+
impl exports::wasi::cli::run::Guest for Component {
236+
async fn run() -> Result<(), ()> {
237+
test_invalid_address_family(IpAddressFamily::Ipv4);
238+
test_invalid_address_family(IpAddressFamily::Ipv6);
239+
test_ephemeral_port_assignment(IpAddressFamily::Ipv4);
240+
test_ephemeral_port_assignment(IpAddressFamily::Ipv6);
241+
test_non_unicast(IpAddressFamily::Ipv4);
242+
test_non_unicast(IpAddressFamily::Ipv6);
243+
test_reject_dual_stack();
244+
test_bind_addrinuse(IpAddressFamily::Ipv4);
245+
test_bind_addrinuse(IpAddressFamily::Ipv6);
246+
test_not_bindable(IpAddressFamily::Ipv4);
247+
test_not_bindable(IpAddressFamily::Ipv6);
248+
test_already_bound(IpAddressFamily::Ipv4);
249+
test_already_bound(IpAddressFamily::Ipv6);
250+
test_reuseaddr(IpAddressFamily::Ipv4).await;
251+
test_reuseaddr(IpAddressFamily::Ipv6).await;
252+
Ok(())
253+
}
254+
}
255+
256+
fn main() {
257+
unreachable!()
258+
}

0 commit comments

Comments
 (0)