| // Copyright 2018 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}; |
| use fidl_fuchsia_hardware_ethernet as zx_eth; |
| use fidl_fuchsia_inspect_deprecated as inspect; |
| use fidl_fuchsia_net as net; |
| use fidl_fuchsia_net_ext as net_ext; |
| use fidl_fuchsia_net_filter::{FilterMarker, FilterProxy}; |
| use fidl_fuchsia_net_neighbor as neighbor; |
| use fidl_fuchsia_net_neighbor_ext as neighbor_ext; |
| use fidl_fuchsia_net_stack::{ |
| self as netstack, InterfaceInfo, LogMarker, LogProxy, StackMarker, StackProxy, |
| }; |
| use fidl_fuchsia_net_stack_ext::{self as pretty, exec_fidl as stack_fidl, FidlReturn}; |
| use fidl_fuchsia_netstack::{NetstackMarker, NetstackProxy}; |
| use fuchsia_async as fasync; |
| use fuchsia_component::client::connect_to_service; |
| use fuchsia_zircon as zx; |
| use futures::{FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _}; |
| use glob::glob; |
| use log::{info, Level, Log, Metadata, Record, SetLoggerError}; |
| use netfilter::FidlReturn as FilterFidlReturn; |
| use prettytable::{cell, format, row, Row, Table}; |
| use std::fs::File; |
| use std::os::unix::io::AsRawFd; |
| use std::str::FromStr; |
| |
| mod opts; |
| |
| use crate::opts::*; |
| |
| macro_rules! filter_fidl { |
| ($method:expr, $context:expr) => { |
| $method.await.transform_result().context($context) |
| }; |
| } |
| |
| /// Logger which prints levels at or below info to stdout and levels at or |
| /// above warn to stderr. |
| struct Logger; |
| |
| const LOG_LEVEL: Level = Level::Info; |
| |
| impl Log for Logger { |
| fn enabled(&self, metadata: &Metadata<'_>) -> bool { |
| metadata.level() <= LOG_LEVEL |
| } |
| |
| fn log(&self, record: &Record<'_>) { |
| if self.enabled(record.metadata()) { |
| match record.metadata().level() { |
| Level::Trace | Level::Debug | Level::Info => println!("{}", record.args()), |
| Level::Warn | Level::Error => eprintln!("{}", record.args()), |
| } |
| } |
| } |
| |
| fn flush(&self) {} |
| } |
| |
| static LOGGER: Logger = Logger; |
| |
| fn logger_init() -> Result<(), SetLoggerError> { |
| log::set_logger(&LOGGER).map(|()| log::set_max_level(LOG_LEVEL.to_level_filter())) |
| } |
| |
| fn add_row(t: &mut Table, row: Row) { |
| let _: &mut Row = t.add_row(row); |
| } |
| |
| #[fasync::run_singlethreaded] |
| async fn main() -> Result<(), Error> { |
| let () = logger_init()?; |
| let command: Command = argh::from_env(); |
| let stack = connect_to_service::<StackMarker>().context("failed to connect to netstack")?; |
| let netstack = |
| connect_to_service::<NetstackMarker>().context("failed to connect to netstack")?; |
| let filter = connect_to_service::<FilterMarker>().context("failed to connect to netfilter")?; |
| let log = connect_to_service::<LogMarker>().context("failed to connect to netstack log")?; |
| |
| match command.cmd { |
| CommandEnum::If(If { if_cmd: cmd }) => { |
| do_if(cmd, &stack, &netstack).await.context("failed during if command") |
| } |
| CommandEnum::Fwd(Fwd { fwd_cmd: cmd }) => { |
| do_fwd(cmd, stack).await.context("failed during fwd command") |
| } |
| CommandEnum::Route(Route { route_cmd: cmd }) => { |
| do_route(cmd, netstack).await.context("failed during route command") |
| } |
| CommandEnum::Filter(Filter { filter_cmd: cmd }) => { |
| do_filter(cmd, filter).await.context("failed during filter command") |
| } |
| CommandEnum::IpFwd(IpFwd { ip_fwd_cmd: cmd }) => { |
| do_ip_fwd(cmd, stack).await.context("failed during ip-fwd command") |
| } |
| CommandEnum::Log(crate::opts::Log { log_cmd: cmd }) => { |
| do_log(cmd, log).await.context("failed during log command") |
| } |
| CommandEnum::Stat(Stat { stat_cmd: cmd }) => { |
| do_stat(cmd).await.context("failed during stat command") |
| } |
| CommandEnum::Metric(Metric { metric_cmd: cmd }) => { |
| do_metric(cmd, netstack).await.context("failed during metric command") |
| } |
| CommandEnum::Dhcp(Dhcp { dhcp_cmd: cmd }) => { |
| do_dhcp(cmd, netstack).await.context("failed during dhcp command") |
| } |
| CommandEnum::Neigh(Neigh { neigh_cmd: cmd }) => { |
| do_neigh(cmd).await.context("failed during neigh command") |
| } |
| } |
| } |
| |
| fn shortlist_interfaces(name_pattern: &str, interfaces: &mut Vec<InterfaceInfo>) { |
| interfaces.retain(|i| i.properties.name.contains(name_pattern)) |
| } |
| |
| async fn tabulate_interfaces_info(interfaces: Vec<InterfaceInfo>) -> Result<String, Error> { |
| let mut t = Table::new(); |
| t.set_format(format::FormatBuilder::new().padding(2, 2).build()); |
| |
| for (i, info) in interfaces.into_iter().enumerate() { |
| if i > 0 { |
| let () = add_row(&mut t, row![]); |
| } |
| |
| let pretty::InterfaceInfo { |
| id, |
| properties: |
| pretty::InterfaceProperties { |
| name, |
| topopath, |
| filepath, |
| mac, |
| mtu, |
| features, |
| administrative_status, |
| physical_status, |
| addresses, |
| }, |
| } = info.into(); |
| |
| let () = add_row(&mut t, row!["nicid", id]); |
| let () = add_row(&mut t, row!["name", name]); |
| let () = add_row(&mut t, row!["topopath", topopath]); |
| let () = add_row(&mut t, row!["filepath", filepath]); |
| |
| let () = if let Some(mac) = mac { |
| add_row(&mut t, row!["mac", mac]) |
| } else { |
| add_row(&mut t, row!["mac", "-"]) |
| }; |
| |
| let () = add_row(&mut t, row!["mtu", mtu]); |
| let () = add_row(&mut t, row!["features", format!("{:?}", features)]); |
| let () = add_row( |
| &mut t, |
| row!["status", format!("{} | {}", administrative_status, physical_status)], |
| ); |
| for addr in addresses { |
| let () = add_row(&mut t, row!["addr", addr]); |
| } |
| } |
| Ok(t.to_string()) |
| } |
| |
| async fn do_if( |
| cmd: opts::IfEnum, |
| stack: &StackProxy, |
| netstack: &NetstackProxy, |
| ) -> Result<(), Error> { |
| match cmd { |
| IfEnum::List(IfList { name_pattern }) => { |
| let mut response = stack.list_interfaces().await.context("error getting response")?; |
| if let Some(name_pattern) = name_pattern { |
| let () = shortlist_interfaces(&name_pattern, &mut response); |
| } |
| let result = tabulate_interfaces_info(response) |
| .await |
| .context("error tabulating interface info")?; |
| println!("{}", result); |
| } |
| IfEnum::Add(IfAdd { path }) => { |
| let dev = File::open(&path).context("failed to open device")?; |
| let topological_path = |
| fdio::device_get_topo_path(&dev).context("failed to get topological path")?; |
| let fd = dev.as_raw_fd(); |
| let mut client = 0; |
| zx::Status::ok(unsafe { fdio::fdio_sys::fdio_get_service_handle(fd, &mut client) }) |
| .context("failed to get fdio service handle")?; |
| let dev = fidl::endpoints::ClientEnd::<zx_eth::DeviceMarker>::new( |
| // Safe because we checked the return status above. |
| zx::Channel::from(unsafe { zx::Handle::from_raw(client) }), |
| ); |
| let id = stack_fidl!( |
| stack.add_ethernet_interface(&topological_path, dev), |
| "error adding interface" |
| )?; |
| info!("Added interface {}", id); |
| } |
| IfEnum::Del(IfDel { id }) => { |
| let () = stack_fidl!(stack.del_ethernet_interface(id), "error removing interface")?; |
| info!("Deleted interface {}", id); |
| } |
| IfEnum::Get(IfGet { id }) => { |
| let info = stack_fidl!(stack.get_interface_info(id), "error getting interface")?; |
| println!("{}", pretty::InterfaceInfo::from(info)); |
| } |
| IfEnum::Enable(IfEnable { id }) => { |
| let () = stack_fidl!(stack.enable_interface(id), "error enabling interface")?; |
| info!("Interface {} enabled", id); |
| } |
| IfEnum::Disable(IfDisable { id }) => { |
| let () = stack_fidl!(stack.disable_interface(id), "error disabling interface")?; |
| info!("Interface {} disabled", id); |
| } |
| IfEnum::Addr(IfAddr { addr_cmd }) => match addr_cmd { |
| IfAddrEnum::Add(IfAddrAdd { id, addr, prefix }) => { |
| let parsed_addr = net_ext::IpAddress::from_str(&addr)?.into(); |
| let mut fidl_addr = net::Subnet { addr: parsed_addr, prefix_len: prefix }; |
| let () = stack_fidl!( |
| stack.add_interface_address(id, &mut fidl_addr), |
| "error adding interface address" |
| )?; |
| info!("Address {} added to interface {}", net_ext::Subnet::from(fidl_addr), id); |
| } |
| IfAddrEnum::Del(IfAddrDel { id, addr, prefix }) => { |
| let parsed_addr = net_ext::IpAddress::from_str(&addr)?.into(); |
| let prefix_len = prefix.unwrap_or_else(|| match parsed_addr { |
| net::IpAddress::Ipv4(_) => 32, |
| net::IpAddress::Ipv6(_) => 128, |
| }); |
| let mut fidl_addr = net::Subnet { addr: parsed_addr, prefix_len }; |
| let () = stack_fidl!( |
| stack.del_interface_address(id, &mut fidl_addr), |
| "error deleting interface address" |
| )?; |
| info!("Address {} deleted from interface {}", net_ext::Subnet::from(fidl_addr), id); |
| } |
| }, |
| IfEnum::Bridge(IfBridge { ids }) => { |
| let (result, bridge_id) = netstack.bridge_interfaces(&ids).await?; |
| if result.status != fidl_fuchsia_netstack::Status::Ok { |
| return Err(anyhow::anyhow!("{:?}: {}", result.status, result.message)); |
| } else { |
| info!("network bridge created with id {}", bridge_id); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn do_fwd(cmd: opts::FwdEnum, stack: StackProxy) -> Result<(), Error> { |
| match cmd { |
| FwdEnum::List(_) => { |
| let response = |
| stack.get_forwarding_table().await.context("error retrieving forwarding table")?; |
| for entry in response { |
| println!("{}", pretty::ForwardingEntry::from(entry)); |
| } |
| } |
| FwdEnum::AddDevice(FwdAddDevice { id, addr, prefix }) => { |
| let mut entry = netstack::ForwardingEntry { |
| subnet: net::Subnet { |
| addr: net_ext::IpAddress::from_str(&addr)?.into(), |
| prefix_len: prefix, |
| }, |
| destination: netstack::ForwardingDestination::DeviceId(id), |
| }; |
| let () = stack_fidl!( |
| stack.add_forwarding_entry(&mut entry), |
| "error adding device forwarding entry" |
| )?; |
| info!("Added forwarding entry for {}/{} to device {}", addr, prefix, id); |
| } |
| FwdEnum::AddHop(FwdAddHop { next_hop, addr, prefix }) => { |
| let mut entry = netstack::ForwardingEntry { |
| subnet: net::Subnet { |
| addr: net_ext::IpAddress::from_str(&addr)?.into(), |
| prefix_len: prefix, |
| }, |
| destination: netstack::ForwardingDestination::NextHop( |
| net_ext::IpAddress::from_str(&next_hop)?.into(), |
| ), |
| }; |
| let () = stack_fidl!( |
| stack.add_forwarding_entry(&mut entry), |
| "error adding next-hop forwarding entry" |
| )?; |
| info!("Added forwarding entry for {}/{} to {}", addr, prefix, next_hop); |
| } |
| FwdEnum::Del(FwdDel { addr, prefix }) => { |
| let mut entry = net::Subnet { |
| addr: net_ext::IpAddress::from_str(&addr)?.into(), |
| prefix_len: prefix, |
| }; |
| let () = stack_fidl!( |
| stack.del_forwarding_entry(&mut entry), |
| "error removing forwarding entry" |
| )?; |
| info!("Removed forwarding entry for {}/{}", addr, prefix); |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn do_route(cmd: opts::RouteEnum, netstack: NetstackProxy) -> Result<(), Error> { |
| match cmd { |
| RouteEnum::List(RouteList {}) => { |
| let response = |
| netstack.get_route_table2().await.context("error retrieving routing table")?; |
| |
| let mut t = Table::new(); |
| t.set_format(format::FormatBuilder::new().padding(2, 2).build()); |
| |
| t.set_titles(row!["Destination", "Netmask", "Gateway", "NICID", "Metric"]); |
| for entry in response { |
| let route = fidl_fuchsia_netstack_ext::RouteTableEntry2::from(entry); |
| let gateway_str = match route.gateway { |
| None => "-".to_string(), |
| Some(g) => format!("{}", g), |
| }; |
| let () = add_row( |
| &mut t, |
| row![route.destination, route.netmask, gateway_str, route.nicid, route.metric], |
| ); |
| } |
| |
| let _lines_printed: usize = t.printstd(); |
| println!(); |
| } |
| RouteEnum::Add(route) => { |
| let () = with_route_table_transaction_and_entry(&netstack, |transaction| { |
| transaction.add_route(&mut route.into()) |
| }) |
| .await?; |
| } |
| RouteEnum::Del(route) => { |
| let () = with_route_table_transaction_and_entry(&netstack, |transaction| { |
| transaction.del_route(&mut route.into()) |
| }) |
| .await?; |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn with_route_table_transaction_and_entry<T, F>( |
| netstack: &fidl_fuchsia_netstack::NetstackProxy, |
| func: T, |
| ) -> Result<(), Error> |
| where |
| F: core::future::Future<Output = Result<i32, fidl::Error>>, |
| T: FnOnce(&fidl_fuchsia_netstack::RouteTableTransactionProxy) -> F, |
| { |
| let (route_table, server_end) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_netstack::RouteTableTransactionMarker>()?; |
| let () = fuchsia_zircon::Status::ok(netstack.start_route_table_transaction(server_end).await?)?; |
| let status = func(&route_table).await?; |
| let () = fuchsia_zircon::Status::ok(status)?; |
| Ok(()) |
| } |
| |
| async fn do_filter(cmd: opts::FilterEnum, filter: FilterProxy) -> Result<(), Error> { |
| match cmd { |
| FilterEnum::Enable(_) => { |
| let () = filter_fidl!(filter.enable(true), "error enabling filter")?; |
| info!("successfully enabled filter"); |
| } |
| FilterEnum::Disable(_) => { |
| let () = filter_fidl!(filter.enable(false), "error disabling filter")?; |
| info!("successfully disabled filter"); |
| } |
| FilterEnum::IsEnabled(_) => { |
| let is_enabled = filter.is_enabled().await.context("FIDL error")?; |
| println!("{:?}", is_enabled); |
| } |
| FilterEnum::GetRules(_) => { |
| let (rules, generation) = |
| filter_fidl!(filter.get_rules(), "error getting filter rules")?; |
| println!("{:?} (generation {})", rules, generation); |
| } |
| FilterEnum::SetRules(FilterSetRules { rules }) => { |
| let (_cur_rules, generation) = |
| filter_fidl!(filter.get_rules(), "error getting filter rules")?; |
| let mut rules = netfilter::parser::parse_str_to_rules(&rules)?; |
| let () = filter_fidl!( |
| filter.update_rules(&mut rules.iter_mut(), generation), |
| "error setting filter rules" |
| )?; |
| info!("successfully set filter rules"); |
| } |
| FilterEnum::GetNatRules(_) => { |
| let (rules, generation) = |
| filter_fidl!(filter.get_nat_rules(), "error getting NAT rules")?; |
| println!("{:?} (generation {})", rules, generation); |
| } |
| FilterEnum::SetNatRules(FilterSetNatRules { rules }) => { |
| let (_cur_rules, generation) = |
| filter_fidl!(filter.get_nat_rules(), "error getting NAT rules")?; |
| let mut rules = netfilter::parser::parse_str_to_nat_rules(&rules)?; |
| let () = filter_fidl!( |
| filter.update_nat_rules(&mut rules.iter_mut(), generation), |
| "error setting NAT rules" |
| )?; |
| info!("successfully set NAT rules"); |
| } |
| FilterEnum::GetRdrRules(_) => { |
| let (rules, generation) = |
| filter_fidl!(filter.get_rdr_rules(), "error getting RDR rules")?; |
| println!("{:?} (generation {})", rules, generation); |
| } |
| FilterEnum::SetRdrRules(FilterSetRdrRules { rules }) => { |
| let (_cur_rules, generation) = |
| filter_fidl!(filter.get_rdr_rules(), "error getting RDR rules")?; |
| let mut rules = netfilter::parser::parse_str_to_rdr_rules(&rules)?; |
| let () = filter_fidl!( |
| filter.update_rdr_rules(&mut rules.iter_mut(), generation), |
| "error setting RDR rules" |
| )?; |
| info!("successfully set RDR rules"); |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn do_ip_fwd(cmd: opts::IpFwdEnum, stack: StackProxy) -> Result<(), Error> { |
| match cmd { |
| IpFwdEnum::Enable(IpFwdEnable {}) => { |
| let () = stack |
| .enable_ip_forwarding() |
| .await |
| .context("fuchsia.net.stack/Stack.EnableIpForwarding FIDL error")?; |
| info!("Enabled IP forwarding"); |
| } |
| IpFwdEnum::Disable(IpFwdDisable {}) => { |
| let () = stack |
| .disable_ip_forwarding() |
| .await |
| .context("fuchsia.net.stack/Stack.DisableIpForwarding FIDL error")?; |
| info!("Disabled IP forwarding"); |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn do_log(cmd: opts::LogEnum, log: LogProxy) -> Result<(), Error> { |
| match cmd { |
| LogEnum::SetLevel(LogSetLevel { log_level }) => { |
| let () = stack_fidl!(log.set_log_level(log_level.into()), "error setting log level")?; |
| info!("log level set to {:?}", log_level); |
| } |
| LogEnum::SetPackets(LogSetPackets { enabled }) => { |
| let () = log.set_log_packets(enabled).await.context("error setting log packets")?; |
| info!("log packets set to {:?}", enabled); |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn do_stat(cmd: opts::StatEnum) -> Result<(), Error> { |
| match cmd { |
| StatEnum::Show(_) => { |
| let mut entries = Vec::new(); |
| let globs = [ |
| "/hub", // accessed in "sys" realm |
| "/hub/r/sys/*", // accessed in "app" realm |
| ] |
| .iter() |
| .map(|prefix| { |
| format!( |
| "{}/c/netstack.cmx/*/out/diagnostics/counters/{}", |
| prefix, |
| <inspect::InspectMarker as fidl::endpoints::ServiceMarker>::NAME |
| ) |
| }) |
| .collect::<Vec<_>>(); |
| globs.iter().try_for_each(|pattern| { |
| Ok::<_, glob::PatternError>(entries.extend(glob(pattern)?)) |
| })?; |
| if entries.is_empty() { |
| let () = Err(format_err!("failed to find netstack counters in {:?}", globs))?; |
| } |
| let multiple = entries.len() > 1; |
| for (i, entry) in entries.into_iter().enumerate() { |
| let path = entry?; |
| if multiple { |
| if i > 0 { |
| println!(); |
| } |
| // Show the stats path for distinction. |
| println!("Stats from {}", path.display()); |
| } |
| |
| let path = path |
| .to_str() |
| .ok_or_else(|| format_err!("failed to convert {} to str", path.display()))?; |
| let object = inspect_fidl_load::load_hierarchy_from_path(path).await?; |
| |
| let mut t = Table::new(); |
| t.set_format(format::FormatBuilder::new().padding(2, 2).build()); |
| t.set_titles(row!["Packet Count", "Classification"]); |
| let () = visit_inspect_object(&mut t, "", &object); |
| let _lines_printed: usize = t.printstd(); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn do_metric(cmd: opts::MetricEnum, netstack: NetstackProxy) -> Result<(), Error> { |
| match cmd { |
| MetricEnum::Set(MetricSet { id, metric }) => { |
| let result = netstack.set_interface_metric(id, metric).await?; |
| if result.status != fidl_fuchsia_netstack::Status::Ok { |
| Err(anyhow::anyhow!("{:?}: {}", result.status, result.message)) |
| } else { |
| info!("interface {} metric set to {}", id, metric); |
| Ok(()) |
| } |
| } |
| } |
| } |
| |
| async fn do_dhcp(cmd: opts::DhcpEnum, netstack: NetstackProxy) -> Result<(), Error> { |
| let (dhcp, server_end) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_net_dhcp::ClientMarker>()?; |
| match cmd { |
| DhcpEnum::Start(DhcpStart { id }) => { |
| let () = netstack |
| .get_dhcp_client(id, server_end) |
| .await? |
| .map_err(fuchsia_zircon::Status::from_raw)?; |
| let () = dhcp.start().await?.map_err(fuchsia_zircon::Status::from_raw)?; |
| info!("dhcp client started on interface {}", id); |
| } |
| DhcpEnum::Stop(DhcpStop { id }) => { |
| let () = netstack |
| .get_dhcp_client(id, server_end) |
| .await? |
| .map_err(fuchsia_zircon::Status::from_raw)?; |
| let () = dhcp.stop().await?.map_err(fuchsia_zircon::Status::from_raw)?; |
| info!("dhcp client stopped on interface {}", id); |
| } |
| } |
| Ok(()) |
| } |
| |
| fn visit_inspect_object( |
| t: &mut Table, |
| prefix: &str, |
| fuchsia_inspect::reader::DiagnosticsHierarchy { name: _, properties, children, missing: _ }: &fuchsia_inspect::reader::DiagnosticsHierarchy, |
| ) { |
| use fuchsia_inspect::reader::Property::*; |
| |
| for property in properties { |
| let (key, value) = match property { |
| Int(key, value) => (key, value.to_string()), |
| Uint(key, value) => (key, value.to_string()), |
| Double(key, value) => (key, value.to_string()), |
| String(_, _) |
| | Bytes(_, _) |
| | Bool(_, _) |
| | DoubleArray(_, _) |
| | IntArray(_, _) |
| | UintArray(_, _) |
| | StringList(_, _) => continue, |
| }; |
| let () = add_row(t, row![r->value, format!("{}{}", prefix, key)]); |
| } |
| for child in children { |
| let prefix = format!("{}{}/", prefix, child.name); |
| visit_inspect_object(t, &prefix, child); |
| } |
| } |
| |
| async fn do_neigh(cmd: opts::NeighEnum) -> Result<(), Error> { |
| match cmd { |
| NeighEnum::Add(NeighAdd { interface, ip, mac }) => { |
| let controller = connect_to_service::<neighbor::ControllerMarker>() |
| .context("failed to connect to neighbor controller")?; |
| let () = do_neigh_add(interface, ip.into(), mac.into(), controller) |
| .await |
| .context("failed during neigh add command")?; |
| info!("Added entry ({}, {}) for interface {}", ip, mac, interface); |
| } |
| NeighEnum::Clear(NeighClear { interface, ip_version }) => { |
| let controller = connect_to_service::<neighbor::ControllerMarker>() |
| .context("failed to connect to neighbor controller")?; |
| let () = do_neigh_clear(interface, ip_version, controller) |
| .await |
| .context("failed during neigh clear command")?; |
| info!("Cleared entries for interface {}", interface); |
| } |
| NeighEnum::Del(NeighDel { interface, ip }) => { |
| let controller = connect_to_service::<neighbor::ControllerMarker>() |
| .context("failed to connect to neighbor controller")?; |
| let () = do_neigh_del(interface, ip.into(), controller) |
| .await |
| .context("failed during neigh del command")?; |
| info!("Deleted entry {} for interface {}", ip, interface); |
| } |
| NeighEnum::List(NeighList {}) => { |
| let () = print_neigh_entries(false /* watch_for_changes */) |
| .await |
| .context("error listing neighbor entries")?; |
| } |
| NeighEnum::Watch(NeighWatch {}) => { |
| let () = print_neigh_entries(true /* watch_for_changes */) |
| .await |
| .context("error watching for changes to the neighbor table")?; |
| } |
| NeighEnum::Config(NeighConfig { neigh_config_cmd }) => match neigh_config_cmd { |
| NeighConfigEnum::Get(NeighGetConfig { interface, ip_version }) => { |
| let view = connect_to_service::<neighbor::ViewMarker>() |
| .context("failed to connect to neighbor view")?; |
| let () = print_neigh_config(interface, ip_version, view) |
| .await |
| .context("failed during neigh config get command")?; |
| } |
| NeighConfigEnum::Update(NeighUpdateConfig { |
| interface, |
| ip_version, |
| base_reachable_time, |
| learn_base_reachable_time, |
| min_random_factor, |
| max_random_factor, |
| retransmit_timer, |
| learn_retransmit_timer, |
| delay_first_probe_time, |
| max_multicast_probes, |
| max_unicast_probes, |
| max_anycast_delay_time, |
| max_reachability_confirmations, |
| }) => { |
| let updates = neighbor::UnreachabilityConfig { |
| base_reachable_time, |
| learn_base_reachable_time, |
| min_random_factor, |
| max_random_factor, |
| retransmit_timer, |
| learn_retransmit_timer, |
| delay_first_probe_time, |
| max_multicast_probes, |
| max_unicast_probes, |
| max_anycast_delay_time, |
| max_reachability_confirmations, |
| ..neighbor::UnreachabilityConfig::EMPTY |
| }; |
| let controller = connect_to_service::<neighbor::ControllerMarker>() |
| .context("failed to connect to neighbor controller")?; |
| let () = update_neigh_config(interface, ip_version, updates, controller) |
| .await |
| .context("failed during neigh config update command")?; |
| info!("Updated config for interface {}", interface); |
| } |
| }, |
| } |
| Ok(()) |
| } |
| |
| async fn do_neigh_add( |
| interface: u64, |
| neighbor: net::IpAddress, |
| mac: net::MacAddress, |
| controller: neighbor::ControllerProxy, |
| ) -> Result<(), Error> { |
| controller |
| .add_entry(interface, &mut neighbor.into(), &mut mac.into()) |
| .await |
| .context("FIDL error adding neighbor entry")? |
| .map_err(zx::Status::from_raw) |
| .context("error adding neighbor entry") |
| } |
| |
| async fn do_neigh_clear( |
| interface: u64, |
| ip_version: net::IpVersion, |
| controller: neighbor::ControllerProxy, |
| ) -> Result<(), Error> { |
| controller |
| .clear_entries(interface, ip_version) |
| .await |
| .context("FIDL error clearing neighbor table")? |
| .map_err(zx::Status::from_raw) |
| .context("error clearing neighbor table") |
| } |
| |
| async fn do_neigh_del( |
| interface: u64, |
| neighbor: net::IpAddress, |
| controller: neighbor::ControllerProxy, |
| ) -> Result<(), Error> { |
| controller |
| .remove_entry(interface, &mut neighbor.into()) |
| .await |
| .context("FIDL error removing neighbor entry")? |
| .map_err(zx::Status::from_raw) |
| .context("error removing neighbor entry") |
| } |
| |
| async fn print_neigh_entries(watch_for_changes: bool) -> Result<(), Error> { |
| let view = connect_to_service::<neighbor::ViewMarker>() |
| .context("failed to connect to neighbor view")?; |
| let (it_client, it_server) = |
| fidl::endpoints::create_endpoints::<neighbor::EntryIteratorMarker>() |
| .context("error creating channel for entry iterator")?; |
| let it = it_client.into_proxy().context("error creating proxy to entry iterator")?; |
| |
| let () = view |
| .open_entry_iterator(it_server, neighbor::EntryIteratorOptions::EMPTY) |
| .context("error opening a connection to the entry iterator")?; |
| |
| neigh_entry_stream(it, watch_for_changes) |
| .map_ok(|item| { |
| write_neigh_entry(&mut std::io::stdout(), item, watch_for_changes) |
| .context("error writing entry") |
| }) |
| .try_fold((), |(), r| futures::future::ready(r)) |
| .await |
| } |
| |
| async fn print_neigh_config( |
| interface: u64, |
| version: net::IpVersion, |
| view: neighbor::ViewProxy, |
| ) -> Result<(), Error> { |
| let config = view |
| .get_unreachability_config(interface, version) |
| .await |
| .context("get_unreachability_config FIDL error")? |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .context("get_unreachability_config failed")?; |
| |
| println!("{:#?}", config); |
| Ok(()) |
| } |
| |
| async fn update_neigh_config( |
| interface: u64, |
| version: net::IpVersion, |
| updates: neighbor::UnreachabilityConfig, |
| controller: neighbor::ControllerProxy, |
| ) -> Result<(), Error> { |
| controller |
| .update_unreachability_config(interface, version, updates) |
| .await |
| .context("update_unreachability_config FIDL error")? |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .context("update_unreachability_config failed") |
| } |
| |
| fn neigh_entry_stream( |
| iterator: neighbor::EntryIteratorProxy, |
| watch_for_changes: bool, |
| ) -> impl futures::Stream<Item = Result<neighbor::EntryIteratorItem, Error>> { |
| futures::stream::try_unfold(iterator, |iterator| { |
| iterator |
| .get_next() |
| .map_ok(|items| Some((items, iterator))) |
| .map(|r| r.context("error getting items from iterator")) |
| }) |
| .map_ok(|items| futures::stream::iter(items.into_iter().map(Ok))) |
| .try_flatten() |
| .take_while(move |item| { |
| futures::future::ready(item.as_ref().map_or(false, |item| { |
| if let neighbor::EntryIteratorItem::Idle(neighbor::IdleEvent {}) = item { |
| watch_for_changes |
| } else { |
| true |
| } |
| })) |
| }) |
| } |
| |
| fn write_neigh_entry<W: std::io::Write>( |
| f: &mut W, |
| item: neighbor::EntryIteratorItem, |
| watch_for_changes: bool, |
| ) -> Result<(), std::io::Error> { |
| match item { |
| neighbor::EntryIteratorItem::Existing(entry) => { |
| if watch_for_changes { |
| writeln!(f, "EXISTING | {}", neighbor_ext::Entry::from(entry)) |
| } else { |
| writeln!(f, "{}", neighbor_ext::Entry::from(entry)) |
| } |
| } |
| neighbor::EntryIteratorItem::Idle(neighbor::IdleEvent {}) => writeln!(f, "IDLE"), |
| neighbor::EntryIteratorItem::Added(entry) => { |
| writeln!(f, "ADDED | {}", neighbor_ext::Entry::from(entry)) |
| } |
| neighbor::EntryIteratorItem::Changed(entry) => { |
| writeln!(f, "CHANGED | {}", neighbor_ext::Entry::from(entry)) |
| } |
| neighbor::EntryIteratorItem::Removed(entry) => { |
| writeln!(f, "REMOVED | {}", neighbor_ext::Entry::from(entry)) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use fidl_fuchsia_net as net; |
| use fuchsia_async::{self as fasync, TimeoutExt as _}; |
| use futures::prelude::*; |
| use net_declare::{fidl_mac, fidl_subnet}; |
| use {super::*, fidl_fuchsia_net_stack::*, fidl_fuchsia_netstack::*}; |
| |
| const SUBNET_V4: net::Subnet = fidl_subnet!("192.168.0.1/32"); |
| const SUBNET_V6: net::Subnet = fidl_subnet!("fd00::1/128"); |
| |
| const MAC_1: net::MacAddress = fidl_mac!("01:02:03:04:05:06"); |
| const MAC_2: net::MacAddress = fidl_mac!("02:03:04:05:06:07"); |
| |
| fn get_fake_interface(id: u64, name: &str) -> InterfaceInfo { |
| InterfaceInfo { |
| id, |
| properties: InterfaceProperties { |
| name: name.to_string(), |
| topopath: "loopback".to_string(), |
| filepath: "[none]".to_string(), |
| mac: None, |
| mtu: 65536, |
| features: zx_eth::Features::Loopback, |
| administrative_status: AdministrativeStatus::Enabled, |
| physical_status: PhysicalStatus::Up, |
| addresses: vec![], |
| }, |
| } |
| } |
| |
| fn get_fake_interfaces() -> Vec<InterfaceInfo> { |
| vec![ |
| get_fake_interface(1, "lo"), |
| get_fake_interface(10, "eth001"), |
| get_fake_interface(20, "eth002"), |
| get_fake_interface(30, "eth003"), |
| get_fake_interface(100, "wlan001"), |
| get_fake_interface(200, "wlan002"), |
| get_fake_interface(300, "wlan003"), |
| ] |
| } |
| |
| fn shortlist_interfaces_by_nicid(name_pattern: &str) -> Vec<u64> { |
| let mut interfaces = get_fake_interfaces(); |
| let () = shortlist_interfaces(name_pattern, &mut interfaces); |
| interfaces.into_iter().map(|i| i.id).collect() |
| } |
| |
| #[test] |
| fn test_shortlist_interfaces() { |
| assert_eq!(vec![1, 10, 20, 30, 100, 200, 300], shortlist_interfaces_by_nicid("")); |
| assert_eq!(vec![0_u64; 0], shortlist_interfaces_by_nicid("no such thing")); |
| |
| assert_eq!(vec![1], shortlist_interfaces_by_nicid("lo")); |
| assert_eq!(vec![10, 20, 30], shortlist_interfaces_by_nicid("eth")); |
| assert_eq!(vec![10, 20, 30], shortlist_interfaces_by_nicid("th")); |
| assert_eq!(vec![100, 200, 300], shortlist_interfaces_by_nicid("wlan")); |
| assert_eq!(vec![10, 100], shortlist_interfaces_by_nicid("001")); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_if_del_addr() { |
| async fn next_request( |
| requests: &mut StackRequestStream, |
| ) -> (u64, net::Subnet, StackDelInterfaceAddressResponder) { |
| requests |
| .try_next() |
| .await |
| .expect("del interface address FIDL error") |
| .expect("request stream should not have ended") |
| .into_del_interface_address() |
| .expect("request should be of type DelInterfaceAddress") |
| } |
| |
| let (stack, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<StackMarker>().unwrap(); |
| let (netstack, _) = fidl::endpoints::create_proxy::<NetstackMarker>().unwrap(); |
| |
| // Make the first request. |
| let succeeds = do_if( |
| IfEnum::Addr(IfAddr { |
| addr_cmd: IfAddrEnum::Del(IfAddrDel { |
| id: 1, |
| addr: net_ext::IpAddress::from(SUBNET_V4.addr).to_string(), |
| prefix: None, // The prefix should be set to the default of 32 for IPv4. |
| }), |
| }), |
| &stack, |
| &netstack, |
| ); |
| let success_response = async { |
| // Verify that the first request is as expected and return OK. |
| let (id, addr, responder) = next_request(&mut requests).await; |
| assert_eq!(id, 1); |
| assert_eq!(addr, SUBNET_V4); |
| responder.send(&mut Ok(())).map_err(anyhow::Error::new) |
| }; |
| let ((), ()) = futures::future::try_join(success_response, succeeds) |
| .await |
| .expect("do_if should succeed"); |
| |
| // Make the second request. |
| let fails = do_if( |
| IfEnum::Addr(IfAddr { |
| addr_cmd: IfAddrEnum::Del(IfAddrDel { |
| id: 2, |
| addr: net_ext::IpAddress::from(SUBNET_V6.addr).to_string(), |
| prefix: None, // The prefix should be set to the default of 128 for IPv6. |
| }), |
| }), |
| &stack, |
| &netstack, |
| ); |
| let fail_response = async { |
| // Verify that the second request is as expected and return a NotFound error. |
| let (id, addr, responder) = next_request(&mut requests).await; |
| assert_eq!(id, 2); |
| assert_eq!(addr, SUBNET_V6); |
| responder |
| .send(&mut Err(fidl_fuchsia_net_stack::Error::NotFound)) |
| .map_err(anyhow::Error::new) |
| }; |
| let (fails_response, fails) = futures::future::join(fail_response, fails).await; |
| let () = fails_response.expect("responder.send should succeed"); |
| let fidl_err = fails.expect_err("do_if should fail"); |
| let fidl_fuchsia_net_stack_ext::NetstackError(underlying_error) = fidl_err |
| .root_cause() |
| .downcast_ref::<fidl_fuchsia_net_stack_ext::NetstackError>() |
| .expect("fidl_err should downcast to NetstackError"); |
| assert_eq!(*underlying_error, fidl_fuchsia_net_stack::Error::NotFound); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_metric_set() { |
| async fn next_request( |
| requests: &mut NetstackRequestStream, |
| ) -> (u32, u32, NetstackSetInterfaceMetricResponder) { |
| requests |
| .try_next() |
| .await |
| .expect("set interface metric FIDL error") |
| .expect("request stream should not have ended") |
| .into_set_interface_metric() |
| .expect("request should be of type SetInterfaceMetric") |
| } |
| |
| let (netstack, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<NetstackMarker>().unwrap(); |
| |
| // both test values have been arbitrarily selected |
| let expected_id = 1; |
| let expected_metric = 64; |
| let succeeds = do_metric( |
| MetricEnum::Set(MetricSet { id: expected_id, metric: expected_metric }), |
| netstack, |
| ); |
| let response = async move { |
| // Verify that the request is as expected and return OK. |
| let (id, metric, responder) = next_request(&mut requests).await; |
| assert_eq!(id, expected_id); |
| assert_eq!(metric, expected_metric); |
| responder |
| .send(&mut NetErr { status: Status::Ok, message: String::from("") }) |
| .map_err(anyhow::Error::new) |
| }; |
| let ((), ()) = |
| futures::future::try_join(succeeds, response).await.expect("metric set should succeed"); |
| } |
| |
| async fn test_do_dhcp(cmd: DhcpEnum) { |
| let (netstack, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<NetstackMarker>().unwrap(); |
| let op = do_dhcp(cmd.clone(), netstack.clone()); |
| let op_succeeds = async move { |
| let (received_id, dhcp_requests, netstack_responder) = requests |
| .try_next() |
| .await |
| .expect("get dhcp client FIDL error") |
| .expect("request stream should not have ended") |
| .into_get_dhcp_client() |
| .expect("request should be of type GetDhcpClient"); |
| let mut dhcp_requests = |
| dhcp_requests.into_stream().expect("should convert to request stream"); |
| let () = netstack_responder |
| .send(&mut Ok(())) |
| .expect("netstack_responder.send should succeed"); |
| match cmd { |
| DhcpEnum::Start(DhcpStart { id: expected_id }) => { |
| assert_eq!(received_id, expected_id); |
| dhcp_requests |
| .try_next() |
| .await |
| .expect("start FIDL error") |
| .expect("request stream should not have ended") |
| .into_start() |
| .expect("request should be of type Start") |
| .send(&mut Ok(())) |
| .map_err(anyhow::Error::new) |
| } |
| DhcpEnum::Stop(DhcpStop { id: expected_id }) => { |
| assert_eq!(received_id, expected_id); |
| dhcp_requests |
| .try_next() |
| .await |
| .expect("stop FIDL error") |
| .expect("request stream should not have ended") |
| .into_stop() |
| .expect("request should be of type Stop") |
| .send(&mut Ok(())) |
| .map_err(anyhow::Error::new) |
| } |
| } |
| }; |
| let ((), ()) = |
| futures::future::try_join(op, op_succeeds).await.expect("dhcp command should succeed"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_dhcp_start() { |
| let () = test_do_dhcp(DhcpEnum::Start(DhcpStart { id: 1 })).await; |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_dhcp_stop() { |
| let () = test_do_dhcp(DhcpEnum::Stop(DhcpStop { id: 1 })).await; |
| } |
| |
| async fn test_modify_route(cmd: RouteEnum) { |
| let (netstack, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<NetstackMarker>().unwrap(); |
| let op = do_route(cmd.clone(), netstack.clone()); |
| let op_succeeds = async move { |
| let (route_table_requests, netstack_responder) = requests |
| .try_next() |
| .await |
| .expect("start route table transaction FIDL error") |
| .expect("request stream should not have ended") |
| .into_start_route_table_transaction() |
| .expect("request should be of type StartRouteTableTransaction"); |
| let mut route_table_requests = |
| route_table_requests.into_stream().expect("should convert to request stream"); |
| let () = netstack_responder |
| .send(fuchsia_zircon::Status::OK.into_raw()) |
| .expect("netstack_responder.send should succeed"); |
| let () = match cmd { |
| RouteEnum::List(RouteList {}) => { |
| panic!("test_modify_route should not take a List command") |
| } |
| RouteEnum::Add(route) => { |
| let expected_entry = route.into(); |
| let (entry, responder) = route_table_requests |
| .try_next() |
| .await |
| .expect("add route FIDL error") |
| .expect("request stream should not have ended") |
| .into_add_route() |
| .expect("request should be of type AddRoute"); |
| assert_eq!(entry, expected_entry); |
| responder.send(fuchsia_zircon::Status::OK.into_raw()) |
| } |
| RouteEnum::Del(route) => { |
| let expected_entry = route.into(); |
| let (entry, responder) = route_table_requests |
| .try_next() |
| .await |
| .expect("del route FIDL error") |
| .expect("request stream should not have ended") |
| .into_del_route() |
| .expect("request should be of type DelRoute"); |
| assert_eq!(entry, expected_entry); |
| responder.send(fuchsia_zircon::Status::OK.into_raw()) |
| } |
| }?; |
| Ok(()) |
| }; |
| let ((), ()) = |
| futures::future::try_join(op, op_succeeds).await.expect("dhcp command should succeed"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_route_add() { |
| // Test arguments have been arbitrarily selected. |
| let () = test_modify_route(RouteEnum::Add(RouteAdd { |
| destination: std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 0)), |
| netmask: std::net::IpAddr::V4(std::net::Ipv4Addr::new(255, 255, 255, 0)), |
| gateway: None, |
| nicid: 2, |
| metric: 100, |
| })) |
| .await; |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_route_del() { |
| // Test arguments have been arbitrarily selected. |
| let () = test_modify_route(RouteEnum::Del(RouteDel { |
| destination: std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 0)), |
| netmask: std::net::IpAddr::V4(std::net::Ipv4Addr::new(255, 255, 255, 0)), |
| gateway: None, |
| nicid: 2, |
| metric: 100, |
| })) |
| .await; |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_bridge() { |
| let (stack, _) = fidl::endpoints::create_proxy::<StackMarker>().unwrap(); |
| let (netstack, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<NetstackMarker>().unwrap(); |
| // interface id test values have been selected arbitrarily |
| let bridge_ifs = vec![1, 2, 3]; |
| let bridge_id = 4; |
| let bridge = do_if(IfEnum::Bridge(IfBridge { ids: bridge_ifs.clone() }), &stack, &netstack); |
| let bridge_succeeds = async move { |
| let (requested_ifs, netstack_responder) = requests |
| .try_next() |
| .await |
| .expect("bridge_interfaces FIDL error") |
| .expect("request stream should not have ended") |
| .into_bridge_interfaces() |
| .expect("request should be of type BridgeInterfaces"); |
| assert_eq!(requested_ifs, bridge_ifs); |
| let () = netstack_responder |
| .send( |
| &mut fidl_fuchsia_netstack::NetErr { |
| status: fidl_fuchsia_netstack::Status::Ok, |
| message: String::from(""), |
| }, |
| bridge_id, |
| ) |
| .expect("responder.send should succeed"); |
| Ok(()) |
| }; |
| let ((), ()) = futures::future::try_join(bridge, bridge_succeeds) |
| .await |
| .expect("if bridge should succeed"); |
| } |
| |
| async fn test_get_neigh_entries( |
| watch_for_changes: bool, |
| batches: Vec<Vec<neighbor::EntryIteratorItem>>, |
| want: String, |
| ) { |
| let (it, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<neighbor::EntryIteratorMarker>().unwrap(); |
| |
| let server = async { |
| for mut items in batches { |
| let responder = requests |
| .try_next() |
| .await |
| .expect("neigh FIDL error") |
| .expect("request stream should not have ended") |
| .into_get_next() |
| .expect("request should be of type GetNext"); |
| let () = |
| responder.send(&mut items.iter_mut()).expect("responder.send should succeed"); |
| } |
| } |
| .on_timeout(std::time::Duration::from_secs(60), || panic!("server responder timed out")); |
| |
| let client = async { |
| let mut stream = neigh_entry_stream(it, watch_for_changes); |
| |
| let item_to_string = |item| { |
| let mut buf = Vec::new(); |
| let () = write_neigh_entry(&mut buf, item, watch_for_changes) |
| .expect("write_neigh_entry should succeed"); |
| String::from_utf8(buf).expect("string should be UTF-8") |
| }; |
| |
| // Check each string sent by get_neigh_entries |
| for want_line in want.lines() { |
| let got = stream |
| .next() |
| .await |
| .map(|item| item_to_string(item.expect("neigh_entry_stream should succeed"))); |
| assert_eq!(got, Some(format!("{}\n", want_line))); |
| } |
| |
| // When listing entries, the sender should close after sending all existing entries. |
| if !watch_for_changes { |
| match stream.next().await { |
| Some(Ok(item)) => { |
| panic!("unexpected item from stream: {}", item_to_string(item)) |
| } |
| Some(Err(err)) => panic!("unexpected error from stream: {}", err), |
| None => {} |
| } |
| } |
| }; |
| |
| let ((), ()) = futures::future::join(client, server).await; |
| } |
| |
| async fn test_neigh_none(watch_for_changes: bool, want: String) { |
| test_get_neigh_entries( |
| watch_for_changes, |
| vec![vec![neighbor::EntryIteratorItem::Idle(neighbor::IdleEvent {})]], |
| want, |
| ) |
| .await |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_list_none() { |
| test_neigh_none(false /* watch_for_changes */, "".to_string()).await |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_watch_none() { |
| test_neigh_none(true /* watch_for_changes */, "IDLE".to_string()).await |
| } |
| |
| async fn test_neigh_one(watch_for_changes: bool, want: fn(neighbor_ext::Entry) -> String) { |
| fn new_entry(updated_at: i64) -> neighbor::Entry { |
| neighbor::Entry { |
| interface: Some(1), |
| neighbor: Some(SUBNET_V4.addr), |
| state: Some(neighbor::EntryState::Reachable), |
| mac: Some(MAC_1), |
| updated_at: Some(updated_at), |
| ..neighbor::Entry::EMPTY |
| } |
| } |
| |
| let now = fuchsia_runtime::utc_time(); |
| let updated_at = { |
| let past = now - zx::Duration::from_minutes(1); |
| past.into_nanos() |
| }; |
| |
| test_get_neigh_entries( |
| watch_for_changes, |
| vec![vec![ |
| neighbor::EntryIteratorItem::Existing(new_entry(updated_at)), |
| neighbor::EntryIteratorItem::Idle(neighbor::IdleEvent {}), |
| ]], |
| want(neighbor_ext::Entry::from(new_entry(updated_at))), |
| ) |
| .await |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_list_one() { |
| test_neigh_one(false /* watch_for_changes */, |entry| format!("{}\n", entry)).await |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_watch_one() { |
| test_neigh_one(true /* watch_for_changes */, |entry| { |
| format!( |
| "EXISTING | {}\n\ |
| IDLE\n", |
| entry |
| ) |
| }) |
| .await |
| } |
| |
| async fn test_neigh_many( |
| watch_for_changes: bool, |
| want: fn(neighbor_ext::Entry, neighbor_ext::Entry) -> String, |
| ) { |
| fn new_entry(ip: net::IpAddress, mac: net::MacAddress, updated_at: i64) -> neighbor::Entry { |
| neighbor::Entry { |
| interface: Some(1), |
| neighbor: Some(ip), |
| state: Some(neighbor::EntryState::Reachable), |
| mac: Some(mac), |
| updated_at: Some(updated_at), |
| ..neighbor::Entry::EMPTY |
| } |
| } |
| |
| let now = fuchsia_runtime::utc_time(); |
| let updated_at = { |
| let past = now - zx::Duration::from_minutes(1); |
| past.into_nanos() |
| }; |
| let offset = zx::Duration::from_minutes(1).into_nanos(); |
| |
| test_get_neigh_entries( |
| watch_for_changes, |
| vec![vec![ |
| neighbor::EntryIteratorItem::Existing(new_entry(SUBNET_V4.addr, MAC_1, updated_at)), |
| neighbor::EntryIteratorItem::Existing(new_entry( |
| SUBNET_V6.addr, |
| MAC_2, |
| updated_at - offset, |
| )), |
| neighbor::EntryIteratorItem::Idle(neighbor::IdleEvent {}), |
| ]], |
| want( |
| neighbor_ext::Entry::from(new_entry(SUBNET_V4.addr, MAC_1, updated_at)), |
| neighbor_ext::Entry::from(new_entry(SUBNET_V6.addr, MAC_2, updated_at - offset)), |
| ), |
| ) |
| .await |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_list_many() { |
| test_neigh_many(false /* watch_for_changes */, |a, b| format!("{}\n{}\n", a, b)).await |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_watch_many() { |
| test_neigh_many(true /* watch_for_changes */, |a, b| { |
| format!( |
| "EXISTING | {}\n\ |
| EXISTING | {}\n\ |
| IDLE\n", |
| a, b |
| ) |
| }) |
| .await |
| } |
| |
| const INTERFACE_ID: u64 = 1; |
| const IP_VERSION: net::IpVersion = net::IpVersion::V4; |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_add() { |
| let (controller, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<neighbor::ControllerMarker>().unwrap(); |
| let neigh = do_neigh_add(INTERFACE_ID, SUBNET_V4.addr, MAC_1, controller); |
| let neigh_succeeds = async { |
| let (got_interface_id, got_ip_address, got_mac, responder) = requests |
| .try_next() |
| .await |
| .expect("neigh FIDL error") |
| .expect("request stream should not have ended") |
| .into_add_entry() |
| .expect("request should be of type AddEntry"); |
| assert_eq!(got_interface_id, INTERFACE_ID); |
| assert_eq!(got_ip_address, SUBNET_V4.addr); |
| assert_eq!(got_mac, MAC_1); |
| let () = responder.send(&mut Ok(())).expect("responder.send should succeed"); |
| Ok(()) |
| }; |
| let ((), ()) = futures::future::try_join(neigh, neigh_succeeds) |
| .await |
| .expect("neigh add should succeed"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_clear() { |
| let (controller, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<neighbor::ControllerMarker>().unwrap(); |
| let neigh = do_neigh_clear(INTERFACE_ID, IP_VERSION, controller); |
| let neigh_succeeds = async { |
| let (got_interface_id, got_ip_version, responder) = requests |
| .try_next() |
| .await |
| .expect("neigh FIDL error") |
| .expect("request stream should not have ended") |
| .into_clear_entries() |
| .expect("request should be of type ClearEntries"); |
| assert_eq!(got_interface_id, INTERFACE_ID); |
| assert_eq!(got_ip_version, IP_VERSION); |
| let () = responder.send(&mut Ok(())).expect("responder.send should succeed"); |
| Ok(()) |
| }; |
| let ((), ()) = futures::future::try_join(neigh, neigh_succeeds) |
| .await |
| .expect("neigh clear should succeed"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_del() { |
| let (controller, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<neighbor::ControllerMarker>().unwrap(); |
| let neigh = do_neigh_del(INTERFACE_ID, SUBNET_V4.addr, controller); |
| let neigh_succeeds = async { |
| let (got_interface_id, got_ip_address, responder) = requests |
| .try_next() |
| .await |
| .expect("neigh FIDL error") |
| .expect("request stream should not have ended") |
| .into_remove_entry() |
| .expect("request should be of type RemoveEntry"); |
| assert_eq!(got_interface_id, INTERFACE_ID); |
| assert_eq!(got_ip_address, SUBNET_V4.addr); |
| let () = responder.send(&mut Ok(())).expect("responder.send should succeed"); |
| Ok(()) |
| }; |
| let ((), ()) = futures::future::try_join(neigh, neigh_succeeds) |
| .await |
| .expect("neigh remove should succeed"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_config_get() { |
| let (view, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<neighbor::ViewMarker>() |
| .expect("creating a request stream and proxy for testing should succeed"); |
| let neigh = print_neigh_config(INTERFACE_ID, IP_VERSION, view); |
| let neigh_succeeds = async { |
| let (got_interface_id, got_ip_version, responder) = requests |
| .try_next() |
| .await |
| .expect("neigh FIDL error") |
| .expect("request stream should not have ended") |
| .into_get_unreachability_config() |
| .expect("request should be of type GetUnreachabilityConfig"); |
| assert_eq!(got_interface_id, INTERFACE_ID); |
| assert_eq!(got_ip_version, IP_VERSION); |
| let () = responder |
| .send(&mut Ok(neighbor::UnreachabilityConfig::EMPTY)) |
| .expect("responder.send should succeed"); |
| Ok(()) |
| }; |
| let ((), ()) = futures::future::try_join(neigh, neigh_succeeds) |
| .await |
| .expect("neigh config get should succeed"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_neigh_config_update() { |
| const CONFIG: neighbor::UnreachabilityConfig = neighbor::UnreachabilityConfig::EMPTY; |
| |
| let (controller, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<neighbor::ControllerMarker>() |
| .expect("creating a request stream and proxy for testing should succeed"); |
| let neigh = update_neigh_config( |
| INTERFACE_ID, |
| IP_VERSION, |
| neighbor::UnreachabilityConfig::EMPTY, |
| controller, |
| ); |
| let neigh_succeeds = async { |
| let (got_interface_id, got_ip_version, got_config, responder) = requests |
| .try_next() |
| .await |
| .expect("neigh FIDL error") |
| .expect("request stream should not have ended") |
| .into_update_unreachability_config() |
| .expect("request should be of type UpdateUnreachabilityConfig"); |
| assert_eq!(got_interface_id, INTERFACE_ID); |
| assert_eq!(got_ip_version, IP_VERSION); |
| assert_eq!(got_config, CONFIG); |
| let () = responder.send(&mut Ok(())).expect("responder.send should succeed"); |
| Ok(()) |
| }; |
| let ((), ()) = futures::future::try_join(neigh, neigh_succeeds) |
| .await |
| .expect("neigh config update should succeed"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_ip_fwd_enable() { |
| let (stack, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<netstack::StackMarker>() |
| .expect("creating a request stream and proxy for testing should succeed"); |
| let op = do_ip_fwd(IpFwdEnum::Enable(IpFwdEnable {}), stack); |
| let op_succeeds = async { |
| let responder = requests |
| .try_next() |
| .await |
| .expect("ip-fwd FIDL error") |
| .expect("request stream should not have ended") |
| .into_enable_ip_forwarding() |
| .expect("request should be of type EnableIpForwarding"); |
| let () = responder.send().expect("responder.send should succeed"); |
| Ok(()) |
| }; |
| let ((), ()) = |
| futures::future::try_join(op, op_succeeds).await.expect("fwd enable should succeed"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_ip_fwd_disable() { |
| let (stack, mut requests) = |
| fidl::endpoints::create_proxy_and_stream::<netstack::StackMarker>() |
| .expect("creating a request stream and proxy for testing should succeed"); |
| let op = do_ip_fwd(IpFwdEnum::Disable(IpFwdDisable {}), stack); |
| let op_succeeds = async { |
| let responder = requests |
| .try_next() |
| .await |
| .expect("fwd FIDL error") |
| .expect("request stream should not have ended") |
| .into_disable_ip_forwarding() |
| .expect("request should be of type DisableIpForwarding"); |
| let () = responder.send().expect("responder.send should succeed"); |
| Ok(()) |
| }; |
| let ((), ()) = |
| futures::future::try_join(op, op_succeeds).await.expect("fwd disable should succeed"); |
| } |
| } |