Skip to content

Commit 22c03af

Browse files
committed
feat(wasip3): implement wasi:sockets
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
1 parent 079211b commit 22c03af

32 files changed

+4034
-26
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use futures::try_join;
2+
use test_programs::p3::wasi::sockets::ip_name_lookup::{ErrorCode, resolve_addresses};
3+
use test_programs::p3::wasi::sockets::types::IpAddress;
4+
5+
struct Component;
6+
7+
test_programs::p3::export!(Component);
8+
9+
async fn resolve_one(name: &str) -> Result<IpAddress, ErrorCode> {
10+
Ok(resolve_addresses(name.into())
11+
.await?
12+
.first()
13+
.unwrap()
14+
.to_owned())
15+
}
16+
17+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
18+
async fn run() -> Result<(), ()> {
19+
// Valid domains
20+
try_join!(
21+
resolve_addresses("localhost".into()),
22+
resolve_addresses("example.com".into())
23+
)
24+
.unwrap();
25+
26+
// NB: this is an actual real resolution, so it might time out, might cause
27+
// issues, etc. This result is ignored to prevent flaky failures in CI.
28+
let _ = resolve_addresses("münchen.de".into()).await;
29+
30+
// Valid IP addresses
31+
assert_eq!(
32+
resolve_one("0.0.0.0").await.unwrap(),
33+
IpAddress::IPV4_UNSPECIFIED
34+
);
35+
assert_eq!(
36+
resolve_one("127.0.0.1").await.unwrap(),
37+
IpAddress::IPV4_LOOPBACK
38+
);
39+
assert_eq!(
40+
resolve_one("192.0.2.0").await.unwrap(),
41+
IpAddress::Ipv4((192, 0, 2, 0))
42+
);
43+
assert_eq!(
44+
resolve_one("::").await.unwrap(),
45+
IpAddress::IPV6_UNSPECIFIED
46+
);
47+
assert_eq!(resolve_one("::1").await.unwrap(), IpAddress::IPV6_LOOPBACK);
48+
assert_eq!(
49+
resolve_one("[::]").await.unwrap(),
50+
IpAddress::IPV6_UNSPECIFIED
51+
);
52+
assert_eq!(
53+
resolve_one("2001:0db8:0:0:0:0:0:0").await.unwrap(),
54+
IpAddress::Ipv6((0x2001, 0x0db8, 0, 0, 0, 0, 0, 0))
55+
);
56+
assert_eq!(
57+
resolve_one("dead:beef::").await.unwrap(),
58+
IpAddress::Ipv6((0xdead, 0xbeef, 0, 0, 0, 0, 0, 0))
59+
);
60+
assert_eq!(
61+
resolve_one("dead:beef::0").await.unwrap(),
62+
IpAddress::Ipv6((0xdead, 0xbeef, 0, 0, 0, 0, 0, 0))
63+
);
64+
assert_eq!(
65+
resolve_one("DEAD:BEEF::0").await.unwrap(),
66+
IpAddress::Ipv6((0xdead, 0xbeef, 0, 0, 0, 0, 0, 0))
67+
);
68+
69+
// Invalid inputs
70+
assert_eq!(
71+
resolve_addresses("".into()).await.unwrap_err(),
72+
ErrorCode::InvalidArgument
73+
);
74+
assert_eq!(
75+
resolve_addresses(" ".into()).await.unwrap_err(),
76+
ErrorCode::InvalidArgument
77+
);
78+
assert_eq!(
79+
resolve_addresses("a.b<&>".into()).await.unwrap_err(),
80+
ErrorCode::InvalidArgument
81+
);
82+
assert_eq!(
83+
resolve_addresses("127.0.0.1:80".into()).await.unwrap_err(),
84+
ErrorCode::InvalidArgument
85+
);
86+
assert_eq!(
87+
resolve_addresses("[::]:80".into()).await.unwrap_err(),
88+
ErrorCode::InvalidArgument
89+
);
90+
assert_eq!(
91+
resolve_addresses("http://example.com/".into())
92+
.await
93+
.unwrap_err(),
94+
ErrorCode::InvalidArgument
95+
);
96+
Ok(())
97+
}
98+
}
99+
100+
fn main() {}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
use futures::join;
2+
use test_programs::p3::sockets::attempt_random_port;
3+
use test_programs::p3::wasi::sockets::types::{
4+
ErrorCode, IpAddress, IpAddressFamily, IpSocketAddress, TcpSocket,
5+
};
6+
use test_programs::p3::wit_stream;
7+
use wit_bindgen::yield_blocking;
8+
9+
struct Component;
10+
11+
test_programs::p3::export!(Component);
12+
13+
/// Bind a socket and let the system determine a port.
14+
fn test_tcp_bind_ephemeral_port(ip: IpAddress) {
15+
let bind_addr = IpSocketAddress::new(ip, 0);
16+
17+
let sock = TcpSocket::new(ip.family());
18+
sock.bind(bind_addr).unwrap();
19+
20+
let bound_addr = sock.local_address().unwrap();
21+
22+
assert_eq!(bind_addr.ip(), bound_addr.ip());
23+
assert_ne!(bind_addr.port(), bound_addr.port());
24+
}
25+
26+
/// Bind a socket on a specified port.
27+
fn test_tcp_bind_specific_port(ip: IpAddress) {
28+
let sock = TcpSocket::new(ip.family());
29+
30+
let bind_addr = attempt_random_port(ip, |bind_addr| sock.bind(bind_addr)).unwrap();
31+
32+
let bound_addr = sock.local_address().unwrap();
33+
34+
assert_eq!(bind_addr.ip(), bound_addr.ip());
35+
assert_eq!(bind_addr.port(), bound_addr.port());
36+
}
37+
38+
/// Two sockets may not be actively bound to the same address at the same time.
39+
fn test_tcp_bind_addrinuse(ip: IpAddress) {
40+
let bind_addr = IpSocketAddress::new(ip, 0);
41+
42+
let sock1 = TcpSocket::new(ip.family());
43+
sock1.bind(bind_addr).unwrap();
44+
sock1.listen().unwrap();
45+
46+
let bound_addr = sock1.local_address().unwrap();
47+
48+
let sock2 = TcpSocket::new(ip.family());
49+
assert_eq!(sock2.bind(bound_addr), Err(ErrorCode::AddressInUse));
50+
}
51+
52+
// The WASI runtime should set SO_REUSEADDR for us
53+
async fn test_tcp_bind_reuseaddr(ip: IpAddress) {
54+
let client = TcpSocket::new(ip.family());
55+
56+
let bind_addr = {
57+
let listener1 = TcpSocket::new(ip.family());
58+
59+
let bind_addr = attempt_random_port(ip, |bind_addr| listener1.bind(bind_addr)).unwrap();
60+
61+
let mut accept = listener1.listen().unwrap();
62+
63+
let connect_addr =
64+
IpSocketAddress::new(IpAddress::new_loopback(ip.family()), bind_addr.port());
65+
join!(
66+
async {
67+
client.connect(connect_addr).await.unwrap();
68+
},
69+
async {
70+
let sock = accept.next().await.unwrap();
71+
let (mut data_tx, data_rx) = wit_stream::new();
72+
join!(
73+
async {
74+
sock.send(data_rx).await.unwrap();
75+
},
76+
async {
77+
let remaining = data_tx.write_all(vec![0; 10]).await;
78+
assert!(remaining.is_empty());
79+
drop(data_tx);
80+
}
81+
);
82+
},
83+
);
84+
85+
bind_addr
86+
};
87+
88+
// The socket and `listen` stream have been dropped,
89+
// yield to the host to ensure that the spawned task is aborted
90+
yield_blocking();
91+
92+
{
93+
let listener2 = TcpSocket::new(ip.family());
94+
95+
// If SO_REUSEADDR was configured correctly, the following lines shouldn't be
96+
// affected by the TIME_WAIT state of the just closed `listener1` socket:
97+
listener2.bind(bind_addr).unwrap();
98+
listener2.listen().unwrap();
99+
}
100+
}
101+
102+
// Try binding to an address that is not configured on the system.
103+
fn test_tcp_bind_addrnotavail(ip: IpAddress) {
104+
let bind_addr = IpSocketAddress::new(ip, 0);
105+
106+
let sock = TcpSocket::new(ip.family());
107+
108+
assert_eq!(sock.bind(bind_addr), Err(ErrorCode::AddressNotBindable));
109+
}
110+
111+
/// Bind should validate the address family.
112+
fn test_tcp_bind_wrong_family(family: IpAddressFamily) {
113+
let wrong_ip = match family {
114+
IpAddressFamily::Ipv4 => IpAddress::IPV6_LOOPBACK,
115+
IpAddressFamily::Ipv6 => IpAddress::IPV4_LOOPBACK,
116+
};
117+
118+
let sock = TcpSocket::new(family);
119+
let result = sock.bind(IpSocketAddress::new(wrong_ip, 0));
120+
121+
assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
122+
}
123+
124+
/// Bind only works on unicast addresses.
125+
fn test_tcp_bind_non_unicast() {
126+
let ipv4_broadcast = IpSocketAddress::new(IpAddress::IPV4_BROADCAST, 0);
127+
let ipv4_multicast = IpSocketAddress::new(IpAddress::Ipv4((224, 254, 0, 0)), 0);
128+
let ipv6_multicast = IpSocketAddress::new(IpAddress::Ipv6((0xff00, 0, 0, 0, 0, 0, 0, 0)), 0);
129+
130+
let sock_v4 = TcpSocket::new(IpAddressFamily::Ipv4);
131+
let sock_v6 = TcpSocket::new(IpAddressFamily::Ipv6);
132+
133+
assert!(matches!(
134+
sock_v4.bind(ipv4_broadcast),
135+
Err(ErrorCode::InvalidArgument)
136+
));
137+
assert!(matches!(
138+
sock_v4.bind(ipv4_multicast),
139+
Err(ErrorCode::InvalidArgument)
140+
));
141+
assert!(matches!(
142+
sock_v6.bind(ipv6_multicast),
143+
Err(ErrorCode::InvalidArgument)
144+
));
145+
}
146+
147+
fn test_tcp_bind_dual_stack() {
148+
let sock = TcpSocket::new(IpAddressFamily::Ipv6);
149+
let addr = IpSocketAddress::new(IpAddress::IPV4_MAPPED_LOOPBACK, 0);
150+
151+
// Binding an IPv4-mapped-IPv6 address on a ipv6-only socket should fail:
152+
assert!(matches!(sock.bind(addr), Err(ErrorCode::InvalidArgument)));
153+
}
154+
155+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
156+
async fn run() -> Result<(), ()> {
157+
const RESERVED_IPV4_ADDRESS: IpAddress = IpAddress::Ipv4((192, 0, 2, 0)); // Reserved for documentation and examples.
158+
const RESERVED_IPV6_ADDRESS: IpAddress =
159+
IpAddress::Ipv6((0x2001, 0x0db8, 0, 0, 0, 0, 0, 0)); // Reserved for documentation and examples.
160+
161+
test_tcp_bind_ephemeral_port(IpAddress::IPV4_LOOPBACK);
162+
test_tcp_bind_ephemeral_port(IpAddress::IPV6_LOOPBACK);
163+
test_tcp_bind_ephemeral_port(IpAddress::IPV4_UNSPECIFIED);
164+
test_tcp_bind_ephemeral_port(IpAddress::IPV6_UNSPECIFIED);
165+
166+
test_tcp_bind_specific_port(IpAddress::IPV4_LOOPBACK);
167+
test_tcp_bind_specific_port(IpAddress::IPV6_LOOPBACK);
168+
test_tcp_bind_specific_port(IpAddress::IPV4_UNSPECIFIED);
169+
test_tcp_bind_specific_port(IpAddress::IPV6_UNSPECIFIED);
170+
171+
test_tcp_bind_reuseaddr(IpAddress::IPV4_LOOPBACK).await;
172+
test_tcp_bind_reuseaddr(IpAddress::IPV6_LOOPBACK).await;
173+
174+
test_tcp_bind_addrinuse(IpAddress::IPV4_LOOPBACK);
175+
test_tcp_bind_addrinuse(IpAddress::IPV6_LOOPBACK);
176+
test_tcp_bind_addrinuse(IpAddress::IPV4_UNSPECIFIED);
177+
test_tcp_bind_addrinuse(IpAddress::IPV6_UNSPECIFIED);
178+
179+
test_tcp_bind_addrnotavail(RESERVED_IPV4_ADDRESS);
180+
test_tcp_bind_addrnotavail(RESERVED_IPV6_ADDRESS);
181+
182+
test_tcp_bind_wrong_family(IpAddressFamily::Ipv4);
183+
test_tcp_bind_wrong_family(IpAddressFamily::Ipv6);
184+
185+
test_tcp_bind_non_unicast();
186+
187+
test_tcp_bind_dual_stack();
188+
189+
Ok(())
190+
}
191+
}
192+
193+
fn main() {}

0 commit comments

Comments
 (0)