blob: 38bb5dc160e359f2f09af3b89fe8d08e4edc1d5b [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![deny(missing_docs, unreachable_patterns)]
//! Netemul utilities.
use std::convert::TryInto as _;
use std::path::{Path, PathBuf};
use fidl_fuchsia_hardware_ethernet as hw_eth;
use fidl_fuchsia_hardware_network as hw_net;
use fidl_fuchsia_net as net;
use fidl_fuchsia_net_dhcp as net_dhcp;
use fidl_fuchsia_net_interfaces as net_interfaces;
use fidl_fuchsia_net_stack as net_stack;
use fidl_fuchsia_net_stack_ext::FidlReturn as _;
use fidl_fuchsia_netemul_environment as netemul_environment;
use fidl_fuchsia_netemul_network as netemul_network;
use fidl_fuchsia_netemul_sandbox as netemul_sandbox;
use fidl_fuchsia_netstack as netstack;
use fidl_fuchsia_posix_socket as posix_socket;
use fidl_fuchsia_sys as sys;
use fuchsia_zircon as zx;
use anyhow::Context as _;
use futures::future::{FutureExt as _, TryFutureExt as _};
type Result<T = ()> = std::result::Result<T, anyhow::Error>;
/// The default MTU used in in netemul endpoint configurations.
pub const DEFAULT_MTU: u16 = 1500;
/// Abstraction for different endpoint backing types.
pub trait Endpoint: Copy + Clone {
/// The backing [`EndpointBacking`] for this `Endpoint`.
///
/// [`EndpointBacking`]: netemul_network::EndpointBacking
const NETEMUL_BACKING: netemul_network::EndpointBacking;
/// The relative path from the root device directory where devices of this `Endpoint`
/// can be discovered.
const DEV_PATH: &'static str;
/// Returns the path to an endpoint with the given name in this `Endpoint`'s
/// device directory.
fn dev_path(name: &str) -> PathBuf {
Path::new(Self::DEV_PATH).join(name)
}
/// Returns an [`EndpointConfig`] with the provided parameters for this
/// endpoint type.
///
/// [`EndpointConfig`]: netemul_network::EndpointConfig
fn make_config(mtu: u16, mac: Option<net::MacAddress>) -> netemul_network::EndpointConfig {
netemul_network::EndpointConfig {
mtu,
mac: mac.map(Box::new),
backing: Self::NETEMUL_BACKING,
}
}
}
/// An Ethernet implementation of `Endpoint`.
#[derive(Copy, Clone)]
pub enum Ethernet {}
impl Endpoint for Ethernet {
const NETEMUL_BACKING: netemul_network::EndpointBacking =
netemul_network::EndpointBacking::Ethertap;
const DEV_PATH: &'static str = "class/ethernet";
}
/// A Network Device implementation of `Endpoint`.
#[derive(Copy, Clone)]
pub enum NetworkDevice {}
impl Endpoint for NetworkDevice {
const NETEMUL_BACKING: netemul_network::EndpointBacking =
netemul_network::EndpointBacking::NetworkDevice;
const DEV_PATH: &'static str = "class/network";
}
/// Helper definition to help type system identify `None` as `IntoIterator`
/// where `Item: Into<netemul_environment::LaunchService`.
pub const NO_SERVICES: Option<netemul_environment::LaunchService> = None;
/// A test sandbox backed by a [`netemul_sandbox::SandboxProxy`].
///
/// `TestSandbox` provides various utility methods to set up network
/// environments for use in testing. The lifetime of the `TestSandbox` is tied
/// to the netemul sandbox itself, dropping it will cause all the created
/// environments, networks, and endpoints to be destroyed.
#[must_use]
pub struct TestSandbox {
sandbox: netemul_sandbox::SandboxProxy,
}
impl TestSandbox {
/// Creates a new empty sandbox.
pub fn new() -> Result<TestSandbox> {
let sandbox =
fuchsia_component::client::connect_to_service::<netemul_sandbox::SandboxMarker>()
.context("failed to connect to sandbox service")?;
Ok(TestSandbox { sandbox })
}
/// Creates an environment with `name` and `services`.
pub fn create_environment<I>(
&self,
name: impl Into<String>,
services: I,
) -> Result<TestEnvironment<'_>>
where
I: IntoIterator,
I::Item: Into<netemul_environment::LaunchService>,
{
let (environment, server) =
fidl::endpoints::create_proxy::<netemul_environment::ManagedEnvironmentMarker>()?;
let name = name.into();
let () = self.sandbox.create_environment(
server,
netemul_environment::EnvironmentOptions {
name: Some(name.clone()),
services: Some(services.into_iter().map(Into::into).collect()),
devices: None,
inherit_parent_launch_services: None,
logger_options: Some(netemul_environment::LoggerOptions {
enabled: Some(true),
klogs_enabled: None,
filter_options: None,
syslog_output: Some(true),
..netemul_environment::LoggerOptions::EMPTY
}),
..netemul_environment::EnvironmentOptions::EMPTY
},
)?;
Ok(TestEnvironment { environment, name, _sandbox: self })
}
/// Creates an environment with no services.
pub fn create_empty_environment(&self, name: impl Into<String>) -> Result<TestEnvironment<'_>> {
self.create_environment(name, NO_SERVICES)
}
/// Connects to the sandbox's `NetworkContext`.
fn get_network_context(&self) -> Result<netemul_network::NetworkContextProxy> {
let (ctx, server) =
fidl::endpoints::create_proxy::<netemul_network::NetworkContextMarker>()?;
let () = self.sandbox.get_network_context(server)?;
Ok(ctx)
}
/// Connects to the sandbox's `NetworkManager`.
fn get_network_manager(&self) -> Result<netemul_network::NetworkManagerProxy> {
let ctx = self.get_network_context()?;
let (network_manager, server) =
fidl::endpoints::create_proxy::<netemul_network::NetworkManagerMarker>()?;
let () = ctx.get_network_manager(server)?;
Ok(network_manager)
}
/// Connects to the sandbox's `EndpointManager`.
fn get_endpoint_manager(&self) -> Result<netemul_network::EndpointManagerProxy> {
let ctx = self.get_network_context()?;
let (ep_manager, server) =
fidl::endpoints::create_proxy::<netemul_network::EndpointManagerMarker>()?;
let () = ctx.get_endpoint_manager(server)?;
Ok(ep_manager)
}
/// Creates a new empty network with default configurations and `name`.
pub async fn create_network(&self, name: impl Into<String>) -> Result<TestNetwork<'_>> {
let name = name.into();
let netm = self.get_network_manager()?;
let (status, network) = netm
.create_network(
&name,
netemul_network::NetworkConfig {
latency: None,
packet_loss: None,
reorder: None,
..netemul_network::NetworkConfig::EMPTY
},
)
.await
.context("create_network FIDL error")?;
let () = zx::Status::ok(status).context("create_network failed")?;
let network = network
.ok_or_else(|| anyhow::anyhow!("create_network didn't return a valid network"))?
.into_proxy()?;
Ok(TestNetwork { network, name, sandbox: self })
}
/// Creates a new unattached endpoint with default configurations and `name`.
pub async fn create_endpoint<E, S>(&self, name: S) -> Result<TestEndpoint<'_>>
where
S: Into<String>,
E: Endpoint,
{
self.create_endpoint_with(name, E::make_config(DEFAULT_MTU, None)).await
}
/// Creates a new unattached endpoint with the provided configuration.
pub async fn create_endpoint_with(
&self,
name: impl Into<String>,
mut config: netemul_network::EndpointConfig,
) -> Result<TestEndpoint<'_>> {
let name = name.into();
let epm = self.get_endpoint_manager()?;
let (status, endpoint) =
epm.create_endpoint(&name, &mut config).await.context("create_endpoint FIDL error")?;
let () = zx::Status::ok(status).context("create_endpoint failed")?;
let endpoint = endpoint
.ok_or_else(|| anyhow::anyhow!("create_endpoint didn't return a valid endpoint"))?
.into_proxy()?;
Ok(TestEndpoint { endpoint, name, _sandbox: self })
}
}
/// Interface configuration used by [`TestEnvironment::join_network`].
pub enum InterfaceConfig {
/// Interface is configured with a static address.
StaticIp(net::Subnet),
/// Interface is configured to use DHCP to obtain an address.
Dhcp,
/// No address configuration is performed.
None,
}
/// An environment within a netemul sandbox.
#[must_use]
pub struct TestEnvironment<'a> {
environment: netemul_environment::ManagedEnvironmentProxy,
name: String,
_sandbox: &'a TestSandbox,
}
impl<'a> TestEnvironment<'a> {
/// Connects to a service within the environment.
pub fn connect_to_service<S>(&self) -> Result<S::Proxy>
where
S: fidl::endpoints::ServiceMarker + fidl::endpoints::DiscoverableService,
{
let (proxy, server) = zx::Channel::create()?;
let () = self.environment.connect_to_service(S::SERVICE_NAME, server)?;
let proxy = fuchsia_async::Channel::from_channel(proxy)?;
Ok(<S::Proxy as fidl::endpoints::Proxy>::from_channel(proxy))
}
/// Gets this environment's launcher.
///
/// All applications launched within a netemul environment will have their
/// output (stdout, stderr, syslog) decorated with the environment name.
pub fn get_launcher(&self) -> Result<sys::LauncherProxy> {
let (launcher, server) = fidl::endpoints::create_proxy::<sys::LauncherMarker>()
.context("failed to create launcher proxy")?;
let () = self.environment.get_launcher(server)?;
Ok(launcher)
}
/// Like [`join_network_with`], but uses default endpoint configurations.
pub async fn join_network<E, S>(
&self,
network: &TestNetwork<'a>,
ep_name: S,
config: &InterfaceConfig,
) -> Result<TestInterface<'a>>
where
E: Endpoint,
S: Into<String>,
{
let endpoint =
network.create_endpoint::<E, _>(ep_name).await.context("failed to create endpoint")?;
self.install_endpoint(endpoint, config).await
}
/// Joins `network` with by creating an endpoint with `ep_config` and
/// installing it into the environment with `if_config`.
///
/// `join_network_with` is a helper to create a new endpoint `ep_name`
/// attached to `network` and configure it with `if_config`. Returns a
/// [`TestInterface`] which is already added to this environment's netstack,
/// link online, enabled, and configured according to `config`.
///
/// Note that this environment needs a Netstack for this operation to
/// succeed.
pub async fn join_network_with(
&self,
network: &TestNetwork<'a>,
ep_name: impl Into<String>,
ep_config: netemul_network::EndpointConfig,
if_config: &InterfaceConfig,
) -> Result<TestInterface<'a>> {
let endpoint = network
.create_endpoint_with(ep_name, ep_config)
.await
.context("failed to create endpoint")?;
self.install_endpoint(endpoint, if_config).await
}
/// Installs and configures `endpoint` in this environment with `config`.
pub async fn install_endpoint(
&self,
endpoint: TestEndpoint<'a>,
config: &InterfaceConfig,
) -> Result<TestInterface<'a>> {
let interface =
endpoint.into_interface_in_environment(self).await.context("failed to add endpoint")?;
let () = interface.set_link_up(true).await.context("failed to start endpoint")?;
let () = match config {
InterfaceConfig::StaticIp(addr) => {
interface.add_ip_addr(*addr).await.context("failed to add static IP")?
}
InterfaceConfig::Dhcp => {
interface.start_dhcp().await.context("failed to start DHCP")?;
}
InterfaceConfig::None => (),
};
let () = interface.enable_interface().await.context("failed to enable interface")?;
// Wait for Netstack to observe interface up so callers can safely
// assume the state of the world on return.
let interface_state = self
.connect_to_service::<net_interfaces::StateMarker>()
.context("failed to connect to fuchsia.net.interfaces/State")?;
let () = fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state)?,
&mut fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(interface.id()),
|&fidl_fuchsia_net_interfaces_ext::Properties { online, .. }| {
// TODO(https://github.com/rust-lang/rust/issues/80967): use bool::then_some.
online.then(|| ())
},
)
.await
.context("failed to observe interface up")?;
Ok(interface)
}
/// Adds a device to the environment's virtual device filesystem.
pub fn add_virtual_device(&self, e: &TestEndpoint<'_>, path: &Path) -> Result {
let path = path
.to_str()
.with_context(|| format!("convert {} to str", path.display()))?
.to_string();
let (device, device_server_end) =
fidl::endpoints::create_endpoints::<netemul_network::DeviceProxy_Marker>()
.context("create endpoints")?;
e.get_proxy_(device_server_end).context("get proxy")?;
self.environment
.add_device(&mut netemul_environment::VirtualDevice { path, device })
.context("add device")
}
/// Removes a device from the environment's virtual device filesystem.
pub fn remove_virtual_device(&self, path: &Path) -> Result {
let path = path.to_str().with_context(|| format!("convert {} to str", path.display()))?;
self.environment.remove_device(path).context("remove device")
}
/// Creates a Datagram [`socket2::Socket`] backed by the implementation of
/// `fuchsia.posix.socket/Provider` in this environment.
pub async fn datagram_socket(
&self,
domain: fidl_fuchsia_posix_socket::Domain,
proto: fidl_fuchsia_posix_socket::DatagramSocketProtocol,
) -> Result<socket2::Socket> {
let socket_provider = self
.connect_to_service::<posix_socket::ProviderMarker>()
.context("failed to connect to socket provider")?;
let sock = socket_provider
.datagram_socket(domain, proto)
.await
.context("failed to call socket")?
.map_err(|e| std::io::Error::from_raw_os_error(e.into_primitive()))
.context("failed to create socket")?;
Ok(fdio::create_fd(sock.into()).context("failed to create fd")?)
}
/// Creates a Stream [`socket2::Socket`] backed by the implementation of
/// `fuchsia.posix.socket/Provider` in this environment.
pub async fn stream_socket(
&self,
domain: fidl_fuchsia_posix_socket::Domain,
proto: fidl_fuchsia_posix_socket::StreamSocketProtocol,
) -> Result<socket2::Socket> {
let socket_provider = self
.connect_to_service::<posix_socket::ProviderMarker>()
.context("failed to connect to socket provider")?;
let sock = socket_provider
.stream_socket(domain, proto)
.await
.context("failed to call socket")?
.map_err(|e| std::io::Error::from_raw_os_error(e.into_primitive()))
.context("failed to create socket")?;
Ok(fdio::create_fd(sock.into()).context("failed to create fd")?)
}
}
/// A virtual Network.
///
/// `TestNetwork` is a single virtual broadcast domain backed by Netemul.
/// Created through [`TestSandbox::create_network`].
#[must_use]
pub struct TestNetwork<'a> {
network: netemul_network::NetworkProxy,
name: String,
sandbox: &'a TestSandbox,
}
impl<'a> TestNetwork<'a> {
/// Attaches `ep` to this network.
pub async fn attach_endpoint(&self, ep: &TestEndpoint<'a>) -> Result<()> {
let status =
self.network.attach_endpoint(&ep.name).await.context("attach_endpoint FIDL error")?;
let () = zx::Status::ok(status).context("attach_endpoint failed")?;
Ok(())
}
/// Creates a new endpoint with `name` attached to this network.
pub async fn create_endpoint<E, S>(&self, name: S) -> Result<TestEndpoint<'a>>
where
E: Endpoint,
S: Into<String>,
{
let ep = self
.sandbox
.create_endpoint::<E, _>(name)
.await
.with_context(|| format!("failed to create endpoint for network {}", self.name))?;
let () = self.attach_endpoint(&ep).await.with_context(|| {
format!("failed to attach endpoint {} to network {}", ep.name, self.name)
})?;
Ok(ep)
}
/// Creates a new endpoint with `name` and `config` attached to this
/// network.
pub async fn create_endpoint_with(
&self,
name: impl Into<String>,
config: netemul_network::EndpointConfig,
) -> Result<TestEndpoint<'a>> {
let ep = self
.sandbox
.create_endpoint_with(name, config)
.await
.with_context(|| format!("failed to create endpoint for network {}", self.name))?;
let () = self.attach_endpoint(&ep).await.with_context(|| {
format!("failed to attach endpoint {} to network {}", ep.name, self.name)
})?;
Ok(ep)
}
/// Returns a fake endpoint.
pub fn create_fake_endpoint(&self) -> Result<TestFakeEndpoint<'a>> {
let (endpoint, server) =
fidl::endpoints::create_proxy::<netemul_network::FakeEndpointMarker>()
.context("failed to create launcher proxy")?;
let () = self.network.create_fake_endpoint(server)?;
return Ok(TestFakeEndpoint { endpoint, _sandbox: self.sandbox });
}
}
/// A virtual network endpoint backed by Netemul.
#[must_use]
pub struct TestEndpoint<'a> {
endpoint: netemul_network::EndpointProxy,
name: String,
_sandbox: &'a TestSandbox,
}
impl<'a> std::ops::Deref for TestEndpoint<'a> {
type Target = netemul_network::EndpointProxy;
fn deref(&self) -> &Self::Target {
&self.endpoint
}
}
/// A virtual fake network endpoint backed by Netemul.
#[must_use]
pub struct TestFakeEndpoint<'a> {
endpoint: netemul_network::FakeEndpointProxy,
_sandbox: &'a TestSandbox,
}
impl<'a> std::ops::Deref for TestFakeEndpoint<'a> {
type Target = netemul_network::FakeEndpointProxy;
fn deref(&self) -> &Self::Target {
&self.endpoint
}
}
impl<'a> TestFakeEndpoint<'a> {
/// Return a stream of frames.
///
/// Frames will be yielded as they are read from the fake endpoint.
pub fn frame_stream(
&self,
) -> impl futures::Stream<Item = std::result::Result<(Vec<u8>, u64), fidl::Error>> + '_ {
futures::stream::try_unfold(&self.endpoint, |ep| ep.read().map_ok(move |r| Some((r, ep))))
}
}
impl<'a> TestEndpoint<'a> {
/// Gets access to this device's virtual Ethernet device.
///
/// Note that an error is returned if the Endpoint is not a
/// [`netemul_network::DeviceConnection::Ethernet`].
pub async fn get_ethernet(&self) -> Result<fidl::endpoints::ClientEnd<hw_eth::DeviceMarker>> {
match self
.get_device()
.await
.with_context(|| format!("failed to get device connection for {}", self.name))?
{
netemul_network::DeviceConnection::Ethernet(e) => Ok(e),
netemul_network::DeviceConnection::NetworkDevice(_) => {
Err(anyhow::anyhow!("Endpoint {} is not an Ethernet device", self.name))
}
}
}
/// Gets access to this device's virtual Network device.
///
/// Note that an error is returned if the Endpoint is not a
/// [`netemul_network::DeviceConnection::NetworkDevice`].
pub async fn get_netdevice(
&self,
) -> Result<(
fidl::endpoints::ClientEnd<hw_net::DeviceMarker>,
fidl::endpoints::ClientEnd<hw_net::MacAddressingMarker>,
)> {
match self
.get_device()
.await
.with_context(|| format!("failed to get device connection for {}", self.name))?
{
netemul_network::DeviceConnection::NetworkDevice(n) => {
Self::connect_netdevice_protocols(n)
}
netemul_network::DeviceConnection::Ethernet(_) => {
Err(anyhow::anyhow!("Endpoint {} is not a Network Device", self.name))
}
}
}
/// Helper function to retrieve the protocols from a netdevice client end.
fn connect_netdevice_protocols(
netdevice: fidl::endpoints::ClientEnd<hw_net::DeviceInstanceMarker>,
) -> Result<(
fidl::endpoints::ClientEnd<hw_net::DeviceMarker>,
fidl::endpoints::ClientEnd<hw_net::MacAddressingMarker>,
)> {
let netdevice: hw_net::DeviceInstanceProxy = netdevice.into_proxy()?;
let (device, device_server_end) =
fidl::endpoints::create_endpoints::<hw_net::DeviceMarker>()?;
let (mac, mac_server_end) =
fidl::endpoints::create_endpoints::<hw_net::MacAddressingMarker>()?;
let () = netdevice.get_device(device_server_end)?;
let () = netdevice.get_mac_addressing(mac_server_end)?;
Ok((device, mac))
}
/// Adds this endpoint to `stack`, returning the interface identifier.
pub async fn add_to_stack(&self, stack: &net_stack::StackProxy) -> Result<u64> {
Ok(match self.get_device().await.context("get_device failed")? {
netemul_network::DeviceConnection::Ethernet(eth) => {
stack.add_ethernet_interface(&self.name, eth).await.squash_result()?
}
netemul_network::DeviceConnection::NetworkDevice(netdevice) => {
let (device, mac) = Self::connect_netdevice_protocols(netdevice)?;
stack
.add_interface(
net_stack::InterfaceConfig {
name: None,
topopath: None,
metric: None,
..net_stack::InterfaceConfig::EMPTY
},
&mut net_stack::DeviceDefinition::Ethernet(
net_stack::EthernetDeviceDefinition {
network_device: device,
mac: mac,
},
),
)
.await
.squash_result()?
}
})
}
/// Consumes this `TestEndpoint` and tries to add it to the Netstack in
/// `environment`, returning a [`TestInterface`] on success.
pub async fn into_interface_in_environment(
self,
environment: &TestEnvironment<'a>,
) -> Result<TestInterface<'a>> {
let stack = environment.connect_to_service::<net_stack::StackMarker>()?;
let netstack = environment.connect_to_service::<netstack::NetstackMarker>()?;
let id = self.add_to_stack(&stack).await.with_context(|| {
format!("failed to add {} to environment {}", self.name, environment.name)
})?;
Ok(TestInterface { endpoint: self, id, stack, netstack })
}
}
/// A [`TestEndpoint`] that is installed in an environment's Netstack.
#[must_use]
pub struct TestInterface<'a> {
endpoint: TestEndpoint<'a>,
id: u64,
stack: net_stack::StackProxy,
netstack: netstack::NetstackProxy,
}
impl<'a> std::ops::Deref for TestInterface<'a> {
type Target = netemul_network::EndpointProxy;
fn deref(&self) -> &Self::Target {
&self.endpoint
}
}
impl<'a> TestInterface<'a> {
/// Gets the interface identifier.
pub fn id(&self) -> u64 {
self.id
}
/// Enable interface.
///
/// Equivalent to `stack.enable_interface(test_interface.id())`.
pub async fn enable_interface(&self) -> Result<()> {
self.stack.enable_interface(self.id).await.squash_result().with_context(|| {
format!("stack.enable_interface for endpoint {} failed", self.endpoint.name)
})
}
/// Enable filtering on the interface.
///
/// Equivalent to `stack.enable_packet_filter(test_interface.id())`.
pub async fn enable_filter(&self) -> Result<()> {
self.stack.enable_packet_filter(self.id).await.squash_result().with_context(|| {
format!("stack.enable_packet_filter for endpoint {} failed", self.endpoint.name)
})
}
/// Disable filtering on the interface.
///
/// Equivalent to `stack.disable_packet_filter(test_interface.id())`.
pub async fn disable_filter(&self) -> Result<()> {
self.stack.disable_packet_filter(self.id).await.squash_result().with_context(|| {
format!("stack.disable_packet_filter for endpoint {} failed", self.endpoint.name)
})
}
/// Add interface address.
///
/// Equivalent to `stack.add_interface_address(test_interface.id(), &mut addr)`.
pub async fn add_ip_addr(&self, mut addr: net::Subnet) -> Result<()> {
self.stack.add_interface_address(self.id, &mut addr).await.squash_result().with_context(
|| {
format!(
"stack.add_interface_address({}, {:?}) for endpoint {} failed",
self.id, addr, self.endpoint.name
)
},
)
}
/// Gets the interface's info.
pub async fn get_info(&self) -> Result<net_stack::InterfaceInfo> {
self.stack.get_interface_info(self.id).await.squash_result().with_context(|| {
format!(
"stack.get_interface_info({}) for endpoint {} failed",
self.id, self.endpoint.name
)
})
}
/// Gets the interface's addresses.
pub async fn get_addrs(&self) -> Result<Vec<net::Subnet>> {
Ok(self.get_info().await?.properties.addresses)
}
async fn get_dhcp_client(&self) -> Result<net_dhcp::ClientProxy> {
let (dhcp_client, server_end) =
fidl::endpoints::create_proxy::<net_dhcp::ClientMarker>()
.context("failed to create endpoints for fuchsia.net.dhcp.Client")?;
let () = self
.netstack
.get_dhcp_client(
self.id.try_into().with_context(|| {
format!("interface ID should fit in a u32; ID = {}", self.id)
})?,
server_end,
)
.await
.context("failed to call netstack.get_dhcp_client")?
.map_err(zx::Status::from_raw)
.context("failed to get dhcp client")?;
Ok(dhcp_client)
}
/// Starts DHCP on this interface.
pub async fn start_dhcp(&self) -> Result<()> {
let dhcp_client = self.get_dhcp_client().await?;
let () = dhcp_client
.start()
.await
.context("failed to call dhcp_client.start")?
.map_err(zx::Status::from_raw)
.context("failed to start dhcp client")?;
Ok(())
}
/// Stops DHCP on this interface.
pub async fn stop_dhcp(&self) -> Result<()> {
let dhcp_client = self.get_dhcp_client().await?;
let () = dhcp_client
.stop()
.await
.context("failed to call dhcp_client.stop")?
.map_err(zx::Status::from_raw)
.context("failed to stop dhcp client")?;
Ok(())
}
}
/// Get the [`socket2::Domain`] for `addr`.
fn get_socket2_domain(addr: &std::net::SocketAddr) -> fidl_fuchsia_posix_socket::Domain {
let domain = match addr {
std::net::SocketAddr::V4(_) => fidl_fuchsia_posix_socket::Domain::Ipv4,
std::net::SocketAddr::V6(_) => fidl_fuchsia_posix_socket::Domain::Ipv6,
};
domain
}
/// Trait describing UDP sockets that can be bound in a testing environment.
pub trait EnvironmentUdpSocket: Sized {
/// Creates a UDP socket in `env` bound to `addr`.
fn bind_in_env<'a>(
env: &'a TestEnvironment<'a>,
addr: std::net::SocketAddr,
) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
}
impl EnvironmentUdpSocket for std::net::UdpSocket {
fn bind_in_env<'a>(
env: &'a TestEnvironment<'a>,
addr: std::net::SocketAddr,
) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
async move {
let sock = env
.datagram_socket(
get_socket2_domain(&addr),
fidl_fuchsia_posix_socket::DatagramSocketProtocol::Udp,
)
.await
.context("failed to create socket")?;
let () = sock.bind(&addr.into()).context("bind failed")?;
Result::Ok(sock.into())
}
.boxed_local()
}
}
impl EnvironmentUdpSocket for fuchsia_async::net::UdpSocket {
fn bind_in_env<'a>(
env: &'a TestEnvironment<'a>,
addr: std::net::SocketAddr,
) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
std::net::UdpSocket::bind_in_env(env, addr)
.and_then(|udp| {
futures::future::ready(
fuchsia_async::net::UdpSocket::from_socket(udp)
.context("failed to create fuchsia_async socket"),
)
})
.boxed_local()
}
}
/// Trait describing TCP listeners bound in a testing environment.
pub trait EnvironmentTcpListener: Sized {
/// Creates a TCP listener in `env` bound to `addr`.
fn listen_in_env<'a>(
env: &'a TestEnvironment<'a>,
addr: std::net::SocketAddr,
) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
Self::listen_in_env_with(env, addr, |_: &socket2::Socket| Ok(()))
}
/// Creates a TCP listener by creating a Socket2 socket in `env`. Closure `setup` is called with
/// the reference of the socket before the socket is bound to `addr`.
fn listen_in_env_with<'a>(
env: &'a TestEnvironment<'a>,
addr: std::net::SocketAddr,
setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
}
impl EnvironmentTcpListener for std::net::TcpListener {
fn listen_in_env_with<'a>(
env: &'a TestEnvironment<'a>,
addr: std::net::SocketAddr,
setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
async move {
let sock = env
.stream_socket(
get_socket2_domain(&addr),
fidl_fuchsia_posix_socket::StreamSocketProtocol::Tcp,
)
.await
.context("failed to create server socket")?;
let () = setup(&sock)?;
let () = sock.bind(&addr.into()).context("failed to bind server socket")?;
// Use 128 for the listen() backlog, same as the original implementation of TcpListener
// in Rust std (see https://doc.rust-lang.org/src/std/sys_common/net.rs.html#386).
let () = sock.listen(128).context("failed to listen on server socket")?;
Result::Ok(sock.into())
}
.boxed_local()
}
}
impl EnvironmentTcpListener for fuchsia_async::net::TcpListener {
fn listen_in_env_with<'a>(
env: &'a TestEnvironment<'a>,
addr: std::net::SocketAddr,
setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
std::net::TcpListener::listen_in_env_with(env, addr, setup)
.and_then(|listener| {
futures::future::ready(
fuchsia_async::net::TcpListener::from_std(listener)
.context("failed to create fuchsia_async socket"),
)
})
.boxed_local()
}
}
/// Trait describing TCP streams in a testing environment.
pub trait EnvironmentTcpStream: Sized {
/// Creates a TCP stream in `env` connected to `addr`.
fn connect_in_env<'a>(
env: &'a TestEnvironment<'a>,
addr: std::net::SocketAddr,
) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
// TODO: Implement this trait for std::net::TcpStream.
}
impl EnvironmentTcpStream for fuchsia_async::net::TcpStream {
fn connect_in_env<'a>(
env: &'a TestEnvironment<'a>,
addr: std::net::SocketAddr,
) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
async move {
let sock = env
.stream_socket(
get_socket2_domain(&addr),
fidl_fuchsia_posix_socket::StreamSocketProtocol::Tcp,
)
.await
.context("failed to create socket")?;
let stream = fuchsia_async::net::TcpStream::connect_from_raw(sock, addr)
.context("failed to create client tcp stream")?
.await
.context("failed to connect to server")?;
Result::Ok(stream)
}
.boxed_local()
}
}