Skip to content

Commit

Permalink
Implement the wasi:sockets/ip-name-lookup interface (#7109)
Browse files Browse the repository at this point in the history
* Implement the `wasi:sockets/ip-name-lookup` interface

This commit is an initial implementation of the new `ip-name-lookup`
interface from the `wasi-sockets` proposal. The intention is to get a
sketch of what an implementation would look like for the preview2
release later this year. Notable features of this implementation are:

* Name lookups are disabled by default and must be explicitly enabled in
  `WasiCtx`. This seems like a reasonable default for now while the full
  set of configuration around this is settled.

* I've added new "typed" methods to `preview2::Table` to avoid the need
  for extra helpers when using resources.

* A new `-Sallow-ip-name-lookup` option is added to control this on the
  CLI.

* Implementation-wise this uses the blocking resolution in the Rust
  standard library, built on `getaddrinfo`. This doesn't invoke
  `getaddrinfo` "raw", however, so information such as error details can
  be lost in translation. This will probably need to change in the
  future.

Closes #7070

* Validate the input domain name.

* Use a crate-level helper for `spawn_blocking`

---------

Co-authored-by: Dave Bakker <github@davebakker.io>
  • Loading branch information
alexcrichton and badeend committed Oct 4, 2023
1 parent a5ccfe6 commit e21f45a
Show file tree
Hide file tree
Showing 18 changed files with 263 additions and 37 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ pretty_env_logger = "0.5.0"
syn = "2.0.25"
test-log = { version = "0.2", default-features = false, features = ["trace"] }
tracing-subscriber = { version = "0.3.1", default-features = false, features = ['fmt', 'env-filter'] }
url = "2.3.1"

[features]
default = [
Expand Down
2 changes: 2 additions & 0 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ wasmtime_option_group! {
/// Flag for WASI preview2 to inherit the host's network within the
/// guest so it has full access to all addresses/ports/etc.
pub inherit_network: Option<bool>,
/// Indicates whether `wasi:sockets/ip-name-lookup` is enabled or not.
pub allow_ip_name_lookup: Option<bool>,

}

Expand Down
6 changes: 6 additions & 0 deletions crates/test-programs/tests/wasi-sockets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ async fn run(name: &str) -> anyhow::Result<()> {
let wasi = WasiCtxBuilder::new()
.inherit_stdio()
.inherit_network(ambient_authority())
.allow_ip_name_lookup(true)
.arg(name)
.build();

Expand All @@ -74,3 +75,8 @@ async fn tcp_v4() {
async fn tcp_v6() {
run("tcp_v6").await.unwrap();
}

#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn ip_name_lookup() {
run("ip_name_lookup").await.unwrap();
}
36 changes: 36 additions & 0 deletions crates/test-programs/wasi-sockets-tests/src/bin/ip_name_lookup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use wasi_sockets_tests::wasi::clocks::*;
use wasi_sockets_tests::wasi::io::*;
use wasi_sockets_tests::wasi::sockets::*;

fn main() {
let network = instance_network::instance_network();

let addresses =
ip_name_lookup::resolve_addresses(&network, "example.com", None, false).unwrap();
let pollable = addresses.subscribe();
poll::poll_one(&pollable);
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)));

// Try resolving a valid address and ensure that it eventually terminates.
// To help prevent this test from being flaky this additionally times out
// the resolution and allows errors.
let addresses = ip_name_lookup::resolve_addresses(&network, "github.com", None, false).unwrap();
let lookup = addresses.subscribe();
let timeout = monotonic_clock::subscribe(1_000_000_000, false);
let ready = poll::poll_list(&[&lookup, &timeout]);
assert!(ready.len() > 0);
match ready[0] {
0 => loop {
match addresses.resolve_next_address() {
Ok(Some(_)) => {}
Ok(None) => break,
Err(_) => break,
}
},
1 => {}
_ => unreachable!(),
}
}
2 changes: 2 additions & 0 deletions crates/wasi-http/wit/test.wit
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ world test-command-with-sockets {
import wasi:sockets/tcp-create-socket
import wasi:sockets/network
import wasi:sockets/instance-network
import wasi:sockets/ip-name-lookup
import wasi:clocks/monotonic-clock
}
2 changes: 2 additions & 0 deletions crates/wasi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ wasi-tokio = { workspace = true, optional = true }
wiggle = { workspace = true, optional = true }
libc = { workspace = true }
once_cell = { workspace = true }
log = { workspace = true }
url = { workspace = true }

tokio = { workspace = true, optional = true, features = ["time", "sync", "io-std", "io-util", "rt", "rt-multi-thread", "net"] }
bytes = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions crates/wasi/src/preview2/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub fn add_to_linker<T: WasiView>(l: &mut wasmtime::component::Linker<T>) -> any
crate::preview2::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?;
crate::preview2::bindings::sockets::instance_network::add_to_linker(l, |t| t)?;
crate::preview2::bindings::sockets::network::add_to_linker(l, |t| t)?;
crate::preview2::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?;
Ok(())
}

Expand Down Expand Up @@ -116,6 +117,7 @@ pub mod sync {
crate::preview2::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?;
crate::preview2::bindings::sockets::instance_network::add_to_linker(l, |t| t)?;
crate::preview2::bindings::sockets::network::add_to_linker(l, |t| t)?;
crate::preview2::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?;
Ok(())
}
}
37 changes: 28 additions & 9 deletions crates/wasi/src/preview2/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub struct WasiCtxBuilder {
insecure_random_seed: u128,
wall_clock: Box<dyn HostWallClock + Send + Sync>,
monotonic_clock: Box<dyn HostMonotonicClock + Send + Sync>,
allow_ip_name_lookup: bool,
built: bool,
}

Expand Down Expand Up @@ -76,6 +77,7 @@ impl WasiCtxBuilder {
insecure_random_seed,
wall_clock: wall_clock(),
monotonic_clock: monotonic_clock(),
allow_ip_name_lookup: false,
built: false,
}
}
Expand Down Expand Up @@ -201,21 +203,27 @@ impl WasiCtxBuilder {
}

/// Add network addresses to the pool.
pub fn insert_addr<A: cap_std::net::ToSocketAddrs>(&mut self, addrs: A) -> std::io::Result<()> {
self.pool.insert(addrs, ambient_authority())
pub fn insert_addr<A: cap_std::net::ToSocketAddrs>(
&mut self,
addrs: A,
) -> std::io::Result<&mut Self> {
self.pool.insert(addrs, ambient_authority())?;
Ok(self)
}

/// Add a specific [`cap_std::net::SocketAddr`] to the pool.
pub fn insert_socket_addr(&mut self, addr: cap_std::net::SocketAddr) {
pub fn insert_socket_addr(&mut self, addr: cap_std::net::SocketAddr) -> &mut Self {
self.pool.insert_socket_addr(addr, ambient_authority());
self
}

/// Add a range of network addresses, accepting any port, to the pool.
///
/// Unlike `insert_ip_net`, this function grants access to any requested port.
pub fn insert_ip_net_port_any(&mut self, ip_net: ipnet::IpNet) {
pub fn insert_ip_net_port_any(&mut self, ip_net: ipnet::IpNet) -> &mut Self {
self.pool
.insert_ip_net_port_any(ip_net, ambient_authority())
.insert_ip_net_port_any(ip_net, ambient_authority());
self
}

/// Add a range of network addresses, accepting a range of ports, to
Expand All @@ -228,14 +236,22 @@ impl WasiCtxBuilder {
ip_net: ipnet::IpNet,
ports_start: u16,
ports_end: Option<u16>,
) {
) -> &mut Self {
self.pool
.insert_ip_net_port_range(ip_net, ports_start, ports_end, ambient_authority())
.insert_ip_net_port_range(ip_net, ports_start, ports_end, ambient_authority());
self
}

/// Add a range of network addresses with a specific port to the pool.
pub fn insert_ip_net(&mut self, ip_net: ipnet::IpNet, port: u16) {
self.pool.insert_ip_net(ip_net, port, ambient_authority())
pub fn insert_ip_net(&mut self, ip_net: ipnet::IpNet, port: u16) -> &mut Self {
self.pool.insert_ip_net(ip_net, port, ambient_authority());
self
}

/// Allow usage of `wasi:sockets/ip-name-lookup`
pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self {
self.allow_ip_name_lookup = enable;
self
}

/// Uses the configured context so far to construct the final `WasiCtx`.
Expand Down Expand Up @@ -264,6 +280,7 @@ impl WasiCtxBuilder {
insecure_random_seed,
wall_clock,
monotonic_clock,
allow_ip_name_lookup,
built: _,
} = mem::replace(self, Self::new());
self.built = true;
Expand All @@ -281,6 +298,7 @@ impl WasiCtxBuilder {
insecure_random_seed,
wall_clock,
monotonic_clock,
allow_ip_name_lookup,
}
}
}
Expand All @@ -305,4 +323,5 @@ pub struct WasiCtx {
pub(crate) stdout: Box<dyn StdoutStream>,
pub(crate) stderr: Box<dyn StdoutStream>,
pub(crate) pool: Pool,
pub(crate) allow_ip_name_lookup: bool,
}
17 changes: 9 additions & 8 deletions crates/wasi/src/preview2/filesystem.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::preview2::bindings::filesystem::types;
use crate::preview2::{AbortOnDropJoinHandle, HostOutputStream, StreamError, Subscribe};
use crate::preview2::{
spawn_blocking, AbortOnDropJoinHandle, HostOutputStream, StreamError, Subscribe,
};
use anyhow::anyhow;
use bytes::{Bytes, BytesMut};
use std::io;
Expand Down Expand Up @@ -74,7 +76,7 @@ impl File {
R: Send + 'static,
{
let f = self.file.clone();
tokio::task::spawn_blocking(move || body(&f)).await.unwrap()
spawn_blocking(move || body(&f)).await
}
}

Expand Down Expand Up @@ -110,7 +112,7 @@ impl Dir {
R: Send + 'static,
{
let d = self.dir.clone();
tokio::task::spawn_blocking(move || body(&d)).await.unwrap()
spawn_blocking(move || body(&d)).await
}
}

Expand All @@ -127,13 +129,12 @@ impl FileInputStream {
use system_interface::fs::FileIoExt;
let f = Arc::clone(&self.file);
let p = self.position;
let (r, mut buf) = tokio::task::spawn_blocking(move || {
let (r, mut buf) = spawn_blocking(move || {
let mut buf = BytesMut::zeroed(size);
let r = f.read_at(&mut buf, p);
(r, buf)
})
.await
.unwrap();
.await;
let n = read_result(r)?;
buf.truncate(n);
self.position += n as u64;
Expand Down Expand Up @@ -213,7 +214,7 @@ impl HostOutputStream for FileOutputStream {

let f = Arc::clone(&self.file);
let m = self.mode;
let task = AbortOnDropJoinHandle::from(tokio::task::spawn_blocking(move || match m {
let task = spawn_blocking(move || match m {
FileOutputMode::Position(mut p) => {
let mut buf = buf;
while !buf.is_empty() {
Expand All @@ -232,7 +233,7 @@ impl HostOutputStream for FileOutputStream {
}
Ok(())
}
}));
});
self.state = OutputState::Waiting(task);
Ok(())
}
Expand Down
5 changes: 4 additions & 1 deletion crates/wasi/src/preview2/host/instance_network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use wasmtime::component::Resource;

impl<T: WasiView> instance_network::Host for T {
fn instance_network(&mut self) -> Result<Resource<Network>, anyhow::Error> {
let network = Network::new(self.ctx().pool.clone());
let network = Network {
pool: self.ctx().pool.clone(),
allow_ip_name_lookup: self.ctx().allow_ip_name_lookup,
};
let network = self.table_mut().push_resource(network)?;
Ok(network)
}
Expand Down
6 changes: 5 additions & 1 deletion crates/wasi/src/preview2/host/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ impl From<io::Error> for network::Error {
Some(libc::EADDRINUSE) => ErrorCode::AddressInUse,
Some(_) => return Self::trap(error.into()),
},
_ => return Self::trap(error.into()),

_ => {
log::debug!("unknown I/O error: {error}");
ErrorCode::Unknown
}
}
.into()
}
Expand Down
4 changes: 2 additions & 2 deletions crates/wasi/src/preview2/host/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
}

let network = table.get_resource(&network)?;
let binder = network.0.tcp_binder(local_address)?;
let binder = network.pool.tcp_binder(local_address)?;

// Perform the OS bind call.
binder.bind_existing_tcp_listener(
Expand Down Expand Up @@ -75,7 +75,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
}

let network = table.get_resource(&network)?;
let connecter = network.0.tcp_connecter(remote_address)?;
let connecter = network.pool.tcp_connecter(remote_address)?;

// Do an OS `connect`. Our socket is non-blocking, so it'll either...
{
Expand Down
Loading

0 comments on commit e21f45a

Please sign in to comment.