Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backport wasi-sockets changes to Wasmtime 14 #7216

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions crates/test-programs/tests/wasi-sockets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,33 @@ async fn run(name: &str) -> anyhow::Result<()> {
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn tcp_v4() {
run("tcp_v4").await.unwrap();
async fn tcp_sample_application() {
run("tcp_sample_application").await.unwrap();
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn tcp_v6() {
run("tcp_v6").await.unwrap();
async fn tcp_bind() {
run("tcp_bind").await.unwrap();
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn tcp_connect() {
run("tcp_connect").await.unwrap();
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn tcp_states() {
run("tcp_states").await.unwrap();
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn tcp_sockopts() {
run("tcp_sockopts").await.unwrap();
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn udp_sample_application() {
run("udp_sample_application").await.unwrap();
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn main() {
assert!(addresses.resolve_next_address().is_ok());

let result = ip_name_lookup::resolve_addresses(&network, "a.b<&>", None, false);
assert!(matches!(result, Err(network::ErrorCode::InvalidName)));
assert!(matches!(result, Err(network::ErrorCode::InvalidArgument)));

// Try resolving a valid address and ensure that it eventually terminates.
// To help prevent this test from being flaky this additionally times out
Expand Down
151 changes: 151 additions & 0 deletions crates/test-programs/wasi-sockets-tests/src/bin/tcp_bind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use wasi::sockets::network::{ErrorCode, IpAddress, IpAddressFamily, IpSocketAddress, Network};
use wasi::sockets::tcp::TcpSocket;
use wasi_sockets_tests::*;

/// Bind a socket and let the system determine a port.
fn test_tcp_bind_ephemeral_port(net: &Network, ip: IpAddress) {
let bind_addr = IpSocketAddress::new(ip, 0);

let sock = TcpSocket::new(ip.family()).unwrap();
sock.blocking_bind(net, bind_addr).unwrap();

let bound_addr = sock.local_address().unwrap();

assert_eq!(bind_addr.ip(), bound_addr.ip());
assert_ne!(bind_addr.port(), bound_addr.port());
}

/// Bind a socket on a specified port.
fn test_tcp_bind_specific_port(net: &Network, ip: IpAddress) {
const PORT: u16 = 54321;

let bind_addr = IpSocketAddress::new(ip, PORT);

let sock = TcpSocket::new(ip.family()).unwrap();
sock.blocking_bind(net, bind_addr).unwrap();

let bound_addr = sock.local_address().unwrap();

assert_eq!(bind_addr.ip(), bound_addr.ip());
assert_eq!(bind_addr.port(), bound_addr.port());
}

/// Two sockets may not be actively bound to the same address at the same time.
fn test_tcp_bind_addrinuse(net: &Network, ip: IpAddress) {
let bind_addr = IpSocketAddress::new(ip, 0);

let sock1 = TcpSocket::new(ip.family()).unwrap();
sock1.blocking_bind(net, bind_addr).unwrap();
sock1.blocking_listen().unwrap();

let bound_addr = sock1.local_address().unwrap();

let sock2 = TcpSocket::new(ip.family()).unwrap();
assert_eq!(
sock2.blocking_bind(net, bound_addr),
Err(ErrorCode::AddressInUse)
);
}

// Try binding to an address that is not configured on the system.
fn test_tcp_bind_addrnotavail(net: &Network, ip: IpAddress) {
let bind_addr = IpSocketAddress::new(ip, 0);

let sock = TcpSocket::new(ip.family()).unwrap();

assert_eq!(
sock.blocking_bind(net, bind_addr),
Err(ErrorCode::AddressNotBindable)
);
}

/// Bind should validate the address family.
fn test_tcp_bind_wrong_family(net: &Network, family: IpAddressFamily) {
let wrong_ip = match family {
IpAddressFamily::Ipv4 => IpAddress::IPV6_LOOPBACK,
IpAddressFamily::Ipv6 => IpAddress::IPV4_LOOPBACK,
};

let sock = TcpSocket::new(family).unwrap();
let result = sock.blocking_bind(net, IpSocketAddress::new(wrong_ip, 0));

assert!(matches!(result, Err(ErrorCode::InvalidArgument)));
}

/// Bind only works on unicast addresses.
fn test_tcp_bind_non_unicast(net: &Network) {
let ipv4_broadcast = IpSocketAddress::new(IpAddress::IPV4_BROADCAST, 0);
let ipv4_multicast = IpSocketAddress::new(IpAddress::Ipv4((224, 254, 0, 0)), 0);
let ipv6_multicast = IpSocketAddress::new(IpAddress::Ipv6((0xff00, 0, 0, 0, 0, 0, 0, 0)), 0);

let sock_v4 = TcpSocket::new(IpAddressFamily::Ipv4).unwrap();
let sock_v6 = TcpSocket::new(IpAddressFamily::Ipv6).unwrap();

assert!(matches!(
sock_v4.blocking_bind(net, ipv4_broadcast),
Err(ErrorCode::InvalidArgument)
));
assert!(matches!(
sock_v4.blocking_bind(net, ipv4_multicast),
Err(ErrorCode::InvalidArgument)
));
assert!(matches!(
sock_v6.blocking_bind(net, ipv6_multicast),
Err(ErrorCode::InvalidArgument)
));
}

fn test_tcp_bind_dual_stack(net: &Network) {
let sock = TcpSocket::new(IpAddressFamily::Ipv6).unwrap();
let addr = IpSocketAddress::new(IpAddress::IPV4_MAPPED_LOOPBACK, 0);

// Even on platforms that don't support dualstack sockets,
// setting ipv6_only to true (disabling dualstack mode) should work.
sock.set_ipv6_only(true).unwrap();

// Binding an IPv4-mapped-IPv6 address on a ipv6-only socket should fail:
assert!(matches!(
sock.blocking_bind(net, addr),
Err(ErrorCode::InvalidArgument)
));

sock.set_ipv6_only(false).unwrap();

sock.blocking_bind(net, addr).unwrap();

let bound_addr = sock.local_address().unwrap();

assert_eq!(bound_addr.family(), IpAddressFamily::Ipv6);
}

fn main() {
const RESERVED_IPV4_ADDRESS: IpAddress = IpAddress::Ipv4((192, 0, 2, 0)); // Reserved for documentation and examples.
const RESERVED_IPV6_ADDRESS: IpAddress = IpAddress::Ipv6((0x2001, 0x0db8, 0, 0, 0, 0, 0, 0)); // Reserved for documentation and examples.

let net = Network::default();

test_tcp_bind_ephemeral_port(&net, IpAddress::IPV4_LOOPBACK);
test_tcp_bind_ephemeral_port(&net, IpAddress::IPV6_LOOPBACK);
test_tcp_bind_ephemeral_port(&net, IpAddress::IPV4_UNSPECIFIED);
test_tcp_bind_ephemeral_port(&net, IpAddress::IPV6_UNSPECIFIED);

test_tcp_bind_specific_port(&net, IpAddress::IPV4_LOOPBACK);
test_tcp_bind_specific_port(&net, IpAddress::IPV6_LOOPBACK);
test_tcp_bind_specific_port(&net, IpAddress::IPV4_UNSPECIFIED);
test_tcp_bind_specific_port(&net, IpAddress::IPV6_UNSPECIFIED);

test_tcp_bind_addrinuse(&net, IpAddress::IPV4_LOOPBACK);
test_tcp_bind_addrinuse(&net, IpAddress::IPV6_LOOPBACK);
test_tcp_bind_addrinuse(&net, IpAddress::IPV4_UNSPECIFIED);
test_tcp_bind_addrinuse(&net, IpAddress::IPV6_UNSPECIFIED);

test_tcp_bind_addrnotavail(&net, RESERVED_IPV4_ADDRESS);
test_tcp_bind_addrnotavail(&net, RESERVED_IPV6_ADDRESS);

test_tcp_bind_wrong_family(&net, IpAddressFamily::Ipv4);
test_tcp_bind_wrong_family(&net, IpAddressFamily::Ipv6);

test_tcp_bind_non_unicast(&net);

test_tcp_bind_dual_stack(&net);
}
119 changes: 119 additions & 0 deletions crates/test-programs/wasi-sockets-tests/src/bin/tcp_connect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use wasi::sockets::network::{ErrorCode, IpAddress, IpAddressFamily, IpSocketAddress, Network};
use wasi::sockets::tcp::TcpSocket;
use wasi_sockets_tests::*;

const SOME_PORT: u16 = 47; // If the tests pass, this will never actually be connected to.

/// `0.0.0.0` / `::` is not a valid remote address in WASI.
fn test_tcp_connect_unspec(net: &Network, family: IpAddressFamily) {
let addr = IpSocketAddress::new(IpAddress::new_unspecified(family), SOME_PORT);
let sock = TcpSocket::new(family).unwrap();

assert!(matches!(
sock.blocking_connect(net, addr),
Err(ErrorCode::InvalidArgument)
));
}

/// 0 is not a valid remote port.
fn test_tcp_connect_port_0(net: &Network, family: IpAddressFamily) {
let addr = IpSocketAddress::new(IpAddress::new_loopback(family), 0);
let sock = TcpSocket::new(family).unwrap();

assert!(matches!(
sock.blocking_connect(net, addr),
Err(ErrorCode::InvalidArgument)
));
}

/// Bind should validate the address family.
fn test_tcp_connect_wrong_family(net: &Network, family: IpAddressFamily) {
let wrong_ip = match family {
IpAddressFamily::Ipv4 => IpAddress::IPV6_LOOPBACK,
IpAddressFamily::Ipv6 => IpAddress::IPV4_LOOPBACK,
};
let remote_addr = IpSocketAddress::new(wrong_ip, SOME_PORT);

let sock = TcpSocket::new(family).unwrap();

assert!(matches!(
sock.blocking_connect(net, remote_addr),
Err(ErrorCode::InvalidArgument)
));
}

/// Can only connect to unicast addresses.
fn test_tcp_connect_non_unicast(net: &Network) {
let ipv4_broadcast = IpSocketAddress::new(IpAddress::IPV4_BROADCAST, SOME_PORT);
let ipv4_multicast = IpSocketAddress::new(IpAddress::Ipv4((224, 254, 0, 0)), SOME_PORT);
let ipv6_multicast =
IpSocketAddress::new(IpAddress::Ipv6((0xff00, 0, 0, 0, 0, 0, 0, 0)), SOME_PORT);

let sock_v4 = TcpSocket::new(IpAddressFamily::Ipv4).unwrap();
let sock_v6 = TcpSocket::new(IpAddressFamily::Ipv6).unwrap();

assert!(matches!(
sock_v4.blocking_connect(net, ipv4_broadcast),
Err(ErrorCode::InvalidArgument)
));
assert!(matches!(
sock_v4.blocking_connect(net, ipv4_multicast),
Err(ErrorCode::InvalidArgument)
));
assert!(matches!(
sock_v6.blocking_connect(net, ipv6_multicast),
Err(ErrorCode::InvalidArgument)
));
}

fn test_tcp_connect_dual_stack(net: &Network) {
// Set-up:
let v4_listener = TcpSocket::new(IpAddressFamily::Ipv4).unwrap();
v4_listener
.blocking_bind(&net, IpSocketAddress::new(IpAddress::IPV4_LOOPBACK, 0))
.unwrap();
v4_listener.blocking_listen().unwrap();

let v4_listener_addr = v4_listener.local_address().unwrap();
let v6_listener_addr =
IpSocketAddress::new(IpAddress::IPV4_MAPPED_LOOPBACK, v4_listener_addr.port());

let v6_client = TcpSocket::new(IpAddressFamily::Ipv6).unwrap();

// Tests:

// Even on platforms that don't support dualstack sockets,
// setting ipv6_only to true (disabling dualstack mode) should work.
v6_client.set_ipv6_only(true).unwrap();

// Connecting to an IPv4-mapped-IPv6 address on an ipv6-only socket should fail:
assert!(matches!(
v6_client.blocking_connect(net, v6_listener_addr),
Err(ErrorCode::InvalidArgument)
));

v6_client.set_ipv6_only(false).unwrap();

v6_client.blocking_connect(net, v6_listener_addr).unwrap();

let connected_addr = v6_client.local_address().unwrap();

assert_eq!(connected_addr.family(), IpAddressFamily::Ipv6);
}

fn main() {
let net = Network::default();

test_tcp_connect_unspec(&net, IpAddressFamily::Ipv4);
test_tcp_connect_unspec(&net, IpAddressFamily::Ipv6);

test_tcp_connect_port_0(&net, IpAddressFamily::Ipv4);
test_tcp_connect_port_0(&net, IpAddressFamily::Ipv6);

test_tcp_connect_wrong_family(&net, IpAddressFamily::Ipv4);
test_tcp_connect_wrong_family(&net, IpAddressFamily::Ipv6);

test_tcp_connect_non_unicast(&net);

test_tcp_connect_dual_stack(&net);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use wasi::sockets::network::{
IpAddressFamily, IpSocketAddress, Ipv4SocketAddress, Ipv6SocketAddress, Network,
};
use wasi::sockets::tcp::TcpSocket;
use wasi_sockets_tests::*;

fn test_sample_application(family: IpAddressFamily, bind_address: IpSocketAddress) {
let first_message = b"Hello, world!";
let second_message = b"Greetings, planet!";

let net = Network::default();
let listener = TcpSocket::new(family).unwrap();

listener.blocking_bind(&net, bind_address).unwrap();
listener.set_listen_backlog_size(32).unwrap();
listener.blocking_listen().unwrap();

let addr = listener.local_address().unwrap();

{
let client = TcpSocket::new(family).unwrap();
let (_client_input, client_output) = client.blocking_connect(&net, addr).unwrap();

client_output.blocking_write_util(&[]).unwrap();
client_output.blocking_write_util(first_message).unwrap();
}

{
let (_accepted, input, _output) = listener.accept().unwrap();

let empty_data = input.read(0).unwrap();
assert!(empty_data.is_empty());

let data = input.blocking_read(first_message.len() as u64).unwrap();

// Check that we sent and recieved our message!
assert_eq!(data, first_message); // Not guaranteed to work but should work in practice.
}

// Another client
{
let client = TcpSocket::new(family).unwrap();
let (_client_input, client_output) = client.blocking_connect(&net, addr).unwrap();

client_output.blocking_write_util(second_message).unwrap();
}

{
let (_accepted, input, _output) = listener.accept().unwrap();
let data = input.blocking_read(second_message.len() as u64).unwrap();

// Check that we sent and recieved our message!
assert_eq!(data, second_message); // Not guaranteed to work but should work in practice.
}
}

fn main() {
test_sample_application(
IpAddressFamily::Ipv4,
IpSocketAddress::Ipv4(Ipv4SocketAddress {
port: 0, // use any free port
address: (127, 0, 0, 1), // localhost
}),
);
test_sample_application(
IpAddressFamily::Ipv6,
IpSocketAddress::Ipv6(Ipv6SocketAddress {
port: 0, // use any free port
address: (0, 0, 0, 0, 0, 0, 0, 1), // localhost
flow_info: 0,
scope_id: 0,
}),
);
}
Loading