blob: 07c336d98362b6cfa3f1a15f3a041089d69effa2 [file] [log] [blame]
// Copyright 2019 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.
use {
anyhow::{format_err, Context as _, Error},
fidl_fuchsia_net as fnet, fidl_fuchsia_net_dhcpv6 as fdhcpv6,
fidl_fuchsia_net_interfaces as finterfaces, fidl_fuchsia_net_name as fnetname,
fidl_fuchsia_net_stack as fstack,
fidl_fuchsia_net_stack_ext::FidlReturn as _,
fidl_fuchsia_netemul_guest::{
CommandListenerMarker, GuestDiscoveryMarker, GuestInteractionMarker,
},
fuchsia_async::TimeoutExt as _,
fuchsia_component::client,
futures::TryFutureExt as _,
netemul_guest_lib::wait_for_command_completion,
std::time::Duration,
};
/// Run a command on a guest VM to configure its DHCP server.
///
/// # Arguments
///
/// * `guest_name` - String slice name of the guest to be configured.
/// * `command_to_run` - String slice command that will be executed on the guest VM.
pub async fn configure_dhcp_server(guest_name: &str, command_to_run: &str) -> Result<(), Error> {
// Run a bash script to start the DHCP service on the Debian guest.
let mut env = vec![];
let guest_discovery_service = client::connect_to_service::<GuestDiscoveryMarker>()?;
let (gis, gis_ch) = fidl::endpoints::create_proxy::<GuestInteractionMarker>()?;
let () = guest_discovery_service.get_guest(None, guest_name, gis_ch)?;
let (client_proxy, server_end) = fidl::endpoints::create_proxy::<CommandListenerMarker>()?;
gis.execute_command(command_to_run, &mut env.iter_mut(), None, None, None, server_end)?;
// Ensure that the process completes normally.
wait_for_command_completion(client_proxy.take_event_stream(), None).await
}
/// Ensure that an address is added to a Netstack interface.
///
/// # Arguments
///
/// * `want_addr` - IpAddress that should appear on a Netstack interface.
/// * `timeout` - Duration to wait for the address to appear in Netstack.
pub async fn verify_v4_addr_present(
want_addr: fnet::IpAddress,
timeout: Duration,
) -> Result<(), Error> {
let interface_state = client::connect_to_service::<finterfaces::StateMarker>()?;
let mut if_map = std::collections::HashMap::new();
fidl_fuchsia_net_interfaces_ext::wait_interface(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state)?,
&mut if_map,
|if_map| {
// TODO(https://github.com/rust-lang/rust/issues/64260): use bool::then when we're on Rust 1.50.0.
if if_map.values().any(
|fidl_fuchsia_net_interfaces_ext::Properties { addresses, .. }| {
addresses.iter().any(
|fidl_fuchsia_net_interfaces_ext::Address {
addr: fnet::Subnet { addr, prefix_len: _ },
}| { *addr == want_addr },
)
},
) {
Some(())
} else {
None
}
},
)
.map_err(anyhow::Error::from)
.on_timeout(timeout, || Err(anyhow::anyhow!("timed out")))
.await
.with_context(|| {
format!(
"failed to wait for address {:?}, final interfaces state: {}",
want_addr,
if_map.iter().fold(
String::from("addresses present:"),
|s, (id, fidl_fuchsia_net_interfaces_ext::Properties { addresses, .. })| {
s + &format!(" {:?}: {:?}", id, addresses)
}
),
)
})
}
/// Verifies a DHCPv6 client can receive an expected list of DNS servers.
///
/// # Arguments
///
/// * `interface_id` - ID identifying the interface to start the DHCPv6 client on.
/// * `want_dns_servers` - Vector of DNS servers that the DHCPv6 client is expected to receive.
pub async fn verify_v6_dns_servers(
interface_id: u64,
want_dns_servers: Vec<fnetname::DnsServer_>,
) -> Result<(), Error> {
let stack = client::connect_to_service::<fstack::StackMarker>()
.context("connecting to stack service")?;
let info = stack
.get_interface_info(interface_id)
.await
.squash_result()
.context("getting interface info")?;
let addr = info
.properties
.addresses
.into_iter()
.find_map(|addr: fnet::Subnet| match addr.addr {
fnet::IpAddress::Ipv4(_addr) => None,
fnet::IpAddress::Ipv6(addr) => {
// Only use link-local addresses.
//
// TODO(https://github.com/rust-lang/rust/issues/27709): use
// `is_unicast_link_local_strict` when it's available in stable Rust.
if addr.addr[..8] == [0xfe, 0x80, 0, 0, 0, 0, 0, 0] {
Some(addr)
} else {
None
}
}
})
.ok_or(format_err!("no addresses found to start DHCPv6 client on"))?;
let provider = client::connect_to_service::<fdhcpv6::ClientProviderMarker>()
.context("connecting to DHCPv6 client")?;
let (client_end, server_end) = fidl::endpoints::create_endpoints::<fdhcpv6::ClientMarker>()
.context("creating DHCPv6 client channel")?;
let () = provider.new_client(
fdhcpv6::NewClientParams {
interface_id: Some(interface_id),
address: Some(fnet::Ipv6SocketAddress {
address: addr,
port: fdhcpv6::DEFAULT_CLIENT_PORT,
zone_index: interface_id,
}),
models: Some(fdhcpv6::OperationalModels {
stateless: Some(fdhcpv6::Stateless {
options_to_request: Some(vec![fdhcpv6::RequestableOptionCode::DnsServers]),
..fdhcpv6::Stateless::EMPTY
}),
..fdhcpv6::OperationalModels::EMPTY
}),
..fdhcpv6::NewClientParams::EMPTY
},
server_end,
)?;
let client_proxy = client_end.into_proxy().context("getting client proxy from channel")?;
let got_dns_servers = client_proxy.watch_servers().await.context("watching DNS servers")?;
if got_dns_servers == want_dns_servers {
Ok(())
} else {
Err(format_err!(
"DHCPv6 client received unexpected DNS servers:\ngot dns servers: {:?}\n, want dns servers: {:?}\n",
got_dns_servers,
want_dns_servers
))
}
}