| // 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. |
| |
| #![cfg(test)] |
| #![deny(warnings)] |
| #![feature(async_await, await_macro, futures_api)] |
| |
| use failure::ResultExt; |
| |
| type Result = std::result::Result<(), failure::Error>; |
| |
| fn connect_to_service<S: fidl::endpoints::ServiceMarker>( |
| managed_environment: &fidl_fuchsia_netemul_environment::ManagedEnvironmentProxy, |
| ) -> std::result::Result<S::Proxy, failure::Error> { |
| let (proxy, server) = fuchsia_zircon::Channel::create()?; |
| let () = managed_environment.connect_to_service(S::NAME, server)?; |
| let proxy = fuchsia_async::Channel::from_channel(proxy)?; |
| Ok(<S::Proxy as fidl::endpoints::Proxy>::from_channel(proxy)) |
| } |
| |
| fn get_network_context( |
| sandbox: &fidl_fuchsia_netemul_sandbox::SandboxProxy, |
| ) -> std::result::Result<fidl_fuchsia_netemul_network::NetworkContextProxy, failure::Error> { |
| let (client, server) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_netemul_network::NetworkContextMarker>() |
| .context("failed to create network context proxy")?; |
| let () = sandbox.get_network_context(server).context("failed to get network context")?; |
| Ok(client) |
| } |
| |
| fn get_endpoint_manager( |
| network_context: &fidl_fuchsia_netemul_network::NetworkContextProxy, |
| ) -> std::result::Result<fidl_fuchsia_netemul_network::EndpointManagerProxy, failure::Error> { |
| let (client, server) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_netemul_network::EndpointManagerMarker>() |
| .context("failed to create endpoint manager proxy")?; |
| let () = |
| network_context.get_endpoint_manager(server).context("failed to get endpoint manager")?; |
| Ok(client) |
| } |
| |
| async fn create_endpoint<'a>( |
| name: &'static str, |
| endpoint_manager: &'a fidl_fuchsia_netemul_network::EndpointManagerProxy, |
| ) -> std::result::Result<fidl_fuchsia_netemul_network::EndpointProxy, failure::Error> { |
| let (status, endpoint) = await!(endpoint_manager.create_endpoint( |
| name, |
| &mut fidl_fuchsia_netemul_network::EndpointConfig { |
| mtu: 1500, |
| mac: None, |
| backing: fidl_fuchsia_netemul_network::EndpointBacking::Ethertap, |
| }, |
| )) |
| .context("failed to create endpoint")?; |
| let () = fuchsia_zircon::Status::ok(status).context("failed to create endpoint")?; |
| let endpoint = endpoint |
| .ok_or(failure::err_msg("failed to create endpoint"))? |
| .into_proxy() |
| .context("failed to get endpoint proxy")?; |
| Ok(endpoint) |
| } |
| |
| fn create_netstack_environment( |
| sandbox: &fidl_fuchsia_netemul_sandbox::SandboxProxy, |
| name: String, |
| ) -> std::result::Result<fidl_fuchsia_netemul_environment::ManagedEnvironmentProxy, failure::Error> |
| { |
| let (client, server) = fidl::endpoints::create_proxy::< |
| fidl_fuchsia_netemul_environment::ManagedEnvironmentMarker, |
| >() |
| .context("failed to create managed environment proxy")?; |
| let () = sandbox |
| .create_environment( |
| server, |
| fidl_fuchsia_netemul_environment::EnvironmentOptions { |
| name: Some(name), |
| services: Some([ |
| <fidl_fuchsia_netstack::NetstackMarker as fidl::endpoints::ServiceMarker>::NAME, |
| <fidl_fuchsia_net::SocketProviderMarker as fidl::endpoints::ServiceMarker>::NAME, |
| <fidl_fuchsia_net_stack::StackMarker as fidl::endpoints::ServiceMarker>::NAME, |
| ] |
| // TODO(tamird): use into_iter after |
| // https://github.com/rust-lang/rust/issues/25725. |
| .iter() |
| .map(std::ops::Deref::deref) |
| .map(str::to_string) |
| .map(|name| fidl_fuchsia_netemul_environment::LaunchService { |
| name, |
| url: fuchsia_component::fuchsia_single_component_package_url!("netstack") |
| .to_string(), |
| arguments: Some(vec!["--sniff".to_string()]), |
| }) |
| .collect()), |
| devices: None, |
| inherit_parent_launch_services: None, |
| logger_options: Some(fidl_fuchsia_netemul_environment::LoggerOptions { |
| enabled: Some(true), |
| klogs_enabled: None, |
| filter_options: None, |
| syslog_output: Some(true), |
| }), |
| }, |
| ) |
| .context("failed to create environment")?; |
| Ok(client) |
| } |
| |
| async fn with_netstack_and_device<F, T, S>(name: &'static str, async_fn: T) -> Result |
| where |
| F: futures::Future<Output = Result>, |
| T: FnOnce( |
| S::Proxy, |
| fidl::endpoints::ClientEnd<fidl_fuchsia_hardware_ethernet::DeviceMarker>, |
| ) -> F, |
| S: fidl::endpoints::ServiceMarker, |
| { |
| let sandbox = fuchsia_component::client::connect_to_service::< |
| fidl_fuchsia_netemul_sandbox::SandboxMarker, |
| >() |
| .context("failed to connect to sandbox")?; |
| let network_context = get_network_context(&sandbox).context("failed to get network context")?; |
| let endpoint_manager = |
| get_endpoint_manager(&network_context).context("failed to get endpoint manager")?; |
| let endpoint = |
| await!(create_endpoint(name, &endpoint_manager)).context("failed to create endpoint")?; |
| let device = await!(endpoint.get_ethernet_device()).context("failed to get ethernet device")?; |
| let managed_environment = create_netstack_environment(&sandbox, name.to_string()) |
| .context("failed to create netstack environment")?; |
| let netstack_proxy = |
| connect_to_service::<S>(&managed_environment).context("failed to connect to netstack")?; |
| await!(async_fn(netstack_proxy, device)) |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn add_ethernet_device() -> Result { |
| let name = stringify!(add_ethernet_device); |
| |
| await!(with_netstack_and_device::<_, _, fidl_fuchsia_netstack::NetstackMarker>( |
| name, |
| async move |netstack, device| -> Result { |
| let id = await!(netstack.add_ethernet_device( |
| name, |
| &mut fidl_fuchsia_netstack::InterfaceConfig { |
| name: name.to_string(), |
| metric: 0, |
| ip_address_config: fidl_fuchsia_netstack::IpAddressConfig::Dhcp(true), |
| }, |
| device, |
| )) |
| .context("failed to add ethernet device")?; |
| let interface = await!(netstack.get_interfaces2()) |
| .context("failed to get interfaces")? |
| .into_iter() |
| .find(|interface| interface.id == id) |
| .ok_or(failure::err_msg("failed to find added ethernet device"))?; |
| assert_eq!( |
| interface.features & fidl_fuchsia_hardware_ethernet::INFO_FEATURE_LOOPBACK, |
| 0 |
| ); |
| assert_eq!(interface.flags & fidl_fuchsia_netstack::NET_INTERFACE_FLAG_UP, 0,); |
| Ok(()) |
| }, |
| )) |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn add_ethernet_interface() -> Result { |
| let name = stringify!(add_ethernet_interface); |
| |
| await!(with_netstack_and_device::<_, _, fidl_fuchsia_net_stack::StackMarker>( |
| name, |
| async move |stack, device| -> Result { |
| let (error, id) = await!(stack.add_ethernet_interface(name, device)) |
| .context("failed to add ethernet interface")?; |
| assert_eq!(error, None); |
| let interface = await!(stack.list_interfaces()) |
| .context("failed to list interfaces")? |
| .into_iter() |
| .find(|interface| interface.id == id) |
| .ok_or(failure::err_msg("failed to find added ethernet interface"))?; |
| assert_eq!( |
| interface.properties.features |
| & fidl_fuchsia_hardware_ethernet::INFO_FEATURE_LOOPBACK, |
| 0 |
| ); |
| assert_eq!( |
| interface.properties.physical_status, |
| fidl_fuchsia_net_stack::PhysicalStatus::Down |
| ); |
| Ok(()) |
| }, |
| )) |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn add_interface_address_not_found() -> Result { |
| let name = stringify!(add_interface_address_not_found); |
| |
| let sandbox = fuchsia_component::client::connect_to_service::< |
| fidl_fuchsia_netemul_sandbox::SandboxMarker, |
| >() |
| .context("failed to connect to sandbox")?; |
| let managed_environment = create_netstack_environment(&sandbox, name.to_string()) |
| .context("failed to create netstack environment")?; |
| let stack = connect_to_service::<fidl_fuchsia_net_stack::StackMarker>(&managed_environment) |
| .context("failed to connect to netstack")?; |
| let interfaces = await!(stack.list_interfaces()).context("failed to list interfaces")?; |
| let max_id = interfaces.iter().map(|interface| interface.id).max().unwrap_or(0); |
| let mut interface_address = fidl_fuchsia_net_stack::InterfaceAddress { |
| ip_address: fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { |
| addr: [0, 0, 0, 0], |
| }), |
| prefix_len: 0, |
| }; |
| let error = await!(stack.add_interface_address(max_id + 1, &mut interface_address,)) |
| .context("failed to add interface address")? |
| .ok_or(failure::err_msg("failed to get add interface address response"))?; |
| assert_eq!( |
| error.as_ref(), |
| &fidl_fuchsia_net_stack::Error { type_: fidl_fuchsia_net_stack::ErrorType::NotFound } |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn disable_interface_loopback() -> Result { |
| let name = stringify!(disable_interface_loopback); |
| |
| let sandbox = fuchsia_component::client::connect_to_service::< |
| fidl_fuchsia_netemul_sandbox::SandboxMarker, |
| >() |
| .context("failed to connect to sandbox")?; |
| let managed_environment = create_netstack_environment(&sandbox, name.to_string()) |
| .context("failed to create netstack environment")?; |
| let stack = connect_to_service::<fidl_fuchsia_net_stack::StackMarker>(&managed_environment) |
| .context("failed to connect to netstack")?; |
| let interfaces = await!(stack.list_interfaces()).context("failed to list interfaces")?; |
| let localhost = interfaces |
| .iter() |
| .find(|interface| { |
| interface.properties.features & fidl_fuchsia_hardware_ethernet::INFO_FEATURE_LOOPBACK |
| != 0 |
| }) |
| .ok_or(failure::err_msg("failed to find loopback interface"))?; |
| assert_eq!( |
| localhost.properties.administrative_status, |
| fidl_fuchsia_net_stack::AdministrativeStatus::Enabled |
| ); |
| assert_eq!( |
| await!(stack.disable_interface(localhost.id)).context("failed to disable interface")?, |
| None |
| ); |
| let (info, error) = |
| await!(stack.get_interface_info(localhost.id)).context("failed to get interface info")?; |
| assert_eq!(error, None); |
| assert_eq!( |
| info.ok_or(failure::err_msg("expected interface info to be present"))? |
| .properties |
| .administrative_status, |
| fidl_fuchsia_net_stack::AdministrativeStatus::Disabled |
| ); |
| Ok(()) |
| } |
| |
| // TODO(tamird): could this be done with a single stack and bridged interfaces? |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn acquire_dhcp() -> Result { |
| let name = stringify!(acquire_dhcp); |
| |
| let sandbox = fuchsia_component::client::connect_to_service::< |
| fidl_fuchsia_netemul_sandbox::SandboxMarker, |
| >() |
| .context("failed to connect to sandbox")?; |
| let network_context = get_network_context(&sandbox).context("failed to get network context")?; |
| let endpoint_manager = |
| get_endpoint_manager(&network_context).context("failed to get endpoint manager")?; |
| let server_environment = create_netstack_environment(&sandbox, format!("{}_server", name)) |
| .context("failed to create server environment")?; |
| let server_endpoint_name = "server"; |
| let server_endpoint = await!(create_endpoint(server_endpoint_name, &endpoint_manager)) |
| .context("failed to create endpoint")?; |
| let () = |
| await!(server_endpoint.set_link_up(true)).context("failed to start server endpoint")?; |
| { |
| let server_device = await!(server_endpoint.get_ethernet_device()) |
| .context("failed to get server ethernet device")?; |
| let server_stack = |
| connect_to_service::<fidl_fuchsia_net_stack::StackMarker>(&server_environment) |
| .context("failed to connect to server stack")?; |
| let (error, id) = await!(server_stack.add_ethernet_interface(name, server_device)) |
| .context("failed to add server ethernet interface")?; |
| assert_eq!(error, None); |
| let error = await!(server_stack.add_interface_address( |
| id, |
| &mut fidl_fuchsia_net_stack::InterfaceAddress { |
| ip_address: fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { |
| addr: [192, 168, 0, 1] |
| }), |
| prefix_len: 24, |
| } |
| )) |
| .context("failed to add interface address")?; |
| assert_eq!(error, None); |
| let error = await!(server_stack.enable_interface(id)) |
| .context("failed to enable server interface")?; |
| assert_eq!(error, None); |
| } |
| let launcher = { |
| let (client, server) = fidl::endpoints::create_proxy::<fidl_fuchsia_sys::LauncherMarker>() |
| .context("failed to create launcher proxy")?; |
| let () = server_environment.get_launcher(server).context("failed to get launcher")?; |
| client |
| }; |
| let dhcpd = fuchsia_component::client::launch( |
| &launcher, |
| fuchsia_component::fuchsia_single_component_package_url!("dhcpd").to_string(), |
| None, |
| ) |
| .context("failed to start dhcpd")?; |
| let client_environment = create_netstack_environment(&sandbox, format!("{}_client", name)) |
| .context("failed to create client environment")?; |
| let client_endpoint_name = "client"; |
| let client_endpoint = await!(create_endpoint(client_endpoint_name, &endpoint_manager)) |
| .context("failed to create endpoint")?; |
| let () = |
| await!(client_endpoint.set_link_up(true)).context("failed to start client endpoint")?; |
| |
| let network_manager = { |
| let (client, server) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_netemul_network::NetworkManagerMarker>() |
| .context("failed to create network manager proxy")?; |
| let () = |
| network_context.get_network_manager(server).context("failed to get network manager")?; |
| client |
| }; |
| |
| let (status, network) = await!(network_manager.create_network( |
| name, |
| fidl_fuchsia_netemul_network::NetworkConfig { |
| latency: None, |
| packet_loss: None, |
| reorder: None, |
| }, |
| )) |
| .context("failed to create network")?; |
| let network = network |
| .ok_or(failure::err_msg("failed to create network"))? |
| .into_proxy() |
| .context("failed to get network proxy")?; |
| let () = fuchsia_zircon::Status::ok(status).context("failed to create network")?; |
| let status = await!(network.attach_endpoint(server_endpoint_name)) |
| .context("failed to attach server endpoint")?; |
| let () = fuchsia_zircon::Status::ok(status).context("failed to attach server endpoint")?; |
| let status = await!(network.attach_endpoint(client_endpoint_name)) |
| .context("failed to attach client endpoint")?; |
| let () = fuchsia_zircon::Status::ok(status).context("failed to attach client endpoint")?; |
| |
| { |
| let client_device = await!(client_endpoint.get_ethernet_device()) |
| .context("failed to get client ethernet device")?; |
| let client_stack = |
| connect_to_service::<fidl_fuchsia_net_stack::StackMarker>(&client_environment) |
| .context("failed to connect to client stack")?; |
| let (error, id) = await!(client_stack.add_ethernet_interface(name, client_device)) |
| .context("failed to add client ethernet interface")?; |
| assert_eq!(error, None); |
| let error = await!(client_stack.enable_interface(id)) |
| .context("failed to enable client interface")?; |
| assert_eq!(error, None); |
| let client_netstack = |
| connect_to_service::<fidl_fuchsia_netstack::NetstackMarker>(&client_environment) |
| .context("failed to connect to client netstack")?; |
| let error = await!(client_netstack.set_dhcp_client_status(id as u32, true)) |
| .context("failed to set DHCP client status")?; |
| assert_eq!(error.status, fidl_fuchsia_netstack::Status::Ok, "{}", error.message); |
| |
| let mut address_change_stream = futures::TryStreamExt::try_filter_map( |
| client_netstack.take_event_stream(), |
| |fidl_fuchsia_netstack::NetstackEvent::OnInterfacesChanged { interfaces }| { |
| futures::future::ok( |
| interfaces.into_iter().find(|interface| interface.id as u64 == id).and_then( |
| |interface| match interface.addr { |
| fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { |
| addr, |
| }) => { |
| if addr == std::net::Ipv4Addr::UNSPECIFIED.octets() { |
| None |
| } else { |
| Some(interface.addr) |
| } |
| } |
| fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address { |
| .. |
| }) => None, |
| }, |
| ), |
| ) |
| }, |
| ); |
| let address_change = futures::StreamExt::next(&mut address_change_stream); |
| let address_change = fuchsia_async::TimeoutExt::on_timeout( |
| address_change, |
| // Netstack's DHCP client retries every 3 seconds. At the time of writing, dhcpd loses |
| // the race here and only starts after the first request from the DHCP client, which |
| // results in a 3 second toll. This test typically takes ~4.5 seconds; we apply a large |
| // multiple to be safe. |
| fuchsia_zircon::Time::after(fuchsia_zircon::Duration::from_seconds(60)), |
| || None, |
| ); |
| let _: fidl_fuchsia_net::IpAddress = await!(address_change) |
| .ok_or(failure::err_msg("failed to observe DHCP acquisition"))? |
| .context("failed to observe DHCP acquisition")?; |
| } |
| drop(dhcpd); |
| Ok(()) |
| } |