1mod capture;
6mod filter;
7mod opts;
8mod ser;
9
10use anyhow::{Context as _, Error, anyhow};
11use fidl_fuchsia_net_ext as fnet_ext;
12#[cfg(not(feature = "fdomain"))]
13use fidl_fuchsia_net_interfaces_ext as finterfaces_ext;
14#[cfg(feature = "fdomain")]
15use fidl_fuchsia_net_interfaces_ext_fdomain as finterfaces_ext;
16#[cfg(not(feature = "fdomain"))]
17use fidl_fuchsia_net_matchers_ext as fnet_matchers_ext;
18#[cfg(feature = "fdomain")]
19use fidl_fuchsia_net_matchers_ext_fdomain as fnet_matchers_ext;
20#[cfg(not(feature = "fdomain"))]
21use fidl_fuchsia_net_neighbor_ext as fneighbor_ext;
22#[cfg(feature = "fdomain")]
23use fidl_fuchsia_net_neighbor_ext_fdomain as fneighbor_ext;
24#[cfg(not(feature = "fdomain"))]
25use fidl_fuchsia_net_routes_ext as froutes_ext;
26#[cfg(feature = "fdomain")]
27use fidl_fuchsia_net_routes_ext_fdomain as froutes_ext;
28use fidl_fuchsia_net_stack_ext::{self as fstack_ext, FidlReturn as _};
29use flex_client::ProxyHasDomain;
30use flex_fuchsia_net as fnet;
31use flex_fuchsia_net_debug as fdebug;
32use flex_fuchsia_net_dhcp as fdhcp;
33use flex_fuchsia_net_filter as fnet_filter;
34use flex_fuchsia_net_filter_deprecated as ffilter_deprecated;
35use flex_fuchsia_net_interfaces as finterfaces;
36use flex_fuchsia_net_interfaces_admin as finterfaces_admin;
37use flex_fuchsia_net_name as fname;
38use flex_fuchsia_net_neighbor as fneighbor;
39use flex_fuchsia_net_resources as fnet_resources;
40use flex_fuchsia_net_root as froot;
41use flex_fuchsia_net_routes as froutes;
42use flex_fuchsia_net_stack as fstack;
43use futures::{FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _};
44use itertools::Itertools as _;
45use log::{info, warn};
46use net_types::SpecifiedAddr;
47use net_types::ip::{Ip, IpAddress as _, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Subnet};
48use netfilter::FidlReturn as _;
49use prettytable::{Row, Table, format, row};
50use ser::AddressAssignmentState;
51use serde_json::json;
52use serde_json::value::Value;
53use std::borrow::Cow;
54use std::collections::hash_map::HashMap;
55use std::convert::TryFrom as _;
56use std::iter::FromIterator as _;
57use std::num::NonZeroU64;
58use std::ops::Deref;
59use std::pin::pin;
60use std::str::FromStr as _;
61use writer::ToolIO as _;
62use zx_status as zx;
63
64pub use capture::DEFAULT_CAPTURE_NAME;
65pub use opts::{
66 Command, CommandEnum, UserFacingError, underlying_user_facing_error, user_facing_error,
67};
68
69macro_rules! filter_fidl {
70 ($method:expr, $context:expr) => {
71 $method.await.transform_result().context($context)
72 };
73}
74
75fn add_row(t: &mut Table, row: Row) {
76 let _: &mut Row = t.add_row(row);
77}
78
79#[async_trait::async_trait]
81pub trait ServiceConnector<S: flex_client::fidl::ProtocolMarker> {
82 async fn connect(&self) -> Result<S::Proxy, Error>;
84}
85
86pub trait CaptureDeps {
87 type OutputWriter: std::io::Write + Send + 'static;
88 fn create_output_writer(
89 &self,
90 path: &std::path::Path,
91 ) -> Result<Self::OutputWriter, anyhow::Error>;
92}
93
94pub trait NetCliDepsConnector:
99 ServiceConnector<fdebug::InterfacesMarker>
100 + ServiceConnector<froot::InterfacesMarker>
101 + ServiceConnector<froot::RoutesV4Marker>
102 + ServiceConnector<froot::RoutesV6Marker>
103 + ServiceConnector<froot::FilterMarker>
104 + ServiceConnector<fdhcp::Server_Marker>
105 + ServiceConnector<ffilter_deprecated::FilterMarker>
106 + ServiceConnector<finterfaces::StateMarker>
107 + ServiceConnector<finterfaces_admin::InstallerMarker>
108 + ServiceConnector<fneighbor::ControllerMarker>
109 + ServiceConnector<fneighbor::ViewMarker>
110 + ServiceConnector<fstack::LogMarker>
111 + ServiceConnector<fstack::StackMarker>
112 + ServiceConnector<froutes::StateV4Marker>
113 + ServiceConnector<froutes::StateV6Marker>
114 + ServiceConnector<fname::LookupMarker>
115 + ServiceConnector<fnet_filter::StateMarker>
116 + ServiceConnector<fdebug::PacketCaptureProviderMarker>
117{
118}
119
120impl<O> NetCliDepsConnector for O where
121 O: ServiceConnector<fdebug::InterfacesMarker>
122 + ServiceConnector<froot::InterfacesMarker>
123 + ServiceConnector<froot::RoutesV4Marker>
124 + ServiceConnector<froot::RoutesV6Marker>
125 + ServiceConnector<froot::FilterMarker>
126 + ServiceConnector<fdhcp::Server_Marker>
127 + ServiceConnector<ffilter_deprecated::FilterMarker>
128 + ServiceConnector<finterfaces::StateMarker>
129 + ServiceConnector<finterfaces_admin::InstallerMarker>
130 + ServiceConnector<fneighbor::ControllerMarker>
131 + ServiceConnector<fneighbor::ViewMarker>
132 + ServiceConnector<fstack::LogMarker>
133 + ServiceConnector<fstack::StackMarker>
134 + ServiceConnector<froutes::StateV4Marker>
135 + ServiceConnector<froutes::StateV6Marker>
136 + ServiceConnector<fname::LookupMarker>
137 + ServiceConnector<fnet_filter::StateMarker>
138 + ServiceConnector<fdebug::PacketCaptureProviderMarker>
139{
140}
141
142pub async fn do_root<C, D>(
143 mut out: writer::JsonWriter<serde_json::Value>,
144 Command { cmd }: Command,
145 connector: &C,
146 deps: &D,
147) -> Result<(), Error>
148where
149 C: NetCliDepsConnector,
150 D: CaptureDeps,
151{
152 match cmd {
153 CommandEnum::Capture(cmd) => capture::do_capture(out, cmd, connector, deps)
154 .await
155 .context("failed during capture command"),
156 CommandEnum::If(opts::If { if_cmd: cmd }) => {
157 do_if(&mut out, cmd, connector).await.context("failed during if command")
158 }
159 CommandEnum::Route(opts::Route { route_cmd: cmd }) => {
160 do_route(&mut out, cmd, connector).await.context("failed during route command")
161 }
162 CommandEnum::Rule(opts::Rule { rule_cmd: cmd }) => {
163 do_rule(&mut out, cmd, connector).await.context("failed during rule command")
164 }
165 CommandEnum::FilterDeprecated(opts::FilterDeprecated { filter_cmd: cmd }) => {
166 do_filter_deprecated(out, cmd, connector)
167 .await
168 .context("failed during filter-deprecated command")
169 }
170 CommandEnum::Filter(opts::filter::Filter { filter_cmd: cmd }) => {
171 filter::do_filter(out, cmd, connector).await.context("failed during filter command")
172 }
173 CommandEnum::Log(opts::Log { log_cmd: cmd }) => {
174 do_log(cmd, connector).await.context("failed during log command")
175 }
176 CommandEnum::Dhcp(opts::Dhcp { dhcp_cmd: cmd }) => {
177 do_dhcp(cmd, connector).await.context("failed during dhcp command")
178 }
179 CommandEnum::Dhcpd(opts::dhcpd::Dhcpd { dhcpd_cmd: cmd }) => {
180 do_dhcpd(cmd, connector).await.context("failed during dhcpd command")
181 }
182 CommandEnum::Neigh(opts::Neigh { neigh_cmd: cmd }) => {
183 do_neigh(out, cmd, connector).await.context("failed during neigh command")
184 }
185 CommandEnum::Dns(opts::dns::Dns { dns_cmd: cmd }) => {
186 do_dns(out, cmd, connector).await.context("failed during dns command")
187 }
188 }
189}
190
191fn shortlist_interfaces(
192 name_pattern: &str,
193 interfaces: &mut HashMap<
194 u64,
195 finterfaces_ext::PropertiesAndState<(), finterfaces_ext::AllInterest>,
196 >,
197) {
198 interfaces.retain(|_: &u64, properties_and_state| {
199 properties_and_state.properties.name.contains(name_pattern)
200 })
201}
202
203fn write_tabulated_interfaces_info<
204 W: std::io::Write,
205 I: IntoIterator<Item = ser::InterfaceView>,
206>(
207 mut out: W,
208 interfaces: I,
209) -> Result<(), Error> {
210 let mut t = Table::new();
211 t.set_format(format::FormatBuilder::new().padding(2, 2).build());
212 for (
213 i,
214 ser::InterfaceView {
215 nicid,
216 name,
217 device_class,
218 online,
219 addresses,
220 mac,
221 has_default_ipv4_route,
222 has_default_ipv6_route,
223 port_identity_koid,
224 },
225 ) in interfaces.into_iter().enumerate()
226 {
227 if i > 0 {
228 add_row(&mut t, row![]);
229 }
230
231 add_row(&mut t, row!["nicid", nicid]);
232 add_row(&mut t, row!["name", name]);
233 add_row(
234 &mut t,
235 row![
236 "device class",
237 match device_class {
238 ser::DeviceClass::Loopback => "loopback",
239 ser::DeviceClass::Blackhole => "blackhole",
240 ser::DeviceClass::Virtual => "virtual",
241 ser::DeviceClass::Ethernet => "ethernet",
242 ser::DeviceClass::WlanClient => "wlan-client",
243 ser::DeviceClass::Ppp => "ppp",
244 ser::DeviceClass::Bridge => "bridge",
245 ser::DeviceClass::WlanAp => "wlan-ap",
246 ser::DeviceClass::Lowpan => "lowpan",
247 }
248 ],
249 );
250 add_row(&mut t, row!["online", online]);
251
252 let default_routes: std::borrow::Cow<'_, _> =
253 if has_default_ipv4_route || has_default_ipv6_route {
254 itertools::Itertools::intersperse(
255 has_default_ipv4_route
256 .then_some("IPv4")
257 .into_iter()
258 .chain(has_default_ipv6_route.then_some("IPv6")),
259 ",",
260 )
261 .collect::<String>()
262 .into()
263 } else {
264 "-".into()
265 };
266 add_row(&mut t, row!["default routes", default_routes]);
267
268 for ser::Address {
269 subnet: ser::Subnet { addr, prefix_len },
270 valid_until,
271 assignment_state,
272 } in addresses.all_addresses()
273 {
274 let valid_until = valid_until.map(|v| {
275 let v = std::time::Duration::from_nanos(v.try_into().unwrap_or(0)).as_secs_f32();
276 std::borrow::Cow::Owned(format!("valid until [{v}s]"))
277 });
278 let assignment_state: Option<std::borrow::Cow<'_, _>> = match assignment_state {
279 AddressAssignmentState::Assigned => None,
280 AddressAssignmentState::Tentative => Some("TENTATIVE".into()),
281 AddressAssignmentState::Unavailable => Some("UNAVAILABLE".into()),
282 };
283 let extra_bits = itertools::Itertools::intersperse(
284 assignment_state.into_iter().chain(valid_until),
285 " ".into(),
286 )
287 .collect::<String>();
288
289 add_row(&mut t, row!["addr", format!("{addr}/{prefix_len}"), extra_bits]);
290 }
291 match mac {
292 None => add_row(&mut t, row!["mac", "-"]),
293 Some(mac) => add_row(&mut t, row!["mac", mac]),
294 }
295 match port_identity_koid {
296 None => add_row(&mut t, row!["port_identity_koid", "-"]),
297 Some(port_identity_koid) => {
298 add_row(&mut t, row!["port_identity_koid", port_identity_koid])
299 }
300 }
301 }
302 writeln!(out, "{}", t)?;
303 Ok(())
304}
305
306pub(crate) async fn connect_with_context<S, C>(connector: &C) -> Result<S::Proxy, Error>
307where
308 C: ServiceConnector<S>,
309 S: flex_client::fidl::ProtocolMarker,
310{
311 connector.connect().await.with_context(|| format!("failed to connect to {}", S::DEBUG_NAME))
312}
313
314async fn get_control<C>(connector: &C, id: u64) -> Result<finterfaces_ext::admin::Control, Error>
315where
316 C: ServiceConnector<froot::InterfacesMarker>,
317{
318 let root_interfaces = connect_with_context::<froot::InterfacesMarker, _>(connector).await?;
319 let (proxy, server_end) =
320 root_interfaces.domain().create_proxy::<finterfaces_admin::ControlMarker>();
321 let control = finterfaces_ext::admin::Control::new(proxy);
322 root_interfaces.get_admin(id, server_end).context("send get admin request")?;
323 Ok(control)
324}
325
326fn configuration_with_ip_forwarding_set(
327 ip_version: fnet::IpVersion,
328 forwarding: bool,
329) -> finterfaces_admin::Configuration {
330 match ip_version {
331 fnet::IpVersion::V4 => finterfaces_admin::Configuration {
332 ipv4: Some(finterfaces_admin::Ipv4Configuration {
333 unicast_forwarding: Some(forwarding),
334 ..Default::default()
335 }),
336 ..Default::default()
337 },
338 fnet::IpVersion::V6 => finterfaces_admin::Configuration {
339 ipv6: Some(finterfaces_admin::Ipv6Configuration {
340 unicast_forwarding: Some(forwarding),
341 ..Default::default()
342 }),
343 ..Default::default()
344 },
345 }
346}
347
348fn extract_ip_forwarding(
349 finterfaces_admin::Configuration {
350 ipv4: ipv4_config, ipv6: ipv6_config, ..
351 }: finterfaces_admin::Configuration,
352 ip_version: fnet::IpVersion,
353) -> Result<bool, Error> {
354 match ip_version {
355 fnet::IpVersion::V4 => {
356 let finterfaces_admin::Ipv4Configuration { unicast_forwarding, .. } =
357 ipv4_config.context("get IPv4 configuration")?;
358 unicast_forwarding.context("get IPv4 forwarding configuration")
359 }
360 fnet::IpVersion::V6 => {
361 let finterfaces_admin::Ipv6Configuration { unicast_forwarding, .. } =
362 ipv6_config.context("get IPv6 configuration")?;
363 unicast_forwarding.context("get IPv6 forwarding configuration")
364 }
365 }
366}
367
368fn extract_igmp_version(
369 finterfaces_admin::Configuration { ipv4: ipv4_config, .. }: finterfaces_admin::Configuration,
370) -> Result<Option<finterfaces_admin::IgmpVersion>, Error> {
371 let finterfaces_admin::Ipv4Configuration { igmp, .. } =
372 ipv4_config.context("get IPv4 configuration")?;
373 let finterfaces_admin::IgmpConfiguration { version: igmp_version, .. } =
374 igmp.context("get IGMP configuration")?;
375 Ok(igmp_version)
376}
377
378fn extract_mld_version(
379 finterfaces_admin::Configuration { ipv6: ipv6_config, .. }: finterfaces_admin::Configuration,
380) -> Result<Option<finterfaces_admin::MldVersion>, Error> {
381 let finterfaces_admin::Ipv6Configuration { mld, .. } =
382 ipv6_config.context("get IPv6 configuration")?;
383 let finterfaces_admin::MldConfiguration { version: mld_version, .. } =
384 mld.context("get MLD configuration")?;
385 Ok(mld_version)
386}
387
388fn extract_nud_config(
389 finterfaces_admin::Configuration { ipv4, ipv6, .. }: finterfaces_admin::Configuration,
390 ip_version: fnet::IpVersion,
391) -> Result<finterfaces_admin::NudConfiguration, Error> {
392 match ip_version {
393 fnet::IpVersion::V4 => {
394 let finterfaces_admin::Ipv4Configuration { arp, .. } =
395 ipv4.context("get IPv4 configuration")?;
396 let finterfaces_admin::ArpConfiguration { nud, .. } =
397 arp.context("get ARP configuration")?;
398 nud.context("get NUD configuration")
399 }
400 fnet::IpVersion::V6 => {
401 let finterfaces_admin::Ipv6Configuration { ndp, .. } =
402 ipv6.context("get IPv6 configuration")?;
403 let finterfaces_admin::NdpConfiguration { nud, .. } =
404 ndp.context("get NDP configuration")?;
405 nud.context("get NUD configuration")
406 }
407 }
408}
409
410async fn do_if<C: NetCliDepsConnector>(
411 out: &mut writer::JsonWriter<serde_json::Value>,
412 cmd: opts::IfEnum,
413 connector: &C,
414) -> Result<(), Error> {
415 match cmd {
416 opts::IfEnum::List(opts::IfList { name_pattern }) => {
417 let root_interfaces =
418 connect_with_context::<froot::InterfacesMarker, _>(connector).await?;
419 let interface_state =
420 connect_with_context::<finterfaces::StateMarker, _>(connector).await?;
421 let stream = finterfaces_ext::event_stream_from_state::<finterfaces_ext::AllInterest>(
422 &interface_state,
423 Default::default(),
424 )?;
425 let mut response = finterfaces_ext::existing(
426 stream,
427 HashMap::<u64, finterfaces_ext::PropertiesAndState<(), _>>::new(),
428 )
429 .await?;
430 if let Some(name_pattern) = name_pattern {
431 shortlist_interfaces(&name_pattern, &mut response);
432 }
433 let response = response.into_values().map(
434 |finterfaces_ext::PropertiesAndState { properties, state: () }| async {
435 let mac = root_interfaces
436 .get_mac(properties.id.get())
437 .await
438 .context("call get_mac")?;
439 Ok::<_, Error>((properties, mac))
440 },
441 );
442 let response = futures::future::try_join_all(response).await?;
443 let mut response: Vec<_> = response
444 .into_iter()
445 .filter_map(|(properties, mac)| match mac {
446 Err(froot::InterfacesGetMacError::NotFound) => None,
447 Ok(mac) => {
448 let mac = mac.map(|box_| *box_);
449 Some((properties, mac).into())
450 }
451 })
452 .collect();
453 response.sort_by_key(|ser::InterfaceView { nicid, .. }| *nicid);
454 if out.is_machine() {
455 out.machine(&serde_json::to_value(&response)?)?;
456 } else {
457 write_tabulated_interfaces_info(out, response.into_iter())
458 .context("error tabulating interface info")?;
459 }
460 }
461 opts::IfEnum::Get(opts::IfGet { interface }) => {
462 let id = interface.find_nicid(connector).await?.get();
463 let root_interfaces =
464 connect_with_context::<froot::InterfacesMarker, _>(connector).await?;
465 let interface_state =
466 connect_with_context::<finterfaces::StateMarker, _>(connector).await?;
467 let stream = finterfaces_ext::event_stream_from_state::<finterfaces_ext::AllInterest>(
468 &interface_state,
469 Default::default(),
470 )?;
471 let response = finterfaces_ext::existing(
472 stream,
473 finterfaces_ext::InterfaceState::<(), _>::Unknown(id),
474 )
475 .await?;
476 match response {
477 finterfaces_ext::InterfaceState::Unknown(id) => {
478 return Err(user_facing_error(format!("interface with id={} not found", id)));
479 }
480 finterfaces_ext::InterfaceState::Known(finterfaces_ext::PropertiesAndState {
481 properties,
482 state: _,
483 }) => {
484 let finterfaces_ext::Properties { id, .. } = &properties;
485 let mac = root_interfaces.get_mac(id.get()).await.context("call get_mac")?;
486 match mac {
487 Err(froot::InterfacesGetMacError::NotFound) => {
488 return Err(user_facing_error(format!(
489 "interface with id={} not found",
490 id
491 )));
492 }
493 Ok(mac) => {
494 let mac = mac.map(|box_| *box_);
495 let view = (properties, mac).into();
496 if out.is_machine() {
497 out.machine(&serde_json::to_value(&view)?)?;
498 } else {
499 write_tabulated_interfaces_info(out, std::iter::once(view))
500 .context("error tabulating interface info")?;
501 }
502 }
503 };
504 }
505 }
506 }
507 opts::IfEnum::Igmp(opts::IfIgmp { cmd }) => match cmd {
508 opts::IfIgmpEnum::Get(opts::IfIgmpGet { interface }) => {
509 let id = interface.find_nicid(connector).await.context("find nicid")?.get();
510 let control = get_control(connector, id).await.context("get control")?;
511 let configuration = control
512 .get_configuration()
513 .await
514 .map_err(anyhow::Error::new)
515 .and_then(|res| {
516 res.map_err(|e: finterfaces_admin::ControlGetConfigurationError| {
517 anyhow!("{:?}", e)
518 })
519 })
520 .context("get configuration")?;
521
522 out.line(format!("IGMP configuration on interface {}:", id))?;
523 out.line(format!(
524 " Version: {:?}",
525 extract_igmp_version(configuration).context("get IGMP version")?
526 ))?;
527 }
528 opts::IfIgmpEnum::Set(opts::IfIgmpSet { interface, version }) => {
529 let id = interface.find_nicid(connector).await.context("find nicid")?.get();
530 let control = get_control(connector, id).await.context("get control")?;
531 let prev_config = control
532 .set_configuration(&finterfaces_admin::Configuration {
533 ipv4: Some(finterfaces_admin::Ipv4Configuration {
534 igmp: Some(finterfaces_admin::IgmpConfiguration {
535 version,
536 ..Default::default()
537 }),
538 ..Default::default()
539 }),
540 ..Default::default()
541 })
542 .await
543 .map_err(anyhow::Error::new)
544 .and_then(|res| {
545 res.map_err(|e: finterfaces_admin::ControlSetConfigurationError| {
546 anyhow!("{:?}", e)
547 })
548 })
549 .context("set configuration")?;
550
551 info!(
552 "IGMP version set to {:?} on interface {}; previously set to {:?}",
553 version,
554 id,
555 extract_igmp_version(prev_config).context("set IGMP version")?,
556 );
557 }
558 },
559 opts::IfEnum::Mld(opts::IfMld { cmd }) => match cmd {
560 opts::IfMldEnum::Get(opts::IfMldGet { interface }) => {
561 let id = interface.find_nicid(connector).await.context("find nicid")?.get();
562 let control = get_control(connector, id).await.context("get control")?;
563 let configuration = control
564 .get_configuration()
565 .await
566 .map_err(anyhow::Error::new)
567 .and_then(|res| {
568 res.map_err(|e: finterfaces_admin::ControlGetConfigurationError| {
569 anyhow!("{:?}", e)
570 })
571 })
572 .context("get configuration")?;
573
574 out.line(format!("MLD configuration on interface {}:", id))?;
575 out.line(format!(
576 " Version: {:?}",
577 extract_mld_version(configuration).context("get MLD version")?
578 ))?;
579 }
580 opts::IfMldEnum::Set(opts::IfMldSet { interface, version }) => {
581 let id = interface.find_nicid(connector).await.context("find nicid")?.get();
582 let control = get_control(connector, id).await.context("get control")?;
583 let prev_config = control
584 .set_configuration(&finterfaces_admin::Configuration {
585 ipv6: Some(finterfaces_admin::Ipv6Configuration {
586 mld: Some(finterfaces_admin::MldConfiguration {
587 version,
588 ..Default::default()
589 }),
590 ..Default::default()
591 }),
592 ..Default::default()
593 })
594 .await
595 .map_err(anyhow::Error::new)
596 .and_then(|res| {
597 res.map_err(|e: finterfaces_admin::ControlSetConfigurationError| {
598 anyhow!("{:?}", e)
599 })
600 })
601 .context("set configuration")?;
602
603 info!(
604 "MLD version set to {:?} on interface {}; previously set to {:?}",
605 version,
606 id,
607 extract_mld_version(prev_config).context("set MLD version")?,
608 );
609 }
610 },
611 opts::IfEnum::IpForward(opts::IfIpForward { cmd }) => match cmd {
612 opts::IfIpForwardEnum::Get(opts::IfIpForwardGet { interface, ip_version }) => {
613 let id = interface.find_nicid(connector).await.context("find nicid")?.get();
614 let control = get_control(connector, id).await.context("get control")?;
615 let configuration = control
616 .get_configuration()
617 .await
618 .map_err(anyhow::Error::new)
619 .and_then(|res| {
620 res.map_err(|e: finterfaces_admin::ControlGetConfigurationError| {
621 anyhow!("{:?}", e)
622 })
623 })
624 .context("get configuration")?;
625
626 out.line(format!(
627 "IP forwarding for {:?} is {} on interface {}",
628 ip_version,
629 extract_ip_forwarding(configuration, ip_version)
630 .context("extract IP forwarding configuration")?,
631 id
632 ))?;
633 }
634 opts::IfIpForwardEnum::Set(opts::IfIpForwardSet { interface, ip_version, enable }) => {
635 let id = interface.find_nicid(connector).await.context("find nicid")?.get();
636 let control = get_control(connector, id).await.context("get control")?;
637 let prev_config = control
638 .set_configuration(&configuration_with_ip_forwarding_set(ip_version, enable))
639 .await
640 .map_err(anyhow::Error::new)
641 .and_then(|res| {
642 res.map_err(|e: finterfaces_admin::ControlSetConfigurationError| {
643 anyhow!("{:?}", e)
644 })
645 })
646 .context("set configuration")?;
647 info!(
648 "IP forwarding for {:?} set to {} on interface {}; previously set to {}",
649 ip_version,
650 enable,
651 id,
652 extract_ip_forwarding(prev_config, ip_version)
653 .context("set IP forwarding configuration")?
654 );
655 }
656 },
657 opts::IfEnum::Enable(opts::IfEnable { interface }) => {
658 let id = interface.find_nicid(connector).await?.get();
659 let control = get_control(connector, id).await?;
660 let did_enable = control
661 .enable()
662 .await
663 .map_err(anyhow::Error::new)
664 .and_then(|res| {
665 res.map_err(|e: finterfaces_admin::ControlEnableError| anyhow!("{:?}", e))
666 })
667 .context("error enabling interface")?;
668 if did_enable {
669 info!("Interface {} enabled", id);
670 } else {
671 info!("Interface {} already enabled", id);
672 }
673 }
674 opts::IfEnum::Disable(opts::IfDisable { interface }) => {
675 let id = interface.find_nicid(connector).await?.get();
676 let control = get_control(connector, id).await?;
677 let did_disable = control
678 .disable()
679 .await
680 .map_err(anyhow::Error::new)
681 .and_then(|res| {
682 res.map_err(|e: finterfaces_admin::ControlDisableError| anyhow!("{:?}", e))
683 })
684 .context("error disabling interface")?;
685 if did_disable {
686 info!("Interface {} disabled", id);
687 } else {
688 info!("Interface {} already disabled", id);
689 }
690 }
691 opts::IfEnum::Addr(opts::IfAddr { addr_cmd }) => match addr_cmd {
692 opts::IfAddrEnum::Add(opts::IfAddrAdd { interface, addr, prefix, no_subnet_route }) => {
693 let id = interface.find_nicid(connector).await?.get();
694 let control = get_control(connector, id).await?;
695 let addr = fnet_ext::IpAddress::from_str(&addr)?.into();
696 let subnet = fnet_ext::Subnet { addr, prefix_len: prefix };
697 let root_interfaces =
698 connect_with_context::<froot::InterfacesMarker, _>(connector).await?;
699 let (address_state_provider, server_end) =
700 root_interfaces
701 .domain()
702 .create_proxy::<finterfaces_admin::AddressStateProviderMarker>();
703 control
704 .add_address(
705 &subnet.into(),
706 &finterfaces_admin::AddressParameters {
707 add_subnet_route: Some(!no_subnet_route),
708 ..Default::default()
709 },
710 server_end,
711 )
712 .context("call add address")?;
713
714 address_state_provider.detach().context("detach address lifetime")?;
715 let state_stream =
716 finterfaces_ext::admin::assignment_state_stream(address_state_provider);
717
718 state_stream
719 .try_filter_map(|state| {
720 futures::future::ok(match state {
721 finterfaces::AddressAssignmentState::Tentative => None,
722 finterfaces::AddressAssignmentState::Assigned => Some(()),
723 finterfaces::AddressAssignmentState::Unavailable => Some(()),
724 })
725 })
726 .try_next()
727 .await
728 .context("error after adding address")?
729 .ok_or_else(|| {
730 anyhow!(
731 "Address assignment state stream unexpectedly ended \
732 before reaching Assigned or Unavailable state. \
733 This is probably a bug."
734 )
735 })?;
736
737 info!("Address {}/{} added to interface {}", addr, prefix, id);
738 }
739 opts::IfAddrEnum::Del(opts::IfAddrDel { interface, addr, prefix }) => {
740 let id = interface.find_nicid(connector).await?.get();
741 let control = get_control(connector, id).await?;
742 let addr = fnet_ext::IpAddress::from_str(&addr)?;
743 let did_remove = {
744 let addr = addr.into();
745 let subnet = fnet::Subnet {
746 addr,
747 prefix_len: prefix.unwrap_or_else(|| {
748 8 * u8::try_from(match addr {
749 fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr }) => addr.len(),
750 fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr }) => addr.len(),
751 })
752 .expect("prefix length doesn't fit u8")
753 }),
754 };
755 control
756 .remove_address(&subnet)
757 .await
758 .map_err(anyhow::Error::new)
759 .and_then(|res| {
760 res.map_err(|e: finterfaces_admin::ControlRemoveAddressError| {
761 anyhow!("{:?}", e)
762 })
763 })
764 .context("call remove address")?
765 };
766 if !did_remove {
767 return Err(user_facing_error(format!(
768 "Address {} not found on interface {}",
769 addr, id
770 )));
771 }
772 info!("Address {} deleted from interface {}", addr, id);
773 }
774 opts::IfAddrEnum::Wait(opts::IfAddrWait { interface, ipv6 }) => {
775 let id = interface.find_nicid(connector).await?.get();
776 let interfaces_state =
777 connect_with_context::<finterfaces::StateMarker, _>(connector).await?;
778 let mut state = finterfaces_ext::InterfaceState::<(), _>::Unknown(id);
779
780 let assigned_addr = finterfaces_ext::wait_interface_with_id(
781 finterfaces_ext::event_stream_from_state::<finterfaces_ext::AllInterest>(
782 &interfaces_state,
783 Default::default(),
784 )?,
785 &mut state,
786 |finterfaces_ext::PropertiesAndState { properties, state: _ }| {
787 let finterfaces_ext::Properties { addresses, .. } = properties;
788 let addr = if ipv6 {
789 addresses.iter().find_map(
790 |finterfaces_ext::Address {
791 addr: fnet::Subnet { addr, .. },
792 ..
793 }| {
794 match addr {
795 fnet::IpAddress::Ipv4(_) => None,
796 fnet::IpAddress::Ipv6(_) => Some(addr),
797 }
798 },
799 )
800 } else {
801 addresses.first().map(
802 |finterfaces_ext::Address {
803 addr: fnet::Subnet { addr, .. },
804 ..
805 }| addr,
806 )
807 };
808 addr.map(|addr| {
809 let fnet_ext::IpAddress(addr) = (*addr).into();
810 addr
811 })
812 },
813 )
814 .await
815 .context("wait for assigned address")?;
816
817 out.line(format!("{assigned_addr}"))?;
818 info!("Address {} assigned on interface {}", assigned_addr, id);
819 }
820 },
821 opts::IfEnum::Bridge(opts::IfBridge { interfaces }) => {
822 let stack = connect_with_context::<fstack::StackMarker, _>(connector).await?;
823 let build_name_to_id_map = || async {
824 let interface_state =
825 connect_with_context::<finterfaces::StateMarker, _>(connector).await?;
826 let stream = finterfaces_ext::event_stream_from_state::<
827 finterfaces_ext::AllInterest,
828 >(&interface_state, Default::default())?;
829 let response = finterfaces_ext::existing(stream, HashMap::new()).await?;
830 Ok::<HashMap<String, u64>, Error>(
831 response
832 .into_iter()
833 .map(
834 |(
835 id,
836 finterfaces_ext::PropertiesAndState {
837 properties: finterfaces_ext::Properties { name, .. },
838 state: (),
839 },
840 )| (name, id),
841 )
842 .collect(),
843 )
844 };
845
846 let num_interfaces = interfaces.len();
847
848 let (_name_to_id, ids): (Option<HashMap<String, u64>>, Vec<u64>) =
849 futures::stream::iter(interfaces)
850 .map(Ok::<_, Error>)
851 .try_fold(
852 (None, Vec::with_capacity(num_interfaces)),
853 |(name_to_id, mut ids), interface| async move {
854 let (name_to_id, id) = match interface {
855 opts::InterfaceIdentifier::Id(id) => (name_to_id, id.get()),
856 opts::InterfaceIdentifier::Name(name) => {
857 let name_to_id = match name_to_id {
858 Some(name_to_id) => name_to_id,
859 None => build_name_to_id_map().await?,
860 };
861 let id = name_to_id.get(&name).copied().ok_or_else(|| {
862 user_facing_error(format!("no interface named {}", name))
863 })?;
864 (Some(name_to_id), id)
865 }
866 };
867 ids.push(id);
868 Ok((name_to_id, ids))
869 },
870 )
871 .await?;
872
873 let (bridge, server_end) = stack.domain().create_proxy();
874 stack.bridge_interfaces(&ids, server_end).context("bridge interfaces")?;
875 let bridge_id = bridge.get_id().await.context("get bridge id")?;
876 bridge.detach().context("detach bridge")?;
878 info!("network bridge created with id {}", bridge_id);
879 }
880 opts::IfEnum::Config(opts::IfConfig { interface, cmd }) => {
881 let id = interface.find_nicid(connector).await.context("find nicid")?.get();
882 let control = get_control(connector, id).await.context("get control")?;
883
884 match cmd {
885 opts::IfConfigEnum::Set(opts::IfConfigSet { options }) => {
886 do_if_config_set(control, options).await?;
887 }
888 opts::IfConfigEnum::Get(opts::IfConfigGet {}) => {
889 let configuration = control
890 .get_configuration()
891 .await
892 .map_err(anyhow::Error::new)
893 .and_then(|res| {
894 res.map_err(|e: finterfaces_admin::ControlGetConfigurationError| {
895 anyhow::anyhow!("{:?}", e)
896 })
897 })
898 .context("get configuration")?;
899 out.line(format!("{:#?}", configuration))?;
902 }
903 }
904 }
905 opts::IfEnum::Add(opts::IfAdd {
906 cmd: opts::IfAddEnum::Blackhole(opts::IfBlackholeAdd { name }),
907 }) => {
908 let installer =
909 ServiceConnector::<finterfaces_admin::InstallerMarker>::connect(connector)
910 .await
911 .expect("connect should succeed");
912
913 let (proxy, server_end) =
914 installer.domain().create_proxy::<finterfaces_admin::ControlMarker>();
915 let control = finterfaces_ext::admin::Control::new(proxy);
916 installer
917 .install_blackhole_interface(
918 server_end,
919 finterfaces_admin::Options { name: Some(name), ..Default::default() },
920 )
921 .expect("install blackhole interface should succeed");
922 control.detach().expect("detach should succeed");
923 }
924 opts::IfEnum::Remove(opts::IfRemove { interface }) => {
925 let id = interface.find_nicid(connector).await.context("find nicid")?.get();
926 let control = get_control(connector, id).await.context("get control")?;
927 control
928 .remove()
929 .await
930 .expect("should not get FIDL error")
931 .expect("remove should succeed");
932 }
933 }
934 Ok(())
935}
936
937async fn do_if_config_set(
938 control: finterfaces_ext::admin::Control,
939 options: Vec<String>,
940) -> Result<(), Error> {
941 if options.len() % 2 != 0 {
942 return Err(user_facing_error(format!(
943 "if config set expects property value pairs and thus an even number of arguments"
944 )));
945 }
946 let config = options.iter().tuples().try_fold(
947 finterfaces_admin::Configuration::default(),
948 |mut config, (property, value)| {
949 match property.as_str() {
950 "ipv6.ndp.slaac.temporary_address_enabled" => {
951 let enabled = value.parse::<bool>().map_err(|e| {
952 user_facing_error(format!("failed to parse {value} as bool: {e}"))
953 })?;
954 config
955 .ipv6
956 .get_or_insert_default()
957 .ndp
958 .get_or_insert_default()
959 .slaac
960 .get_or_insert_default()
961 .temporary_address = Some(enabled);
962 }
963 "ipv6.ndp.dad.transmits" => {
964 let transmits = value.parse::<u16>().map_err(|e| {
965 user_facing_error(format!("failed to parse {value} as u16: {e}"))
966 })?;
967 config
968 .ipv6
969 .get_or_insert_default()
970 .ndp
971 .get_or_insert_default()
972 .dad
973 .get_or_insert_default()
974 .transmits = Some(transmits);
975 }
976 "ipv6.ndp.router_solicitations.max" => {
977 let max_router_solicitations = value.parse::<u8>().map_err(|e| {
978 user_facing_error(format!("failed to parse {value} as u8: {e}"))
979 })?;
980 config
981 .ipv6
982 .get_or_insert_default()
983 .ndp
984 .get_or_insert_default()
985 .router_solicitations
986 .get_or_insert_default()
987 .max = Some(max_router_solicitations);
988 }
989 unknown_property => {
990 return Err(user_facing_error(format!(
991 "unknown configuration parameter: {unknown_property}"
992 )));
993 }
994 }
995 Ok(config)
996 },
997 )?;
998
999 let _: finterfaces_admin::Configuration = control
1002 .set_configuration(&config)
1003 .await
1004 .map_err(anyhow::Error::new)
1005 .and_then(|res| {
1006 res.map_err(|e: finterfaces_admin::ControlSetConfigurationError| {
1007 anyhow::anyhow!("{:?}", e)
1008 })
1009 })
1010 .context("set configuration")?;
1011
1012 Ok(())
1013}
1014
1015enum IpSubnetAndGateway {
1016 V4(Subnet<Ipv4Addr>, Option<Ipv4Addr>),
1017 V6(Subnet<Ipv6Addr>, Option<Ipv6Addr>),
1018}
1019
1020fn try_into_matched_subnet_and_gateway(
1021 destination: std::net::IpAddr,
1022 prefix_len: u8,
1023 gateway: Option<std::net::IpAddr>,
1024) -> Result<IpSubnetAndGateway, Error> {
1025 match (destination, gateway) {
1026 (std::net::IpAddr::V4(addr), Some(std::net::IpAddr::V4(g))) => Ok(IpSubnetAndGateway::V4(
1027 Subnet::new(Ipv4Addr::from(addr).mask(prefix_len), prefix_len)
1028 .map_err(|e| anyhow!("invalid subnet: {:?}", e))?,
1029 Some(g.into()),
1030 )),
1031 (std::net::IpAddr::V4(addr), None) => Ok(IpSubnetAndGateway::V4(
1032 Subnet::new(Ipv4Addr::from(addr).mask(prefix_len), prefix_len)
1033 .map_err(|e| anyhow!("invalid subnet: {:?}", e))?,
1034 None,
1035 )),
1036 (std::net::IpAddr::V6(addr), Some(std::net::IpAddr::V6(g))) => Ok(IpSubnetAndGateway::V6(
1037 Subnet::new(Ipv6Addr::from(addr).mask(prefix_len), prefix_len)
1038 .map_err(|e| anyhow!("invalid subnet: {:?}", e))?,
1039 Some(g.into()),
1040 )),
1041 (std::net::IpAddr::V6(addr), None) => Ok(IpSubnetAndGateway::V6(
1042 Subnet::new(Ipv6Addr::from(addr).mask(prefix_len), prefix_len)
1043 .map_err(|e| anyhow!("invalid subnet: {:?}", e))?,
1044 None,
1045 )),
1046 (std::net::IpAddr::V4(_), Some(std::net::IpAddr::V6(_)))
1047 | (std::net::IpAddr::V6(_), Some(std::net::IpAddr::V4(_))) => {
1048 Err(anyhow!("Gateway IP version mismatch"))
1049 }
1050 }
1051}
1052
1053async fn get_interface_authorization_proof<C: NetCliDepsConnector>(
1054 interface: &opts::InterfaceIdentifier,
1055 connector: &C,
1056) -> Result<(NonZeroU64, fnet_resources::ProofOfInterfaceAuthorization), Error> {
1057 let nicid = interface.find_nicid(connector).await?;
1058
1059 let control = get_control(connector, nicid.get()).await?;
1060
1061 let grant = control
1062 .get_authorization_for_interface()
1063 .await
1064 .context("failed to get authorization for interface")?;
1065 let proof = fnet_resources::ProofOfInterfaceAuthorization {
1066 interface_id: grant.interface_id,
1067 token: grant.token,
1068 };
1069
1070 Ok((nicid, proof))
1071}
1072
1073async fn do_route<C: NetCliDepsConnector>(
1074 out: &mut writer::JsonWriter<serde_json::Value>,
1075 cmd: opts::RouteEnum,
1076 connector: &C,
1077) -> Result<(), Error> {
1078 match cmd {
1079 opts::RouteEnum::List(opts::RouteList {}) => do_route_list(out, connector).await?,
1080 opts::RouteEnum::Add(route) => do_route_add(route, connector).await?,
1081 opts::RouteEnum::Del(route) => do_route_del(route, connector).await?,
1082 }
1083 Ok(())
1084}
1085
1086async fn do_route_add<C: NetCliDepsConnector>(
1087 route: opts::RouteAdd,
1088 connector: &C,
1089) -> Result<(), Error> {
1090 let interface = route.interface;
1091 let (nicid, proof) = get_interface_authorization_proof(&interface, connector).await?;
1092
1093 match try_into_matched_subnet_and_gateway(route.destination, route.prefix_len, route.gateway)? {
1094 IpSubnetAndGateway::V4(dest, g) => {
1095 let gateway = match g {
1096 Some(g) => Some(
1097 SpecifiedAddr::new(g.into())
1098 .ok_or_else(|| anyhow!("gateway must be a specified address"))?,
1099 ),
1100 None => None,
1101 };
1102 do_route_add_inner::<Ipv4, C>(dest, gateway, nicid, route.metric, proof, connector)
1103 .await?;
1104 }
1105 IpSubnetAndGateway::V6(dest, g) => {
1106 let gateway = match g {
1107 Some(g) => Some(
1108 SpecifiedAddr::new(g.into())
1109 .ok_or_else(|| anyhow!("gateway must be a specified address"))?,
1110 ),
1111 None => None,
1112 };
1113 do_route_add_inner::<Ipv6, C>(dest, gateway, nicid, route.metric, proof, connector)
1114 .await?;
1115 }
1116 }
1117 Ok(())
1118}
1119
1120fn new_route<I: Ip>(
1121 destination: net_types::ip::Subnet<I::Addr>,
1122 nicid: u64,
1123 gateway: Option<SpecifiedAddr<I::Addr>>,
1124 metric: Option<u32>,
1125) -> froutes_ext::Route<I> {
1126 match metric {
1127 Some(metric) => froutes_ext::Route::<I>::new_forward_with_explicit_metric(
1128 destination,
1129 nicid,
1130 gateway,
1131 metric,
1132 ),
1133 None => {
1134 froutes_ext::Route::<I>::new_forward_with_inherited_metric(destination, nicid, gateway)
1135 }
1136 }
1137}
1138
1139async fn do_route_add_inner<I, C>(
1140 destination: net_types::ip::Subnet<I::Addr>,
1141 gateway: Option<SpecifiedAddr<I::Addr>>,
1142 nicid: NonZeroU64,
1143 metric: Option<u32>,
1144 proof: fnet_resources::ProofOfInterfaceAuthorization,
1145 connector: &C,
1146) -> Result<(), Error>
1147where
1148 I: Ip + froutes_ext::admin::FidlRouteAdminIpExt + froutes_ext::FidlRouteIpExt,
1149 C: NetCliDepsConnector + ServiceConnector<I::GlobalRouteTableMarker>,
1150{
1151 let routes_proxy = connect_with_context::<I::GlobalRouteTableMarker, _>(connector).await?;
1152 let route_set_proxy = froutes_ext::admin::new_global_route_set::<I>(&routes_proxy)
1153 .context("failed to create global route set")?;
1154
1155 froutes_ext::admin::authenticate_for_interface::<I>(&route_set_proxy, proof)
1156 .await
1157 .context("authenticate_for_interface failed")?
1158 .map_err(|e| anyhow!("authenticate failed: {:?}", e))?;
1159
1160 let route = new_route::<I>(destination, nicid.get(), gateway, metric);
1161 let fidl_route =
1162 route.try_into().map_err(|e| anyhow!("failed to convert route to FIDL: {:?}", e))?;
1163
1164 let did_add = froutes_ext::admin::add_route::<I>(&route_set_proxy, &fidl_route)
1165 .await
1166 .context("add_route failed")?
1167 .map_err(|e| anyhow!("add_route failed: {:?}", e))?;
1168 if !did_add {
1169 warn!("Route already exists, did not add.");
1170 }
1171 Ok(())
1172}
1173
1174async fn do_route_del<C: NetCliDepsConnector>(
1175 route: opts::RouteDel,
1176 connector: &C,
1177) -> Result<(), Error> {
1178 let interface = route.interface;
1179 let (nicid, proof) = get_interface_authorization_proof(&interface, connector).await?;
1180
1181 match try_into_matched_subnet_and_gateway(route.destination, route.prefix_len, route.gateway)? {
1182 IpSubnetAndGateway::V4(dest, g) => {
1183 let gateway = match g {
1184 Some(g) => Some(
1185 SpecifiedAddr::new(g.into())
1186 .ok_or_else(|| anyhow!("gateway must be a specified address"))?,
1187 ),
1188 None => None,
1189 };
1190 do_route_del_inner::<Ipv4, C>(dest, gateway, nicid, route.metric, proof, connector)
1191 .await?;
1192 }
1193 IpSubnetAndGateway::V6(dest, g) => {
1194 let gateway = match g {
1195 Some(g) => Some(
1196 SpecifiedAddr::new(g.into())
1197 .ok_or_else(|| anyhow!("gateway must be a specified address"))?,
1198 ),
1199 None => None,
1200 };
1201 do_route_del_inner::<Ipv6, C>(dest, gateway, nicid, route.metric, proof, connector)
1202 .await?;
1203 }
1204 }
1205 Ok(())
1206}
1207
1208async fn do_route_del_inner<I, C>(
1209 destination: net_types::ip::Subnet<I::Addr>,
1210 gateway: Option<SpecifiedAddr<I::Addr>>,
1211 nicid: NonZeroU64,
1212 metric: Option<u32>,
1213 proof: fnet_resources::ProofOfInterfaceAuthorization,
1214 connector: &C,
1215) -> Result<(), Error>
1216where
1217 I: Ip + froutes_ext::admin::FidlRouteAdminIpExt + froutes_ext::FidlRouteIpExt,
1218 C: NetCliDepsConnector + ServiceConnector<I::GlobalRouteTableMarker>,
1219{
1220 let routes_proxy = connect_with_context::<I::GlobalRouteTableMarker, _>(connector).await?;
1221 let route_set_proxy = froutes_ext::admin::new_global_route_set::<I>(&routes_proxy)
1222 .context("failed to create global route set")?;
1223
1224 froutes_ext::admin::authenticate_for_interface::<I>(&route_set_proxy, proof)
1225 .await
1226 .context("authenticate_for_interface failed")?
1227 .map_err(|e| anyhow!("authenticate failed: {:?}", e))?;
1228
1229 let route = new_route::<I>(destination, nicid.get(), gateway, metric);
1230 let fidl_route =
1231 route.try_into().map_err(|e| anyhow!("failed to convert route to FIDL: {:?}", e))?;
1232
1233 let did_remove = froutes_ext::admin::remove_route::<I>(&route_set_proxy, &fidl_route)
1234 .await
1235 .context("remove_route failed")?
1236 .map_err(|e| anyhow!("remove_route failed: {:?}", e))?;
1237 if !did_remove {
1238 warn!("Route was not found, did not remove.");
1239 }
1240 Ok(())
1241}
1242
1243async fn do_route_list<C: NetCliDepsConnector>(
1244 out: &mut writer::JsonWriter<serde_json::Value>,
1245 connector: &C,
1246) -> Result<(), Error> {
1247 let ipv4_route_event_stream = pin!({
1248 let state_v4 = connect_with_context::<froutes::StateV4Marker, _>(connector)
1249 .await
1250 .context("failed to connect to fuchsia.net.routes/StateV4")?;
1251 froutes_ext::event_stream_from_state::<Ipv4>(&state_v4)
1252 .context("failed to initialize a `WatcherV4` client")?
1253 .fuse()
1254 });
1255 let ipv6_route_event_stream = pin!({
1256 let state_v6 = connect_with_context::<froutes::StateV6Marker, _>(connector)
1257 .await
1258 .context("failed to connect to fuchsia.net.routes/StateV6")?;
1259 froutes_ext::event_stream_from_state::<Ipv6>(&state_v6)
1260 .context("failed to initialize a `WatcherV6` client")?
1261 .fuse()
1262 });
1263 let (v4_routes, v6_routes) = futures::future::join(
1264 froutes_ext::collect_routes_until_idle::<_, Vec<_>>(ipv4_route_event_stream),
1265 froutes_ext::collect_routes_until_idle::<_, Vec<_>>(ipv6_route_event_stream),
1266 )
1267 .await;
1268 let mut v4_routes = v4_routes.context("failed to collect all existing IPv4 routes")?;
1269 let mut v6_routes = v6_routes.context("failed to collect all existing IPv6 routes")?;
1270
1271 fn group_by_table_id_and_sort<I: net_types::ip::Ip>(
1272 routes: &mut Vec<froutes_ext::InstalledRoute<I>>,
1273 ) {
1274 routes.sort_unstable_by_key(|r| r.table_id);
1275 for chunk in routes.chunk_by_mut(|a, b| a.table_id == b.table_id) {
1276 chunk.sort();
1277 }
1278 }
1279 group_by_table_id_and_sort(&mut v4_routes);
1280 group_by_table_id_and_sort(&mut v6_routes);
1281
1282 if out.is_machine() {
1283 fn to_ser<I: net_types::ip::Ip>(
1284 route: froutes_ext::InstalledRoute<I>,
1285 ) -> Option<ser::ForwardingEntry> {
1286 route.try_into().map_err(|e| warn!("failed to convert route: {:?}", e)).ok()
1287 }
1288 let routes = v4_routes
1289 .into_iter()
1290 .filter_map(to_ser)
1291 .chain(v6_routes.into_iter().filter_map(to_ser))
1292 .collect::<Vec<_>>();
1293 out.machine(&serde_json::to_value(routes)?).context("serialize")?;
1294 } else {
1295 let mut t = Table::new();
1296 t.set_format(format::FormatBuilder::new().padding(2, 2).build());
1297
1298 t.set_titles(row!["Destination", "Gateway", "NICID", "Metric", "TableId"]);
1300 fn write_route<I: net_types::ip::Ip>(t: &mut Table, route: froutes_ext::InstalledRoute<I>) {
1301 let froutes_ext::InstalledRoute {
1302 route: froutes_ext::Route { destination, action, properties: _ },
1303 effective_properties: froutes_ext::EffectiveRouteProperties { metric },
1304 table_id,
1305 } = route;
1306 let (device_id, next_hop) = match action {
1307 froutes_ext::RouteAction::Forward(froutes_ext::RouteTarget {
1308 outbound_interface,
1309 next_hop,
1310 }) => (outbound_interface, next_hop),
1311 froutes_ext::RouteAction::Unknown => {
1312 warn!("observed route with unknown RouteAction.");
1313 return;
1314 }
1315 };
1316 let next_hop = next_hop.map(|next_hop| next_hop.to_string());
1317 let next_hop = next_hop.as_ref().map_or("-", |s| s.as_str());
1318 add_row(t, row![destination, next_hop, device_id, metric, table_id]);
1319 }
1320
1321 for route in v4_routes {
1322 write_route(&mut t, route);
1323 }
1324 for route in v6_routes {
1325 write_route(&mut t, route);
1326 }
1327
1328 let _lines_printed: usize = t.print(out)?;
1329 out.line("")?;
1330 }
1331 Ok(())
1332}
1333
1334async fn do_rule<C: NetCliDepsConnector>(
1335 out: &mut writer::JsonWriter<serde_json::Value>,
1336 cmd: opts::RuleEnum,
1337 connector: &C,
1338) -> Result<(), Error> {
1339 match cmd {
1340 opts::RuleEnum::List(opts::RuleList {}) => do_rule_list(out, connector).await,
1341 }
1342}
1343
1344async fn do_rule_list<C: NetCliDepsConnector>(
1345 out: &mut writer::JsonWriter<serde_json::Value>,
1346 connector: &C,
1347) -> Result<(), Error> {
1348 let ipv4_rule_event_stream = pin!({
1349 let state_v4 = connect_with_context::<froutes::StateV4Marker, _>(connector)
1350 .await
1351 .context("failed to connect to fuchsia.net.routes/StateV4")?;
1352 froutes_ext::rules::rule_event_stream_from_state::<Ipv4>(&state_v4)
1353 .context("failed to initialize a `RuleWatcherV4` client")?
1354 .fuse()
1355 });
1356 let ipv6_rule_event_stream = pin!({
1357 let state_v6 = connect_with_context::<froutes::StateV6Marker, _>(connector)
1358 .await
1359 .context("failed to connect to fuchsia.net.routes/StateV6")?;
1360 froutes_ext::rules::rule_event_stream_from_state::<Ipv6>(&state_v6)
1361 .context("failed to initialize a `RuleWatcherV6` client")?
1362 .fuse()
1363 });
1364 let (v4_rules, v6_rules) = futures::future::join(
1365 froutes_ext::rules::collect_rules_until_idle::<Ipv4, Vec<_>>(ipv4_rule_event_stream),
1366 froutes_ext::rules::collect_rules_until_idle::<Ipv6, Vec<_>>(ipv6_rule_event_stream),
1367 )
1368 .await;
1369 let mut v4_rules = v4_rules.context("failed to collect all existing IPv4 rules")?;
1370 let mut v6_rules = v6_rules.context("failed to collect all existing IPv6 rules")?;
1371
1372 v4_rules.sort_by_key(|r| (r.priority, r.index));
1373 v6_rules.sort_by_key(|r| (r.priority, r.index));
1374
1375 fn format_matcher(matcher: fnet_matchers_ext::Mark) -> Cow<'static, str> {
1376 match matcher {
1377 fnet_matchers_ext::Mark::Unmarked => Cow::Borrowed("unmarked"),
1378 fnet_matchers_ext::Mark::Marked { mask, between, invert: _ } => {
1379 format!("{mask:#010x}:{:#010x}..{:#010x}", between.start(), between.end()).into()
1380 }
1381 }
1382 }
1383
1384 struct FormatRule {
1385 rule_set_priority: u32,
1386 index: u32,
1387 from: Option<String>,
1388 locally_generated: Option<String>,
1389 bound_device: Option<String>,
1390 mark_1: Option<Cow<'static, str>>,
1391 mark_2: Option<Cow<'static, str>>,
1392 action: Cow<'static, str>,
1393 }
1394
1395 impl FormatRule {
1396 fn from<I: Ip>(rule: froutes_ext::rules::InstalledRule<I>) -> Self {
1397 let froutes_ext::rules::InstalledRule {
1398 priority: rule_set_priority,
1399 index,
1400 matcher:
1401 froutes_ext::rules::RuleMatcher {
1402 from,
1403 locally_generated,
1404 bound_device,
1405 mark_1,
1406 mark_2,
1407 },
1408 action,
1409 } = rule;
1410
1411 let rule_set_priority = u32::from(rule_set_priority);
1412 let index = u32::from(index);
1413 let from = from.map(|from| from.to_string());
1414 let locally_generated = locally_generated.map(|x| x.to_string());
1415 let bound_device = bound_device.map(|matcher| match matcher {
1416 fnet_matchers_ext::BoundInterface::Bound(fnet_matchers_ext::Interface::Name(
1417 name,
1418 )) => format!("name:{name}"),
1419 fnet_matchers_ext::BoundInterface::Bound(fnet_matchers_ext::Interface::Id(id)) => {
1420 format!("id:{id}")
1421 }
1422 fnet_matchers_ext::BoundInterface::Bound(
1423 fnet_matchers_ext::Interface::PortClass(class),
1424 ) => format!("class:{class:?}"),
1425 fnet_matchers_ext::BoundInterface::Unbound => "unbound".into(),
1426 });
1427 let mark_1 = mark_1.map(format_matcher);
1428 let mark_2 = mark_2.map(format_matcher);
1429 let action = match action {
1430 froutes_ext::rules::RuleAction::Unreachable => Cow::Borrowed("unreachable"),
1431 froutes_ext::rules::RuleAction::Lookup(table_id) => {
1432 format!("lookup {table_id}").into()
1433 }
1434 };
1435
1436 FormatRule {
1437 rule_set_priority,
1438 index,
1439 from,
1440 locally_generated,
1441 bound_device,
1442 mark_1,
1443 mark_2,
1444 action,
1445 }
1446 }
1447 }
1448
1449 if out.is_machine() {
1450 fn rule_to_json<I: Ip>(rule: froutes_ext::rules::InstalledRule<I>) -> serde_json::Value {
1451 let FormatRule {
1452 rule_set_priority,
1453 index,
1454 from,
1455 locally_generated,
1456 bound_device,
1457 mark_1,
1458 mark_2,
1459 action,
1460 } = FormatRule::from(rule);
1461
1462 serde_json::json!({
1463 "rule_set_priority": rule_set_priority,
1464 "index": index,
1465 "from": from,
1466 "locally_generated": locally_generated,
1467 "bound_device": bound_device,
1468 "mark_1": mark_1,
1469 "mark_2": mark_2,
1470 "action": action,
1471 })
1472 }
1473
1474 let rules = v4_rules
1475 .into_iter()
1476 .map(rule_to_json)
1477 .chain(v6_rules.into_iter().map(rule_to_json))
1478 .collect::<Vec<_>>();
1479 out.machine(&serde_json::Value::Array(rules)).context("serialize")?;
1480 } else {
1481 let mut t = Table::new();
1482 t.set_format(format::FormatBuilder::new().padding(2, 2).build());
1483 t.set_titles(row![
1484 "RuleSetPriority",
1485 "RuleIndex",
1486 "From",
1487 "LocallyGenerated",
1488 "BoundDevice",
1489 "Mark1Matcher",
1490 "Mark2Matcher",
1491 "Action"
1492 ]);
1493
1494 fn option<D: Deref<Target = str>>(string: &Option<D>) -> &str {
1495 string.as_ref().map_or("-", |s| s.deref())
1496 }
1497
1498 fn write_rule<I: Ip>(t: &mut Table, rule: froutes_ext::rules::InstalledRule<I>) {
1499 let FormatRule {
1500 rule_set_priority,
1501 index,
1502 from,
1503 locally_generated,
1504 bound_device,
1505 mark_1,
1506 mark_2,
1507 action,
1508 } = FormatRule::from(rule);
1509
1510 add_row(
1511 t,
1512 row![
1513 rule_set_priority,
1514 index,
1515 option(&from),
1516 option(&locally_generated),
1517 option(&bound_device),
1518 option(&mark_1),
1519 option(&mark_2),
1520 action,
1521 ],
1522 );
1523 }
1524
1525 for rule in v4_rules {
1526 write_rule(&mut t, rule);
1527 }
1528
1529 for rule in v6_rules {
1530 write_rule(&mut t, rule);
1531 }
1532
1533 let _lines_printed: usize = t.print(out)?;
1534 out.line("")?;
1535 }
1536 Ok(())
1537}
1538
1539async fn do_filter_deprecated<C: NetCliDepsConnector, W: std::io::Write>(
1540 mut out: W,
1541 cmd: opts::FilterDeprecatedEnum,
1542 connector: &C,
1543) -> Result<(), Error> {
1544 let filter = connect_with_context::<ffilter_deprecated::FilterMarker, _>(connector).await?;
1545 match cmd {
1546 opts::FilterDeprecatedEnum::GetRules(opts::FilterGetRules {}) => {
1547 let (rules, generation): (Vec<ffilter_deprecated::Rule>, u32) =
1548 filter.get_rules().await?;
1549 writeln!(out, "{:?} (generation {})", rules, generation)?;
1550 }
1551 opts::FilterDeprecatedEnum::SetRules(opts::FilterSetRules { rules }) => {
1552 let (_cur_rules, generation) = filter.get_rules().await?;
1553 let rules = netfilter::parser_deprecated::parse_str_to_rules(&rules)?;
1554 filter_fidl!(filter.update_rules(&rules, generation), "error setting filter rules")?;
1555 info!("successfully set filter rules");
1556 }
1557 opts::FilterDeprecatedEnum::GetNatRules(opts::FilterGetNatRules {}) => {
1558 let (rules, generation): (Vec<ffilter_deprecated::Nat>, u32) =
1559 filter.get_nat_rules().await?;
1560 writeln!(out, "{:?} (generation {})", rules, generation)?;
1561 }
1562 opts::FilterDeprecatedEnum::SetNatRules(opts::FilterSetNatRules { rules }) => {
1563 let (_cur_rules, generation) = filter.get_nat_rules().await?;
1564 let rules = netfilter::parser_deprecated::parse_str_to_nat_rules(&rules)?;
1565 filter_fidl!(filter.update_nat_rules(&rules, generation), "error setting NAT rules")?;
1566 info!("successfully set NAT rules");
1567 }
1568 opts::FilterDeprecatedEnum::GetRdrRules(opts::FilterGetRdrRules {}) => {
1569 let (rules, generation): (Vec<ffilter_deprecated::Rdr>, u32) =
1570 filter.get_rdr_rules().await?;
1571 writeln!(out, "{:?} (generation {})", rules, generation)?;
1572 }
1573 opts::FilterDeprecatedEnum::SetRdrRules(opts::FilterSetRdrRules { rules }) => {
1574 let (_cur_rules, generation) = filter.get_rdr_rules().await?;
1575 let rules = netfilter::parser_deprecated::parse_str_to_rdr_rules(&rules)?;
1576 filter_fidl!(filter.update_rdr_rules(&rules, generation), "error setting RDR rules")?;
1577 info!("successfully set RDR rules");
1578 }
1579 }
1580 Ok(())
1581}
1582
1583async fn do_log<C: NetCliDepsConnector>(cmd: opts::LogEnum, connector: &C) -> Result<(), Error> {
1584 let log = connect_with_context::<fstack::LogMarker, _>(connector).await?;
1585 match cmd {
1586 opts::LogEnum::SetPackets(opts::LogSetPackets { enabled }) => {
1587 log.set_log_packets(enabled).await.context("error setting log packets")?;
1588 info!("log packets set to {:?}", enabled);
1589 }
1590 }
1591 Ok(())
1592}
1593
1594async fn do_dhcp<C: NetCliDepsConnector>(cmd: opts::DhcpEnum, connector: &C) -> Result<(), Error> {
1595 let stack = connect_with_context::<fstack::StackMarker, _>(connector).await?;
1596 match cmd {
1597 opts::DhcpEnum::Start(opts::DhcpStart { interface }) => {
1598 let id = interface.find_nicid(connector).await?.get();
1599 fstack_ext::exec_fidl!(
1600 stack.set_dhcp_client_enabled(id, true),
1601 "error stopping DHCP client"
1602 )?;
1603 info!("dhcp client started on interface {}", id);
1604 }
1605 opts::DhcpEnum::Stop(opts::DhcpStop { interface }) => {
1606 let id = interface.find_nicid(connector).await?.get();
1607 fstack_ext::exec_fidl!(
1608 stack.set_dhcp_client_enabled(id, false),
1609 "error stopping DHCP client"
1610 )?;
1611 info!("dhcp client stopped on interface {}", id);
1612 }
1613 }
1614 Ok(())
1615}
1616
1617async fn do_dhcpd<C: NetCliDepsConnector>(
1618 cmd: opts::dhcpd::DhcpdEnum,
1619 connector: &C,
1620) -> Result<(), Error> {
1621 let dhcpd_server = connect_with_context::<fdhcp::Server_Marker, _>(connector).await?;
1622 match cmd {
1623 opts::dhcpd::DhcpdEnum::Start(opts::dhcpd::Start {}) => {
1624 Ok(do_dhcpd_start(dhcpd_server).await?)
1625 }
1626 opts::dhcpd::DhcpdEnum::Stop(opts::dhcpd::Stop {}) => {
1627 Ok(do_dhcpd_stop(dhcpd_server).await?)
1628 }
1629 opts::dhcpd::DhcpdEnum::Get(get_arg) => Ok(do_dhcpd_get(get_arg, dhcpd_server).await?),
1630 opts::dhcpd::DhcpdEnum::Set(set_arg) => Ok(do_dhcpd_set(set_arg, dhcpd_server).await?),
1631 opts::dhcpd::DhcpdEnum::List(list_arg) => Ok(do_dhcpd_list(list_arg, dhcpd_server).await?),
1632 opts::dhcpd::DhcpdEnum::Reset(reset_arg) => {
1633 Ok(do_dhcpd_reset(reset_arg, dhcpd_server).await?)
1634 }
1635 opts::dhcpd::DhcpdEnum::ClearLeases(opts::dhcpd::ClearLeases {}) => {
1636 Ok(do_dhcpd_clear_leases(dhcpd_server).await?)
1637 }
1638 }
1639}
1640
1641async fn do_neigh<C: NetCliDepsConnector>(
1642 out: writer::JsonWriter<serde_json::Value>,
1643 cmd: opts::NeighEnum,
1644 connector: &C,
1645) -> Result<(), Error> {
1646 match cmd {
1647 opts::NeighEnum::Add(opts::NeighAdd { interface, ip, mac }) => {
1648 let interface = interface.find_nicid(connector).await?.get();
1649 let controller =
1650 connect_with_context::<fneighbor::ControllerMarker, _>(connector).await?;
1651 do_neigh_add(interface, ip.into(), mac.into(), controller)
1652 .await
1653 .context("failed during neigh add command")?;
1654 info!("Added entry ({}, {}) for interface {}", ip, mac, interface);
1655 }
1656 opts::NeighEnum::Clear(opts::NeighClear { interface, ip_version }) => {
1657 let interface = interface.find_nicid(connector).await?.get();
1658 let controller =
1659 connect_with_context::<fneighbor::ControllerMarker, _>(connector).await?;
1660 do_neigh_clear(interface, ip_version, controller)
1661 .await
1662 .context("failed during neigh clear command")?;
1663 info!("Cleared entries for interface {}", interface);
1664 }
1665 opts::NeighEnum::Del(opts::NeighDel { interface, ip }) => {
1666 let interface = interface.find_nicid(connector).await?.get();
1667 let controller =
1668 connect_with_context::<fneighbor::ControllerMarker, _>(connector).await?;
1669 do_neigh_del(interface, ip.into(), controller)
1670 .await
1671 .context("failed during neigh del command")?;
1672 info!("Deleted entry {} for interface {}", ip, interface);
1673 }
1674 opts::NeighEnum::List(opts::NeighList {}) => {
1675 let view = connect_with_context::<fneighbor::ViewMarker, _>(connector).await?;
1676 print_neigh_entries(out, false , view)
1677 .await
1678 .context("error listing neighbor entries")?;
1679 }
1680 opts::NeighEnum::Watch(opts::NeighWatch {}) => {
1681 let view = connect_with_context::<fneighbor::ViewMarker, _>(connector).await?;
1682 print_neigh_entries(out, true , view)
1683 .await
1684 .context("error watching for changes to the neighbor table")?;
1685 }
1686 opts::NeighEnum::Config(opts::NeighConfig { neigh_config_cmd }) => match neigh_config_cmd {
1687 opts::NeighConfigEnum::Get(opts::NeighGetConfig { interface, ip_version }) => {
1688 let interface = interface.find_nicid(connector).await?.get();
1689 let control = get_control(connector, interface).await.context("get control")?;
1690 let configuration = control
1691 .get_configuration()
1692 .await
1693 .map_err(anyhow::Error::new)
1694 .and_then(|res| {
1695 res.map_err(|e: finterfaces_admin::ControlGetConfigurationError| {
1696 anyhow!("{:?}", e)
1697 })
1698 })
1699 .context("get configuration")?;
1700 let nud = extract_nud_config(configuration, ip_version)?;
1701 println!("{:#?}", nud);
1702 }
1703 opts::NeighConfigEnum::Update(opts::NeighUpdateConfig {
1704 interface,
1705 ip_version,
1706 base_reachable_time,
1707 }) => {
1708 let interface = interface.find_nicid(connector).await?.get();
1709 let control = get_control(connector, interface).await.context("get control")?;
1710 let nud_config = finterfaces_admin::NudConfiguration {
1711 base_reachable_time,
1712 ..Default::default()
1713 };
1714 let config = match ip_version {
1715 fnet::IpVersion::V4 => finterfaces_admin::Configuration {
1716 ipv4: Some(finterfaces_admin::Ipv4Configuration {
1717 arp: Some(finterfaces_admin::ArpConfiguration {
1718 nud: Some(nud_config),
1719 ..Default::default()
1720 }),
1721 ..Default::default()
1722 }),
1723 ..Default::default()
1724 },
1725 fnet::IpVersion::V6 => finterfaces_admin::Configuration {
1726 ipv6: Some(finterfaces_admin::Ipv6Configuration {
1727 ndp: Some(finterfaces_admin::NdpConfiguration {
1728 nud: Some(nud_config),
1729 ..Default::default()
1730 }),
1731 ..Default::default()
1732 }),
1733 ..Default::default()
1734 },
1735 };
1736 let prev_config = control
1737 .set_configuration(&config)
1738 .await
1739 .map_err(anyhow::Error::new)
1740 .and_then(|res| {
1741 res.map_err(|e: finterfaces_admin::ControlSetConfigurationError| {
1742 anyhow!("{:?}", e)
1743 })
1744 })
1745 .context("set configuration")?;
1746 let prev_nud = extract_nud_config(prev_config, ip_version)?;
1747 info!("Updated config for interface {}; previously was: {:?}", interface, prev_nud);
1748 }
1749 },
1750 }
1751 Ok(())
1752}
1753
1754async fn do_neigh_add(
1755 interface: u64,
1756 neighbor: fnet::IpAddress,
1757 mac: fnet::MacAddress,
1758 controller: fneighbor::ControllerProxy,
1759) -> Result<(), Error> {
1760 controller
1761 .add_entry(interface, &neighbor.into(), &mac.into())
1762 .await
1763 .context("FIDL error adding neighbor entry")?
1764 .map_err(|e| anyhow::anyhow!("error adding neighbor entry: {e:?}"))
1765}
1766
1767async fn do_neigh_clear(
1768 interface: u64,
1769 ip_version: fnet::IpVersion,
1770 controller: fneighbor::ControllerProxy,
1771) -> Result<(), Error> {
1772 controller
1773 .clear_entries(interface, ip_version)
1774 .await
1775 .context("FIDL error clearing neighbor table")?
1776 .map_err(|e| anyhow::anyhow!("error clearing neighbor table: {e:?}"))
1777}
1778
1779async fn do_neigh_del(
1780 interface: u64,
1781 neighbor: fnet::IpAddress,
1782 controller: fneighbor::ControllerProxy,
1783) -> Result<(), Error> {
1784 controller
1785 .remove_entry(interface, &neighbor.into())
1786 .await
1787 .context("FIDL error removing neighbor entry")?
1788 .map_err(|e| anyhow::anyhow!("error removing neighbor entry: {e:?}"))
1789}
1790
1791fn unpack_neigh_iter_item(
1792 item: fneighbor::EntryIteratorItem,
1793) -> Result<(&'static str, Option<fneighbor_ext::Entry>), Error> {
1794 let displayed_state_change_status = ser::DISPLAYED_NEIGH_ENTRY_VARIANTS.select(&item);
1795
1796 Ok((
1797 displayed_state_change_status,
1798 match item {
1799 fneighbor::EntryIteratorItem::Existing(entry)
1800 | fneighbor::EntryIteratorItem::Added(entry)
1801 | fneighbor::EntryIteratorItem::Changed(entry)
1802 | fneighbor::EntryIteratorItem::Removed(entry) => {
1803 Some(fneighbor_ext::Entry::try_from(entry)?)
1804 }
1805 fneighbor::EntryIteratorItem::Idle(fneighbor::IdleEvent) => None,
1806 },
1807 ))
1808}
1809
1810fn jsonify_neigh_iter_item(
1811 item: fneighbor::EntryIteratorItem,
1812 include_entry_state: bool,
1813) -> Result<Value, Error> {
1814 let (state_change_status, entry) = unpack_neigh_iter_item(item)?;
1815 let entry_json = entry
1816 .map(ser::NeighborTableEntry::from)
1817 .map(serde_json::to_value)
1818 .map(|res| res.map_err(Error::new))
1819 .unwrap_or_else(|| Err(anyhow!("failed to jsonify NeighborTableEntry")))?;
1820 if include_entry_state {
1821 Ok(json!({
1822 "state_change_status": state_change_status,
1823 "entry": entry_json,
1824 }))
1825 } else {
1826 Ok(entry_json)
1827 }
1828}
1829
1830async fn print_neigh_entries(
1831 mut out: writer::JsonWriter<serde_json::Value>,
1832 watch_for_changes: bool,
1833 view: fneighbor::ViewProxy,
1834) -> Result<(), Error> {
1835 let (it, it_server) = view.domain().create_proxy::<fneighbor::EntryIteratorMarker>();
1836
1837 view.open_entry_iterator(it_server, &fneighbor::EntryIteratorOptions::default())
1838 .context("error opening a connection to the entry iterator")?;
1839
1840 let out_ref = &mut out;
1841 if watch_for_changes {
1842 neigh_entry_stream(it, watch_for_changes)
1843 .map_ok(|item| {
1844 write_neigh_entry(out_ref, item, watch_for_changes)
1845 .context("error writing entry")
1846 })
1847 .try_fold((), |(), r| futures::future::ready(r))
1848 .await?;
1849 } else {
1850 let results: Vec<Result<fneighbor::EntryIteratorItem, _>> =
1851 neigh_entry_stream(it, watch_for_changes).collect().await;
1852 if out.is_machine() {
1853 let jsonified_items: Value =
1854 itertools::process_results(results.into_iter(), |items| {
1855 itertools::process_results(
1856 items.map(|item| {
1857 jsonify_neigh_iter_item(
1858 item,
1859 watch_for_changes,
1860 )
1861 }),
1862 |json_values| Value::from_iter(json_values),
1863 )
1864 })??;
1865 out.machine(&jsonified_items)?;
1866 } else {
1867 itertools::process_results(results.into_iter(), |mut items| {
1868 items.try_for_each(|item| {
1869 write_tabular_neigh_entry(
1870 &mut out,
1871 item,
1872 watch_for_changes,
1873 )
1874 })
1875 })??;
1876 }
1877 }
1878
1879 Ok(())
1880}
1881
1882fn neigh_entry_stream(
1883 iterator: fneighbor::EntryIteratorProxy,
1884 watch_for_changes: bool,
1885) -> impl futures::Stream<Item = Result<fneighbor::EntryIteratorItem, Error>> {
1886 futures::stream::try_unfold(iterator, |iterator| {
1887 iterator
1888 .get_next()
1889 .map_ok(|items| Some((items, iterator)))
1890 .map(|r| r.context("error getting items from iterator"))
1891 })
1892 .map_ok(|items| futures::stream::iter(items.into_iter().map(Ok)))
1893 .try_flatten()
1894 .take_while(move |item| {
1895 futures::future::ready(item.as_ref().is_ok_and(|item| {
1896 if let fneighbor::EntryIteratorItem::Idle(fneighbor::IdleEvent {}) = item {
1897 watch_for_changes
1898 } else {
1899 true
1900 }
1901 }))
1902 })
1903}
1904
1905fn write_tabular_neigh_entry<W: std::io::Write>(
1906 mut f: W,
1907 item: fneighbor::EntryIteratorItem,
1908 include_entry_state: bool,
1909) -> Result<(), Error> {
1910 let (state_change_status, entry) = unpack_neigh_iter_item(item)?;
1911 match entry {
1912 Some(entry) => {
1913 if include_entry_state {
1914 writeln!(
1915 &mut f,
1916 "{:width$} | {}",
1917 state_change_status,
1918 entry,
1919 width = ser::DISPLAYED_NEIGH_ENTRY_VARIANTS
1920 .into_iter()
1921 .map(|s| s.len())
1922 .max()
1923 .unwrap_or(0),
1924 )?
1925 } else {
1926 writeln!(&mut f, "{}", entry)?
1927 }
1928 }
1929 None => writeln!(&mut f, "{}", state_change_status)?,
1930 }
1931 Ok(())
1932}
1933
1934fn write_neigh_entry(
1935 f: &mut writer::JsonWriter<serde_json::Value>,
1936 item: fneighbor::EntryIteratorItem,
1937 include_entry_state: bool,
1938) -> Result<(), Error> {
1939 if f.is_machine() {
1940 let entry = jsonify_neigh_iter_item(item, include_entry_state)?;
1941 f.machine(&entry)?;
1942 } else {
1943 write_tabular_neigh_entry(f, item, include_entry_state)?
1944 }
1945 Ok(())
1946}
1947
1948async fn do_dhcpd_start(server: fdhcp::Server_Proxy) -> Result<(), Error> {
1949 server
1950 .start_serving()
1951 .await?
1952 .map_err(zx::Status::err_from_raw)
1953 .context("failed to start server")
1954}
1955
1956async fn do_dhcpd_stop(server: fdhcp::Server_Proxy) -> Result<(), Error> {
1957 server.stop_serving().await.context("failed to stop server")
1958}
1959
1960async fn do_dhcpd_get(get_arg: opts::dhcpd::Get, server: fdhcp::Server_Proxy) -> Result<(), Error> {
1961 match get_arg.arg {
1962 opts::dhcpd::GetArg::Option(opts::dhcpd::OptionArg { name }) => {
1963 let res = server
1964 .get_option(name.clone().into())
1965 .await?
1966 .map_err(zx::Status::err_from_raw)
1967 .with_context(|| format!("get_option({:?}) failed", name))?;
1968 println!("{:#?}", res);
1969 }
1970 opts::dhcpd::GetArg::Parameter(opts::dhcpd::ParameterArg { name }) => {
1971 let res = server
1972 .get_parameter(name.clone().into())
1973 .await?
1974 .map_err(zx::Status::err_from_raw)
1975 .with_context(|| format!("get_parameter({:?}) failed", name))?;
1976 println!("{:#?}", res);
1977 }
1978 };
1979 Ok(())
1980}
1981
1982async fn do_dhcpd_set(set_arg: opts::dhcpd::Set, server: fdhcp::Server_Proxy) -> Result<(), Error> {
1983 match set_arg.arg {
1984 opts::dhcpd::SetArg::Option(opts::dhcpd::OptionArg { name }) => {
1985 server
1986 .set_option(&name.clone().into())
1987 .await?
1988 .map_err(zx::Status::err_from_raw)
1989 .with_context(|| format!("set_option({:?}) failed", name))?;
1990 }
1991 opts::dhcpd::SetArg::Parameter(opts::dhcpd::ParameterArg { name }) => {
1992 server
1993 .set_parameter(&name.clone().into())
1994 .await?
1995 .map_err(zx::Status::err_from_raw)
1996 .with_context(|| format!("set_parameter({:?}) failed", name))?;
1997 }
1998 };
1999 Ok(())
2000}
2001
2002async fn do_dhcpd_list(
2003 list_arg: opts::dhcpd::List,
2004 server: fdhcp::Server_Proxy,
2005) -> Result<(), Error> {
2006 match list_arg.arg {
2007 opts::dhcpd::ListArg::Option(opts::dhcpd::OptionToken {}) => {
2008 let res = server
2009 .list_options()
2010 .await?
2011 .map_err(zx::Status::err_from_raw)
2012 .context("list_options() failed")?;
2013
2014 println!("{:#?}", res);
2015 }
2016 opts::dhcpd::ListArg::Parameter(opts::dhcpd::ParameterToken {}) => {
2017 let res = server
2018 .list_parameters()
2019 .await?
2020 .map_err(zx::Status::err_from_raw)
2021 .context("list_parameters() failed")?;
2022 println!("{:#?}", res);
2023 }
2024 };
2025 Ok(())
2026}
2027
2028async fn do_dhcpd_reset(
2029 reset_arg: opts::dhcpd::Reset,
2030 server: fdhcp::Server_Proxy,
2031) -> Result<(), Error> {
2032 match reset_arg.arg {
2033 opts::dhcpd::ResetArg::Option(opts::dhcpd::OptionToken {}) => {
2034 server
2035 .reset_options()
2036 .await?
2037 .map_err(zx::Status::err_from_raw)
2038 .context("reset_options() failed")?;
2039 }
2040 opts::dhcpd::ResetArg::Parameter(opts::dhcpd::ParameterToken {}) => {
2041 server
2042 .reset_parameters()
2043 .await?
2044 .map_err(zx::Status::err_from_raw)
2045 .context("reset_parameters() failed")?;
2046 }
2047 };
2048 Ok(())
2049}
2050
2051async fn do_dhcpd_clear_leases(server: fdhcp::Server_Proxy) -> Result<(), Error> {
2052 server.clear_leases().await?.map_err(zx::Status::err_from_raw).context("clear_leases() failed")
2053}
2054
2055async fn do_dns<W: std::io::Write, C: NetCliDepsConnector>(
2056 mut out: W,
2057 cmd: opts::dns::DnsEnum,
2058 connector: &C,
2059) -> Result<(), Error> {
2060 let lookup = connect_with_context::<fname::LookupMarker, _>(connector).await?;
2061 let opts::dns::DnsEnum::Lookup(opts::dns::Lookup { hostname, ipv4, ipv6, sort }) = cmd;
2062 let result = lookup
2063 .lookup_ip(
2064 &hostname,
2065 &fname::LookupIpOptions {
2066 ipv4_lookup: Some(ipv4),
2067 ipv6_lookup: Some(ipv6),
2068 sort_addresses: Some(sort),
2069 ..Default::default()
2070 },
2071 )
2072 .await?
2073 .map_err(|e| anyhow!("DNS lookup failed: {:?}", e))?;
2074 let fname::LookupResult { addresses, .. } = result;
2075 let addrs = addresses.context("`addresses` not set in response from DNS resolver")?;
2076 for addr in addrs {
2077 writeln!(out, "{}", fnet_ext::IpAddress::from(addr))?;
2078 }
2079 Ok(())
2080}
2081
2082#[cfg(test)]
2083mod testutil {
2084 use flex_client::fidl::ProtocolMarker;
2085
2086 use super::*;
2087
2088 #[derive(Default)]
2089 pub(crate) struct TestConnector {
2090 pub debug_interfaces: Option<fdebug::InterfacesProxy>,
2091 pub dhcpd: Option<fdhcp::Server_Proxy>,
2092 pub interfaces_state: Option<finterfaces::StateProxy>,
2093 pub stack: Option<fstack::StackProxy>,
2094 pub root_interfaces: Option<froot::InterfacesProxy>,
2095 pub root_filter: Option<froot::FilterProxy>,
2096 pub root_routes_v4: Option<froot::RoutesV4Proxy>,
2097 pub root_routes_v6: Option<froot::RoutesV6Proxy>,
2098 pub routes_v4: Option<froutes::StateV4Proxy>,
2099 pub routes_v6: Option<froutes::StateV6Proxy>,
2100 pub name_lookup: Option<fname::LookupProxy>,
2101 pub filter: Option<fnet_filter::StateProxy>,
2102 pub installer: Option<finterfaces_admin::InstallerProxy>,
2103 pub packet_capture_provider: Option<fdebug::PacketCaptureProviderProxy>,
2104 }
2105
2106 impl CaptureDeps for TestConnector {
2107 type OutputWriter = std::io::Sink;
2108 fn create_output_writer(
2109 &self,
2110 _path: &std::path::Path,
2111 ) -> Result<Self::OutputWriter, anyhow::Error> {
2112 Ok(std::io::sink())
2113 }
2114 }
2115
2116 #[async_trait::async_trait]
2117 impl ServiceConnector<fdebug::InterfacesMarker> for TestConnector {
2118 async fn connect(
2119 &self,
2120 ) -> Result<<fdebug::InterfacesMarker as ProtocolMarker>::Proxy, Error> {
2121 self.debug_interfaces
2122 .as_ref()
2123 .cloned()
2124 .ok_or_else(|| anyhow!("connector has no dhcp server instance"))
2125 }
2126 }
2127
2128 #[async_trait::async_trait]
2129 impl ServiceConnector<froot::InterfacesMarker> for TestConnector {
2130 async fn connect(
2131 &self,
2132 ) -> Result<<froot::InterfacesMarker as ProtocolMarker>::Proxy, Error> {
2133 self.root_interfaces
2134 .as_ref()
2135 .cloned()
2136 .ok_or_else(|| anyhow!("connector has no root interfaces instance"))
2137 }
2138 }
2139
2140 #[async_trait::async_trait]
2141 impl ServiceConnector<froot::FilterMarker> for TestConnector {
2142 async fn connect(&self) -> Result<<froot::FilterMarker as ProtocolMarker>::Proxy, Error> {
2143 self.root_filter
2144 .as_ref()
2145 .cloned()
2146 .ok_or_else(|| anyhow!("connector has no root filter instance"))
2147 }
2148 }
2149
2150 #[async_trait::async_trait]
2151 impl ServiceConnector<fdhcp::Server_Marker> for TestConnector {
2152 async fn connect(&self) -> Result<<fdhcp::Server_Marker as ProtocolMarker>::Proxy, Error> {
2153 self.dhcpd
2154 .as_ref()
2155 .cloned()
2156 .ok_or_else(|| anyhow!("connector has no dhcp server instance"))
2157 }
2158 }
2159
2160 #[async_trait::async_trait]
2161 impl ServiceConnector<ffilter_deprecated::FilterMarker> for TestConnector {
2162 async fn connect(
2163 &self,
2164 ) -> Result<<ffilter_deprecated::FilterMarker as ProtocolMarker>::Proxy, Error> {
2165 Err(anyhow!("connect filter_deprecated unimplemented for test connector"))
2166 }
2167 }
2168
2169 #[async_trait::async_trait]
2170 impl ServiceConnector<finterfaces::StateMarker> for TestConnector {
2171 async fn connect(
2172 &self,
2173 ) -> Result<<finterfaces::StateMarker as ProtocolMarker>::Proxy, Error> {
2174 self.interfaces_state
2175 .as_ref()
2176 .cloned()
2177 .ok_or_else(|| anyhow!("connector has no interfaces state instance"))
2178 }
2179 }
2180
2181 #[async_trait::async_trait]
2182 impl ServiceConnector<finterfaces_admin::InstallerMarker> for TestConnector {
2183 async fn connect(
2184 &self,
2185 ) -> Result<<finterfaces_admin::InstallerMarker as ProtocolMarker>::Proxy, Error> {
2186 self.installer
2187 .as_ref()
2188 .cloned()
2189 .ok_or_else(|| anyhow!("connector has no fuchsia.net.interfaces.admin.Installer"))
2190 }
2191 }
2192
2193 #[async_trait::async_trait]
2194 impl ServiceConnector<fdebug::PacketCaptureProviderMarker> for TestConnector {
2195 async fn connect(
2196 &self,
2197 ) -> Result<<fdebug::PacketCaptureProviderMarker as ProtocolMarker>::Proxy, Error> {
2198 self.packet_capture_provider
2199 .as_ref()
2200 .cloned()
2201 .ok_or_else(|| anyhow!("connector has no packet capture provider instance"))
2202 }
2203 }
2204
2205 #[async_trait::async_trait]
2206 impl ServiceConnector<fneighbor::ControllerMarker> for TestConnector {
2207 async fn connect(
2208 &self,
2209 ) -> Result<<fneighbor::ControllerMarker as ProtocolMarker>::Proxy, Error> {
2210 Err(anyhow!("connect neighbor controller unimplemented for test connector"))
2211 }
2212 }
2213
2214 #[async_trait::async_trait]
2215 impl ServiceConnector<fneighbor::ViewMarker> for TestConnector {
2216 async fn connect(&self) -> Result<<fneighbor::ViewMarker as ProtocolMarker>::Proxy, Error> {
2217 Err(anyhow!("connect neighbor view unimplemented for test connector"))
2218 }
2219 }
2220
2221 #[async_trait::async_trait]
2222 impl ServiceConnector<fstack::LogMarker> for TestConnector {
2223 async fn connect(&self) -> Result<<fstack::LogMarker as ProtocolMarker>::Proxy, Error> {
2224 Err(anyhow!("connect log unimplemented for test connector"))
2225 }
2226 }
2227
2228 #[async_trait::async_trait]
2229 impl ServiceConnector<fstack::StackMarker> for TestConnector {
2230 async fn connect(&self) -> Result<<fstack::StackMarker as ProtocolMarker>::Proxy, Error> {
2231 self.stack.as_ref().cloned().ok_or_else(|| anyhow!("connector has no stack instance"))
2232 }
2233 }
2234
2235 #[async_trait::async_trait]
2236 impl ServiceConnector<froutes::StateV4Marker> for TestConnector {
2237 async fn connect(
2238 &self,
2239 ) -> Result<<froutes::StateV4Marker as ProtocolMarker>::Proxy, Error> {
2240 self.routes_v4
2241 .as_ref()
2242 .cloned()
2243 .ok_or_else(|| anyhow!("connector has no routes_v4 instance"))
2244 }
2245 }
2246
2247 #[async_trait::async_trait]
2248 impl ServiceConnector<froutes::StateV6Marker> for TestConnector {
2249 async fn connect(
2250 &self,
2251 ) -> Result<<froutes::StateV6Marker as ProtocolMarker>::Proxy, Error> {
2252 self.routes_v6
2253 .as_ref()
2254 .cloned()
2255 .ok_or_else(|| anyhow!("connector has no routes_v6 instance"))
2256 }
2257 }
2258
2259 #[async_trait::async_trait]
2260 impl ServiceConnector<froot::RoutesV4Marker> for TestConnector {
2261 async fn connect(&self) -> Result<<froot::RoutesV4Marker as ProtocolMarker>::Proxy, Error> {
2262 self.root_routes_v4
2263 .as_ref()
2264 .cloned()
2265 .ok_or_else(|| anyhow!("connector has no root_routes_v4 instance"))
2266 }
2267 }
2268
2269 #[async_trait::async_trait]
2270 impl ServiceConnector<froot::RoutesV6Marker> for TestConnector {
2271 async fn connect(&self) -> Result<<froot::RoutesV6Marker as ProtocolMarker>::Proxy, Error> {
2272 self.root_routes_v6
2273 .as_ref()
2274 .cloned()
2275 .ok_or_else(|| anyhow!("connector has no root_routes_v6 instance"))
2276 }
2277 }
2278
2279 #[async_trait::async_trait]
2280 impl ServiceConnector<fname::LookupMarker> for TestConnector {
2281 async fn connect(&self) -> Result<<fname::LookupMarker as ProtocolMarker>::Proxy, Error> {
2282 self.name_lookup
2283 .as_ref()
2284 .cloned()
2285 .ok_or_else(|| anyhow!("connector has no name lookup instance"))
2286 }
2287 }
2288
2289 #[async_trait::async_trait]
2290 impl ServiceConnector<fnet_filter::StateMarker> for TestConnector {
2291 async fn connect(
2292 &self,
2293 ) -> Result<<fnet_filter::StateMarker as ProtocolMarker>::Proxy, Error> {
2294 self.filter.as_ref().cloned().ok_or_else(|| anyhow!("connector has no filter instance"))
2295 }
2296 }
2297}
2298
2299#[cfg(test)]
2300mod tests {
2301 use std::convert::TryInto as _;
2302 use std::fmt::Debug;
2303 use std::num::NonZeroU64;
2304
2305 use assert_matches::assert_matches;
2306 #[cfg(not(feature = "fdomain"))]
2307 use fidl_fuchsia_net_routes_ext as froutes_ext;
2308 #[cfg(feature = "fdomain")]
2309 use fidl_fuchsia_net_routes_ext_fdomain as froutes_ext;
2310 use flex_fuchsia_net_routes as froutes;
2311 use fuchsia_async::TimeoutExt as _;
2312 use net_declare::{fidl_ip, fidl_ip_v4, fidl_mac, fidl_subnet};
2313 use test_case::test_case;
2314
2315 use super::testutil::TestConnector;
2316 use super::*;
2317
2318 const IF_ADDR_V4: fnet::Subnet = fidl_subnet!("192.168.0.1/32");
2319 const IF_ADDR_V6: fnet::Subnet = fidl_subnet!("fd00::1/64");
2320
2321 const MAC_1: fnet::MacAddress = fidl_mac!("01:02:03:04:05:06");
2322 const MAC_2: fnet::MacAddress = fidl_mac!("02:03:04:05:06:07");
2323
2324 fn trim_whitespace_for_comparison(s: &str) -> String {
2325 s.trim().lines().map(|s| s.trim()).collect::<Vec<&str>>().join("\n")
2326 }
2327
2328 fn get_fake_interface(
2329 id: u64,
2330 name: &'static str,
2331 port_class: finterfaces_ext::PortClass,
2332 octets: Option<[u8; 6]>,
2333 ) -> (finterfaces_ext::Properties<finterfaces_ext::AllInterest>, Option<fnet::MacAddress>) {
2334 let port_identity_koid = match port_class {
2335 finterfaces_ext::PortClass::Loopback => None,
2336 finterfaces_ext::PortClass::Virtual
2337 | finterfaces_ext::PortClass::Ethernet
2338 | finterfaces_ext::PortClass::WlanClient
2339 | finterfaces_ext::PortClass::WlanAp
2340 | finterfaces_ext::PortClass::Ppp
2341 | finterfaces_ext::PortClass::Bridge
2342 | finterfaces_ext::PortClass::Lowpan
2343 | finterfaces_ext::PortClass::Blackhole => {
2344 Some(finterfaces_ext::PortIdentityKoid::from_raw(id))
2345 }
2346 };
2347 (
2348 finterfaces_ext::Properties {
2349 id: id.try_into().unwrap(),
2350 name: name.to_string(),
2351 port_class,
2352 online: true,
2353 addresses: Vec::new(),
2354 has_default_ipv4_route: false,
2355 has_default_ipv6_route: false,
2356 port_identity_koid,
2357 },
2358 octets.map(|octets| fnet::MacAddress { octets }),
2359 )
2360 }
2361
2362 fn shortlist_interfaces_by_nicid(name_pattern: &str) -> Vec<u64> {
2363 let mut interfaces = [
2364 get_fake_interface(1, "lo", finterfaces_ext::PortClass::Loopback, None),
2365 get_fake_interface(
2366 10,
2367 "eth001",
2368 finterfaces_ext::PortClass::Ethernet,
2369 Some([1, 2, 3, 4, 5, 6]),
2370 ),
2371 get_fake_interface(
2372 20,
2373 "eth002",
2374 finterfaces_ext::PortClass::Ethernet,
2375 Some([1, 2, 3, 4, 5, 7]),
2376 ),
2377 get_fake_interface(
2378 30,
2379 "eth003",
2380 finterfaces_ext::PortClass::Ethernet,
2381 Some([1, 2, 3, 4, 5, 8]),
2382 ),
2383 get_fake_interface(
2384 100,
2385 "wlan001",
2386 finterfaces_ext::PortClass::WlanClient,
2387 Some([2, 2, 3, 4, 5, 6]),
2388 ),
2389 get_fake_interface(
2390 200,
2391 "wlan002",
2392 finterfaces_ext::PortClass::WlanClient,
2393 Some([2, 2, 3, 4, 5, 7]),
2394 ),
2395 get_fake_interface(
2396 300,
2397 "wlan003",
2398 finterfaces_ext::PortClass::WlanClient,
2399 Some([2, 2, 3, 4, 5, 8]),
2400 ),
2401 ]
2402 .into_iter()
2403 .map(|(properties, _): (_, Option<fnet::MacAddress>)| {
2404 let finterfaces_ext::Properties { id, .. } = &properties;
2405 (id.get(), finterfaces_ext::PropertiesAndState { properties, state: () })
2406 })
2407 .collect();
2408 shortlist_interfaces(name_pattern, &mut interfaces);
2409 let mut interfaces: Vec<_> = interfaces.into_keys().collect();
2410 interfaces.sort();
2411 interfaces
2412 }
2413
2414 #[test]
2415 fn test_shortlist_interfaces() {
2416 assert_eq!(vec![1, 10, 20, 30, 100, 200, 300], shortlist_interfaces_by_nicid(""));
2417 assert_eq!(vec![0_u64; 0], shortlist_interfaces_by_nicid("no such thing"));
2418
2419 assert_eq!(vec![1], shortlist_interfaces_by_nicid("lo"));
2420 assert_eq!(vec![10, 20, 30], shortlist_interfaces_by_nicid("eth"));
2421 assert_eq!(vec![10, 20, 30], shortlist_interfaces_by_nicid("th"));
2422 assert_eq!(vec![100, 200, 300], shortlist_interfaces_by_nicid("wlan"));
2423 assert_eq!(vec![10, 100], shortlist_interfaces_by_nicid("001"));
2424 }
2425
2426 #[test_case(fnet::IpVersion::V4, true ; "IPv4 enable routing")]
2427 #[test_case(fnet::IpVersion::V4, false ; "IPv4 disable routing")]
2428 #[test_case(fnet::IpVersion::V6, true ; "IPv6 enable routing")]
2429 #[test_case(fnet::IpVersion::V6, false ; "IPv6 disable routing")]
2430 #[fuchsia::test(logging = false)]
2431 async fn if_ip_forward(ip_version: fnet::IpVersion, enable: bool) {
2432 let client = flex_local::local_client_empty();
2433
2434 let interface1 = TestInterface { nicid: 1, name: "interface1" };
2435 let (root_interfaces, mut requests) =
2436 client.create_proxy_and_stream::<froot::InterfacesMarker>();
2437 let connector =
2438 TestConnector { root_interfaces: Some(root_interfaces), ..Default::default() };
2439
2440 let requests_fut = set_configuration_request(
2441 &mut requests,
2442 interface1.nicid,
2443 |c| extract_ip_forwarding(c, ip_version).expect("extract IP forwarding configuration"),
2444 enable,
2445 );
2446 let buf = writer::TestBuffers::default();
2447 let mut out = writer::JsonWriter::new_test(None, &buf);
2448 let do_if_fut = do_if(
2449 &mut out,
2450 opts::IfEnum::IpForward(opts::IfIpForward {
2451 cmd: opts::IfIpForwardEnum::Set(opts::IfIpForwardSet {
2452 interface: interface1.identifier(false ),
2453 ip_version,
2454 enable,
2455 }),
2456 }),
2457 &connector,
2458 );
2459 let ((), ()) = futures::future::try_join(do_if_fut, requests_fut.map(Ok))
2460 .await
2461 .expect("setting interface ip forwarding should succeed");
2462
2463 let requests_fut = get_configuration_request(
2464 &mut requests,
2465 interface1.nicid,
2466 configuration_with_ip_forwarding_set(ip_version, enable),
2467 );
2468 let buf = writer::TestBuffers::default();
2469 let mut out = writer::JsonWriter::new_test(None, &buf);
2470 let do_if_fut = do_if(
2471 &mut out,
2472 opts::IfEnum::IpForward(opts::IfIpForward {
2473 cmd: opts::IfIpForwardEnum::Get(opts::IfIpForwardGet {
2474 interface: interface1.identifier(false ),
2475 ip_version,
2476 }),
2477 }),
2478 &connector,
2479 );
2480 let ((), ()) = futures::future::try_join(do_if_fut, requests_fut.map(Ok))
2481 .await
2482 .expect("getting interface ip forwarding should succeed");
2483 let got_output = buf.into_stdout_str();
2484 pretty_assertions::assert_eq!(
2485 trim_whitespace_for_comparison(&got_output),
2486 trim_whitespace_for_comparison(&format!(
2487 "IP forwarding for {:?} is {} on interface {}",
2488 ip_version, enable, interface1.nicid
2489 )),
2490 )
2491 }
2492
2493 async fn set_configuration_request<
2494 O: Debug + PartialEq,
2495 F: FnOnce(finterfaces_admin::Configuration) -> O,
2496 >(
2497 requests: &mut froot::InterfacesRequestStream,
2498 expected_nicid: u64,
2499 extract_config: F,
2500 expected_config: O,
2501 ) {
2502 let (id, control, _control_handle) = requests
2503 .next()
2504 .await
2505 .expect("root request stream not ended")
2506 .expect("root request stream not error")
2507 .into_get_admin()
2508 .expect("get admin request");
2509 assert_eq!(id, expected_nicid);
2510
2511 let mut control: finterfaces_admin::ControlRequestStream = control.into_stream();
2512 let (configuration, responder) = control
2513 .next()
2514 .await
2515 .expect("control request stream not ended")
2516 .expect("control request stream not error")
2517 .into_set_configuration()
2518 .expect("set configuration request");
2519 assert_eq!(extract_config(configuration), expected_config);
2520 responder.send(Ok(&Default::default())).expect("responder.send should succeed");
2523 }
2524
2525 async fn get_configuration_request(
2526 requests: &mut froot::InterfacesRequestStream,
2527 expected_nicid: u64,
2528 config: finterfaces_admin::Configuration,
2529 ) {
2530 let (id, control, _control_handle) = requests
2531 .next()
2532 .await
2533 .expect("root request stream not ended")
2534 .expect("root request stream not error")
2535 .into_get_admin()
2536 .expect("get admin request");
2537 assert_eq!(id, expected_nicid);
2538
2539 let mut control: finterfaces_admin::ControlRequestStream = control.into_stream();
2540 let responder = control
2541 .next()
2542 .await
2543 .expect("control request stream not ended")
2544 .expect("control request stream not error")
2545 .into_get_configuration()
2546 .expect("get configuration request");
2547 responder.send(Ok(&config)).expect("responder.send should succeed");
2548 }
2549
2550 #[test_case(finterfaces_admin::IgmpVersion::V1)]
2551 #[test_case(finterfaces_admin::IgmpVersion::V2)]
2552 #[test_case(finterfaces_admin::IgmpVersion::V3)]
2553 #[fuchsia::test(logging = false)]
2554 async fn if_igmp(igmp_version: finterfaces_admin::IgmpVersion) {
2555 let client = flex_local::local_client_empty();
2556
2557 let interface1 = TestInterface { nicid: 1, name: "interface1" };
2558 let (root_interfaces, mut requests) =
2559 client.create_proxy_and_stream::<froot::InterfacesMarker>();
2560 let connector =
2561 TestConnector { root_interfaces: Some(root_interfaces), ..Default::default() };
2562
2563 let requests_fut = set_configuration_request(
2564 &mut requests,
2565 interface1.nicid,
2566 |c| extract_igmp_version(c).unwrap(),
2567 Some(igmp_version),
2568 );
2569 let buffers = writer::TestBuffers::default();
2570 let mut out = writer::JsonWriter::new_test(None, &buffers);
2571 let do_if_fut = do_if(
2572 &mut out,
2573 opts::IfEnum::Igmp(opts::IfIgmp {
2574 cmd: opts::IfIgmpEnum::Set(opts::IfIgmpSet {
2575 interface: interface1.identifier(false ),
2576 version: Some(igmp_version),
2577 }),
2578 }),
2579 &connector,
2580 );
2581 let ((), ()) = futures::future::try_join(do_if_fut, requests_fut.map(Ok))
2582 .await
2583 .expect("setting interface IGMP configuration should succeed");
2584
2585 let requests_fut = get_configuration_request(
2586 &mut requests,
2587 interface1.nicid,
2588 finterfaces_admin::Configuration {
2589 ipv4: Some(finterfaces_admin::Ipv4Configuration {
2590 igmp: Some(finterfaces_admin::IgmpConfiguration {
2591 version: Some(igmp_version),
2592 ..Default::default()
2593 }),
2594 ..Default::default()
2595 }),
2596 ..Default::default()
2597 },
2598 );
2599 let buffers = writer::TestBuffers::default();
2600 let mut output_buf = writer::JsonWriter::new_test(None, &buffers);
2601 let do_if_fut = do_if(
2602 &mut output_buf,
2603 opts::IfEnum::Igmp(opts::IfIgmp {
2604 cmd: opts::IfIgmpEnum::Get(opts::IfIgmpGet {
2605 interface: interface1.identifier(false ),
2606 }),
2607 }),
2608 &connector,
2609 );
2610 let ((), ()) = futures::future::try_join(do_if_fut, requests_fut.map(Ok))
2611 .await
2612 .expect("getting interface IGMP configuration should succeed");
2613 let got_output = buffers.into_stdout_str();
2614 pretty_assertions::assert_eq!(
2615 trim_whitespace_for_comparison(&got_output),
2616 trim_whitespace_for_comparison(&format!(
2617 "IGMP configuration on interface {}:\n Version: {:?}",
2618 interface1.nicid,
2619 Some(igmp_version),
2620 )),
2621 )
2622 }
2623
2624 #[test_case(finterfaces_admin::MldVersion::V1)]
2625 #[test_case(finterfaces_admin::MldVersion::V2)]
2626 #[fuchsia::test(logging = false)]
2627 async fn if_mld(mld_version: finterfaces_admin::MldVersion) {
2628 let client = flex_local::local_client_empty();
2629
2630 let interface1 = TestInterface { nicid: 1, name: "interface1" };
2631 let (root_interfaces, mut requests) =
2632 client.create_proxy_and_stream::<froot::InterfacesMarker>();
2633 let connector =
2634 TestConnector { root_interfaces: Some(root_interfaces), ..Default::default() };
2635
2636 let requests_fut = set_configuration_request(
2637 &mut requests,
2638 interface1.nicid,
2639 |c| extract_mld_version(c).unwrap(),
2640 Some(mld_version),
2641 );
2642 let buffers = writer::TestBuffers::default();
2643 let mut out = writer::JsonWriter::new_test(None, &buffers);
2644 let do_if_fut = do_if(
2645 &mut out,
2646 opts::IfEnum::Mld(opts::IfMld {
2647 cmd: opts::IfMldEnum::Set(opts::IfMldSet {
2648 interface: interface1.identifier(false ),
2649 version: Some(mld_version),
2650 }),
2651 }),
2652 &connector,
2653 );
2654 let ((), ()) = futures::future::try_join(do_if_fut, requests_fut.map(Ok))
2655 .await
2656 .expect("setting interface MLD configuration should succeed");
2657
2658 let requests_fut = get_configuration_request(
2659 &mut requests,
2660 interface1.nicid,
2661 finterfaces_admin::Configuration {
2662 ipv6: Some(finterfaces_admin::Ipv6Configuration {
2663 mld: Some(finterfaces_admin::MldConfiguration {
2664 version: Some(mld_version),
2665 ..Default::default()
2666 }),
2667 ..Default::default()
2668 }),
2669 ..Default::default()
2670 },
2671 );
2672 let buffers = writer::TestBuffers::default();
2673 let mut output_buf = writer::JsonWriter::new_test(None, &buffers);
2674 let do_if_fut = do_if(
2675 &mut output_buf,
2676 opts::IfEnum::Mld(opts::IfMld {
2677 cmd: opts::IfMldEnum::Get(opts::IfMldGet {
2678 interface: interface1.identifier(false ),
2679 }),
2680 }),
2681 &connector,
2682 );
2683 let ((), ()) = futures::future::try_join(do_if_fut, requests_fut.map(Ok))
2684 .await
2685 .expect("getting interface MLD configuration should succeed");
2686 let got_output = buffers.into_stdout_str();
2687 pretty_assertions::assert_eq!(
2688 trim_whitespace_for_comparison(&got_output),
2689 trim_whitespace_for_comparison(&format!(
2690 "MLD configuration on interface {}:\n Version: {:?}",
2691 interface1.nicid,
2692 Some(mld_version),
2693 )),
2694 )
2695 }
2696
2697 async fn always_answer_with_interfaces(
2698 interfaces_state_requests: finterfaces::StateRequestStream,
2699 interfaces: Vec<finterfaces::Properties>,
2700 ) {
2701 interfaces_state_requests
2702 .try_for_each(|request| {
2703 let interfaces = interfaces.clone();
2704 async move {
2705 let (finterfaces::WatcherOptions { .. }, server_end, _): (
2706 _,
2707 _,
2708 finterfaces::StateControlHandle,
2709 ) = request.into_get_watcher().expect("request type should be GetWatcher");
2710
2711 let mut watcher_request_stream: finterfaces::WatcherRequestStream =
2712 server_end.into_stream();
2713
2714 for event in interfaces
2715 .into_iter()
2716 .map(finterfaces::Event::Existing)
2717 .chain(std::iter::once(finterfaces::Event::Idle(finterfaces::Empty)))
2718 {
2719 watcher_request_stream
2720 .try_next()
2721 .await
2722 .expect("watcher watch FIDL error")
2723 .expect("watcher request stream should not have ended")
2724 .into_watch()
2725 .expect("request should be of type Watch")
2726 .send(&event)
2727 .expect("responder.send should succeed");
2728 }
2729
2730 assert_matches!(
2731 watcher_request_stream.try_next().await.expect("watcher watch FIDL error"),
2732 None,
2733 "remaining watcher request stream should be empty"
2734 );
2735 Ok(())
2736 }
2737 })
2738 .await
2739 .expect("interfaces state FIDL error")
2740 }
2741
2742 #[derive(Clone)]
2743 struct TestInterface {
2744 nicid: u64,
2745 name: &'static str,
2746 }
2747
2748 impl TestInterface {
2749 fn identifier(&self, use_ifname: bool) -> opts::InterfaceIdentifier {
2750 let Self { nicid, name } = self;
2751 if use_ifname {
2752 opts::InterfaceIdentifier::Name(name.to_string())
2753 } else {
2754 opts::InterfaceIdentifier::Id(NonZeroU64::new(*nicid).unwrap())
2755 }
2756 }
2757 }
2758
2759 #[test_case(true, false ; "when interface is up, and adding subnet route")]
2760 #[test_case(true, true ; "when interface is up, and not adding subnet route")]
2761 #[test_case(false, false ; "when interface is down, and adding subnet route")]
2762 #[test_case(false, true ; "when interface is down, and not adding subnet route")]
2763 #[fuchsia::test]
2764 async fn if_addr_add(interface_is_up: bool, no_subnet_route: bool) {
2765 let client = flex_local::local_client_empty();
2766
2767 const TEST_PREFIX_LENGTH: u8 = 64;
2768
2769 let interface1 = TestInterface { nicid: 1, name: "interface1" };
2770 let (root_interfaces, mut requests) =
2771 client.create_proxy_and_stream::<froot::InterfacesMarker>();
2772
2773 let connector =
2774 TestConnector { root_interfaces: Some(root_interfaces), ..Default::default() };
2775 let buffers = writer::TestBuffers::default();
2776 let mut out = writer::JsonWriter::new_test(None, &buffers);
2777 let do_if_fut = do_if(
2778 &mut out,
2779 opts::IfEnum::Addr(opts::IfAddr {
2780 addr_cmd: opts::IfAddrEnum::Add(opts::IfAddrAdd {
2781 interface: interface1.identifier(false ),
2782 addr: fnet_ext::IpAddress::from(IF_ADDR_V6.addr).to_string(),
2783 prefix: TEST_PREFIX_LENGTH,
2784 no_subnet_route,
2785 }),
2786 }),
2787 &connector,
2788 )
2789 .map(|res| res.expect("success"));
2790
2791 let admin_fut = async {
2792 let (id, control, _control_handle) = requests
2793 .next()
2794 .await
2795 .expect("root request stream not ended")
2796 .expect("root request stream not error")
2797 .into_get_admin()
2798 .expect("get admin request");
2799 assert_eq!(id, interface1.nicid);
2800
2801 let mut control: finterfaces_admin::ControlRequestStream = control.into_stream();
2802 let (
2803 addr,
2804 addr_params,
2805 address_state_provider_server_end,
2806 _admin_control_control_handle,
2807 ) = control
2808 .next()
2809 .await
2810 .expect("control request stream not ended")
2811 .expect("control request stream not error")
2812 .into_add_address()
2813 .expect("add address request");
2814 assert_eq!(addr, IF_ADDR_V6);
2815 assert_eq!(
2816 addr_params,
2817 finterfaces_admin::AddressParameters {
2818 add_subnet_route: Some(!no_subnet_route),
2819 ..Default::default()
2820 }
2821 );
2822
2823 let mut address_state_provider_request_stream =
2824 address_state_provider_server_end.into_stream();
2825 async fn next_request(
2826 stream: &mut finterfaces_admin::AddressStateProviderRequestStream,
2827 ) -> finterfaces_admin::AddressStateProviderRequest {
2828 stream
2829 .next()
2830 .await
2831 .expect("address state provider request stream not ended")
2832 .expect("address state provider request stream not error")
2833 }
2834
2835 let _address_state_provider_control_handle =
2836 next_request(&mut address_state_provider_request_stream)
2837 .await
2838 .into_detach()
2839 .expect("detach request");
2840
2841 for _ in 0..3 {
2842 next_request(&mut address_state_provider_request_stream)
2843 .await
2844 .into_watch_address_assignment_state()
2845 .expect("watch address assignment state request")
2846 .send(finterfaces::AddressAssignmentState::Tentative)
2847 .expect("send address assignment state succeeds");
2848 }
2849
2850 next_request(&mut address_state_provider_request_stream)
2851 .await
2852 .into_watch_address_assignment_state()
2853 .expect("watch address assignment state request")
2854 .send(if interface_is_up {
2855 finterfaces::AddressAssignmentState::Assigned
2856 } else {
2857 finterfaces::AddressAssignmentState::Unavailable
2858 })
2859 .expect("send address assignment state succeeds");
2860 };
2861
2862 let ((), ()) = futures::join!(admin_fut, do_if_fut);
2863 }
2864
2865 #[test_case(false ; "providing nicids")]
2866 #[test_case(true ; "providing interface names")]
2867 #[fuchsia::test]
2868 async fn if_del_addr(use_ifname: bool) {
2869 let client = flex_local::local_client_empty();
2870
2871 let interface1 = TestInterface { nicid: 1, name: "interface1" };
2872 let interface2 = TestInterface { nicid: 2, name: "interface2" };
2873
2874 let (root_interfaces, mut requests) =
2875 client.create_proxy_and_stream::<froot::InterfacesMarker>();
2876 let (interfaces_state, interfaces_requests) =
2877 client.create_proxy_and_stream::<finterfaces::StateMarker>();
2878
2879 let (interface1_properties, _mac) = get_fake_interface(
2880 interface1.nicid,
2881 interface1.name,
2882 finterfaces_ext::PortClass::Ethernet,
2883 None,
2884 );
2885
2886 let interfaces_fut =
2887 always_answer_with_interfaces(interfaces_requests, vec![interface1_properties.into()])
2888 .fuse();
2889 let mut interfaces_fut = pin!(interfaces_fut);
2890
2891 let connector = TestConnector {
2892 root_interfaces: Some(root_interfaces),
2893 interfaces_state: Some(interfaces_state),
2894 ..Default::default()
2895 };
2896
2897 let buffers = writer::TestBuffers::default();
2898 let mut out = writer::JsonWriter::new_test(None, &buffers);
2899 let succeeds = do_if(
2901 &mut out,
2902 opts::IfEnum::Addr(opts::IfAddr {
2903 addr_cmd: opts::IfAddrEnum::Del(opts::IfAddrDel {
2904 interface: interface1.identifier(use_ifname),
2905 addr: fnet_ext::IpAddress::from(IF_ADDR_V4.addr).to_string(),
2906 prefix: None, }),
2908 }),
2909 &connector,
2910 )
2911 .map(|res| res.expect("success"));
2912 let handler_fut = async {
2913 let (id, control, _control_handle) = requests
2914 .next()
2915 .await
2916 .expect("root request stream not ended")
2917 .expect("root request stream not error")
2918 .into_get_admin()
2919 .expect("get admin request");
2920 assert_eq!(id, interface1.nicid);
2921 let mut control = control.into_stream();
2922 let (addr, responder) = control
2923 .next()
2924 .await
2925 .expect("control request stream not ended")
2926 .expect("control request stream not error")
2927 .into_remove_address()
2928 .expect("del address request");
2929 assert_eq!(addr, IF_ADDR_V4);
2930 responder.send(Ok(true)).expect("responder send");
2931 };
2932
2933 futures::select! {
2934 () = interfaces_fut => panic!("interfaces_fut should never complete"),
2935 ((), ()) = futures::future::join(handler_fut, succeeds).fuse() => {},
2936 }
2937
2938 let buffers = writer::TestBuffers::default();
2939 let mut out = writer::JsonWriter::new_test(None, &buffers);
2940 let fails = do_if(
2942 &mut out,
2943 opts::IfEnum::Addr(opts::IfAddr {
2944 addr_cmd: opts::IfAddrEnum::Del(opts::IfAddrDel {
2945 interface: interface2.identifier(use_ifname),
2946 addr: fnet_ext::IpAddress::from(IF_ADDR_V6.addr).to_string(),
2947 prefix: Some(IF_ADDR_V6.prefix_len),
2948 }),
2949 }),
2950 &connector,
2951 )
2952 .map(|res| res.expect_err("failure"));
2953
2954 if use_ifname {
2955 futures::select! {
2958 () = interfaces_fut => panic!("interfaces_fut should never complete"),
2959 e = fails.fuse() => {
2960 assert_eq!(e.to_string(), format!("No interface with name {}", interface2.name));
2961 },
2962 }
2963 } else {
2964 let handler_fut = async {
2965 let (id, control, _control_handle) = requests
2966 .next()
2967 .await
2968 .expect("root request stream not ended")
2969 .expect("root request stream not error")
2970 .into_get_admin()
2971 .expect("get admin request");
2972 assert_eq!(id, interface2.nicid);
2973 let mut control = control.into_stream();
2974 let (addr, responder) = control
2975 .next()
2976 .await
2977 .expect("control request stream not ended")
2978 .expect("control request stream not error")
2979 .into_remove_address()
2980 .expect("del address request");
2981 assert_eq!(addr, IF_ADDR_V6);
2982 responder.send(Ok(false)).expect("responder send");
2983 };
2984 futures::select! {
2985 () = interfaces_fut => panic!("interfaces_fut should never complete"),
2986 ((), e) = futures::future::join(handler_fut, fails).fuse() => {
2987 let fnet_ext::IpAddress(addr) = IF_ADDR_V6.addr.into();
2988 assert_eq!(e.to_string(), format!("Address {} not found on interface {}", addr, interface2.nicid));
2989 },
2990 }
2991 }
2992 }
2993
2994 const INTERFACE_NAME: &str = "if1";
2995
2996 fn interface_properties(
2997 addrs: Vec<(fnet::Subnet, finterfaces::AddressAssignmentState)>,
2998 ) -> finterfaces::Properties {
2999 finterfaces_ext::Properties {
3000 id: INTERFACE_ID.try_into().unwrap(),
3001 name: INTERFACE_NAME.to_string(),
3002 port_class: finterfaces_ext::PortClass::Ethernet,
3003 online: true,
3004 addresses: addrs
3005 .into_iter()
3006 .map(|(addr, assignment_state)| finterfaces_ext::Address::<
3007 finterfaces_ext::AllInterest,
3008 > {
3009 addr,
3010 assignment_state,
3011 valid_until: finterfaces_ext::PositiveMonotonicInstant::INFINITE_FUTURE,
3012 preferred_lifetime_info:
3013 finterfaces_ext::PreferredLifetimeInfo::preferred_forever(),
3014 })
3015 .collect(),
3016 has_default_ipv4_route: false,
3017 has_default_ipv6_route: false,
3018 port_identity_koid: None,
3019 }
3020 .into()
3021 }
3022
3023 #[test_case(
3024 false,
3025 vec![
3026 finterfaces::Event::Existing(interface_properties(vec![])),
3027 finterfaces::Event::Idle(finterfaces::Empty),
3028 finterfaces::Event::Changed(interface_properties(vec![
3029 (fidl_subnet!("192.168.0.1/32"), finterfaces::AddressAssignmentState::Assigned)
3030 ])),
3031 ],
3032 "192.168.0.1";
3033 "wait for an address to be assigned"
3034 )]
3035 #[test_case(
3036 false,
3037 vec![
3038 finterfaces::Event::Existing(interface_properties(vec![
3039 (fidl_subnet!("192.168.0.1/32"), finterfaces::AddressAssignmentState::Assigned),
3040 (fidl_subnet!("fd00::1/64"), finterfaces::AddressAssignmentState::Assigned),
3041 ])),
3042 ],
3043 "192.168.0.1";
3044 "prefer first when any address requested"
3045 )]
3046 #[test_case(
3047 true,
3048 vec![
3049 finterfaces::Event::Existing(interface_properties(vec![
3050 (fidl_subnet!("192.168.0.1/32"), finterfaces::AddressAssignmentState::Assigned)
3051 ])),
3052 finterfaces::Event::Idle(finterfaces::Empty),
3053 finterfaces::Event::Changed(interface_properties(vec![
3054 (fidl_subnet!("fd00::1/64"), finterfaces::AddressAssignmentState::Assigned)
3055 ])),
3056 ],
3057 "fd00::1";
3058 "wait for IPv6 when IPv6 address requested"
3059 )]
3060 #[fuchsia::test]
3061 async fn if_addr_wait(ipv6: bool, events: Vec<finterfaces::Event>, expected_output: &str) {
3062 let client = flex_local::local_client_empty();
3063
3064 let interface = TestInterface { nicid: INTERFACE_ID, name: INTERFACE_NAME };
3065
3066 let (interfaces_state, mut request_stream) =
3067 client.create_proxy_and_stream::<finterfaces::StateMarker>();
3068
3069 let interfaces_handler = async move {
3070 let (finterfaces::WatcherOptions { include_non_assigned_addresses, .. }, server_end, _) =
3071 request_stream
3072 .next()
3073 .await
3074 .expect("should call state")
3075 .expect("should succeed")
3076 .into_get_watcher()
3077 .expect("request should be GetWatcher");
3078 assert_eq!(include_non_assigned_addresses, Some(false));
3079 let mut request_stream: finterfaces::WatcherRequestStream = server_end.into_stream();
3080 for event in events {
3081 request_stream
3082 .next()
3083 .await
3084 .expect("should call watcher")
3085 .expect("should succeed")
3086 .into_watch()
3087 .expect("request should be Watch")
3088 .send(&event)
3089 .expect("send response");
3090 }
3091 };
3092
3093 let connector =
3094 TestConnector { interfaces_state: Some(interfaces_state), ..Default::default() };
3095 let buffers = writer::TestBuffers::default();
3096 let mut out = writer::JsonWriter::new_test(None, &buffers);
3097 let run_command = do_if(
3098 &mut out,
3099 opts::IfEnum::Addr(opts::IfAddr {
3100 addr_cmd: opts::IfAddrEnum::Wait(opts::IfAddrWait {
3101 interface: interface.identifier(false),
3102 ipv6,
3103 }),
3104 }),
3105 &connector,
3106 )
3107 .map(|r| r.expect("command should succeed"));
3108
3109 let ((), ()) = futures::future::join(interfaces_handler, run_command).await;
3110
3111 let output = buffers.into_stdout_str();
3112 pretty_assertions::assert_eq!(
3113 trim_whitespace_for_comparison(&output),
3114 trim_whitespace_for_comparison(expected_output),
3115 );
3116 }
3117
3118 fn wanted_net_if_list_json() -> String {
3119 json!([
3120 {
3121 "addresses": {
3122 "ipv4": [],
3123 "ipv6": [],
3124 },
3125 "device_class": "Loopback",
3126 "mac": "00:00:00:00:00:00",
3127 "name": "lo",
3128 "nicid": 1,
3129 "online": true,
3130 "has_default_ipv4_route": false,
3131 "has_default_ipv6_route": false,
3132 "port_identity_koid": null,
3133 },
3134 {
3135 "addresses": {
3136 "ipv4": [],
3137 "ipv6": [],
3138 },
3139 "device_class": "Ethernet",
3140 "mac": "01:02:03:04:05:06",
3141 "name": "eth001",
3142 "nicid": 10,
3143 "online": true,
3144 "has_default_ipv4_route": false,
3145 "has_default_ipv6_route": false,
3146 "port_identity_koid": 10,
3147 },
3148 {
3149 "addresses": {
3150 "ipv4": [],
3151 "ipv6": [],
3152 },
3153 "device_class": "Virtual",
3154 "mac": null,
3155 "name": "virt001",
3156 "nicid": 20,
3157 "online": true,
3158 "has_default_ipv4_route": false,
3159 "has_default_ipv6_route": false,
3160 "port_identity_koid": 20,
3161 },
3162 {
3163 "addresses": {
3164 "ipv4": [
3165 {
3166 "addr": "192.168.0.1",
3167 "assignment_state": "Tentative",
3168 "prefix_len": 24,
3169 "valid_until": 2500000000_u64,
3170 }
3171 ],
3172 "ipv6": [],
3173 },
3174 "device_class": "Ethernet",
3175 "mac": null,
3176 "name": "eth002",
3177 "nicid": 30,
3178 "online": true,
3179 "has_default_ipv4_route": false,
3180 "has_default_ipv6_route": true,
3181 "port_identity_koid": null,
3182 },
3183 {
3184 "addresses": {
3185 "ipv4": [],
3186 "ipv6": [{
3187 "addr": "2001:db8::1",
3188 "assignment_state": "Unavailable",
3189 "prefix_len": 64,
3190 "valid_until": null,
3191 }],
3192 },
3193 "device_class": "Ethernet",
3194 "mac": null,
3195 "name": "eth003",
3196 "nicid": 40,
3197 "online": true,
3198 "has_default_ipv4_route": true,
3199 "has_default_ipv6_route": true,
3200 "port_identity_koid": null,
3201 },
3202 ])
3203 .to_string()
3204 }
3205
3206 fn wanted_net_if_list_tabular() -> String {
3207 String::from(
3208 r#"
3209nicid 1
3210name lo
3211device class loopback
3212online true
3213default routes -
3214mac 00:00:00:00:00:00
3215port_identity_koid -
3216
3217nicid 10
3218name eth001
3219device class ethernet
3220online true
3221default routes -
3222mac 01:02:03:04:05:06
3223port_identity_koid 10
3224
3225nicid 20
3226name virt001
3227device class virtual
3228online true
3229default routes -
3230mac -
3231port_identity_koid 20
3232
3233nicid 30
3234name eth002
3235device class ethernet
3236online true
3237default routes IPv6
3238addr 192.168.0.1/24 TENTATIVE valid until [2.5s]
3239mac -
3240port_identity_koid -
3241
3242nicid 40
3243name eth003
3244device class ethernet
3245online true
3246default routes IPv4,IPv6
3247addr 2001:db8::1/64 UNAVAILABLE
3248mac -
3249port_identity_koid -
3250"#,
3251 )
3252 }
3253
3254 #[test_case(true, wanted_net_if_list_json() ; "in json format")]
3255 #[test_case(false, wanted_net_if_list_tabular() ; "in tabular format")]
3256 #[fuchsia::test]
3257 async fn if_list(json: bool, wanted_output: String) {
3258 let client = flex_local::local_client_empty();
3259
3260 let (root_interfaces, root_interfaces_stream) =
3261 client.create_proxy_and_stream::<froot::InterfacesMarker>();
3262 let (interfaces_state, interfaces_state_stream) =
3263 client.create_proxy_and_stream::<finterfaces::StateMarker>();
3264
3265 let buffers = writer::TestBuffers::default();
3266 let mut output = if json {
3267 writer::JsonWriter::new_test(Some(writer::Format::Json), &buffers)
3268 } else {
3269 writer::JsonWriter::new_test(None, &buffers)
3270 };
3271 let output_ref = &mut output;
3272
3273 let do_if_fut = async {
3274 let connector = TestConnector {
3275 root_interfaces: Some(root_interfaces),
3276 interfaces_state: Some(interfaces_state),
3277 ..Default::default()
3278 };
3279 do_if(output_ref, opts::IfEnum::List(opts::IfList { name_pattern: None }), &connector)
3280 .map(|res| res.expect("if list"))
3281 .await
3282 };
3283 let watcher_stream = interfaces_state_stream
3284 .and_then(|req| match req {
3285 finterfaces::StateRequest::GetWatcher {
3286 options: _,
3287 watcher,
3288 control_handle: _,
3289 } => futures::future::ready(Ok(watcher.into_stream())),
3290 })
3291 .try_flatten()
3292 .map(|res| res.expect("watcher stream error"));
3293 let (interfaces, mac_addresses): (Vec<_>, HashMap<_, _>) = [
3294 get_fake_interface(
3295 1,
3296 "lo",
3297 finterfaces_ext::PortClass::Loopback,
3298 Some([0, 0, 0, 0, 0, 0]),
3299 ),
3300 get_fake_interface(
3301 10,
3302 "eth001",
3303 finterfaces_ext::PortClass::Ethernet,
3304 Some([1, 2, 3, 4, 5, 6]),
3305 ),
3306 get_fake_interface(20, "virt001", finterfaces_ext::PortClass::Virtual, None),
3307 (
3308 finterfaces_ext::Properties {
3309 id: 30.try_into().unwrap(),
3310 name: "eth002".to_string(),
3311 port_class: finterfaces_ext::PortClass::Ethernet,
3312 online: true,
3313 addresses: vec![finterfaces_ext::Address {
3314 addr: fidl_subnet!("192.168.0.1/24"),
3315 valid_until: i64::try_from(
3316 std::time::Duration::from_millis(2500).as_nanos(),
3317 )
3318 .unwrap()
3319 .try_into()
3320 .unwrap(),
3321 assignment_state: finterfaces::AddressAssignmentState::Tentative,
3322 preferred_lifetime_info:
3323 finterfaces_ext::PreferredLifetimeInfo::preferred_forever(),
3324 }],
3325 has_default_ipv4_route: false,
3326 has_default_ipv6_route: true,
3327 port_identity_koid: None,
3328 },
3329 None,
3330 ),
3331 (
3332 finterfaces_ext::Properties {
3333 id: 40.try_into().unwrap(),
3334 name: "eth003".to_string(),
3335 port_class: finterfaces_ext::PortClass::Ethernet,
3336 online: true,
3337 addresses: vec![finterfaces_ext::Address {
3338 addr: fidl_subnet!("2001:db8::1/64"),
3339 valid_until: finterfaces_ext::PositiveMonotonicInstant::INFINITE_FUTURE,
3340 assignment_state: finterfaces::AddressAssignmentState::Unavailable,
3341 preferred_lifetime_info:
3342 finterfaces_ext::PreferredLifetimeInfo::preferred_forever(),
3343 }],
3344 has_default_ipv4_route: true,
3345 has_default_ipv6_route: true,
3346 port_identity_koid: None,
3347 },
3348 None,
3349 ),
3350 ]
3351 .into_iter()
3352 .map(|(properties, mac)| {
3353 let finterfaces_ext::Properties { id, .. } = &properties;
3354 let id = *id;
3355 (properties, (id, mac))
3356 })
3357 .unzip();
3358 let interfaces =
3359 futures::stream::iter(interfaces.into_iter().map(Some).chain(std::iter::once(None)));
3360 let watcher_fut = watcher_stream.zip(interfaces).for_each(|(req, properties)| match req {
3361 finterfaces::WatcherRequest::Watch { responder } => {
3362 let event = properties.map_or(
3363 finterfaces::Event::Idle(finterfaces::Empty),
3364 |finterfaces_ext::Properties {
3365 id,
3366 name,
3367 port_class,
3368 online,
3369 addresses,
3370 has_default_ipv4_route,
3371 has_default_ipv6_route,
3372 port_identity_koid,
3373 }| {
3374 finterfaces::Event::Existing(finterfaces::Properties {
3375 id: Some(id.get()),
3376 name: Some(name),
3377 port_class: Some(port_class.into()),
3378 online: Some(online),
3379 addresses: Some(
3380 addresses.into_iter().map(finterfaces::Address::from).collect(),
3381 ),
3382 has_default_ipv4_route: Some(has_default_ipv4_route),
3383 has_default_ipv6_route: Some(has_default_ipv6_route),
3384 port_identity_koid: port_identity_koid.map(|p| p.raw_koid()),
3385 ..Default::default()
3386 })
3387 },
3388 );
3389 responder.send(&event).expect("send watcher event");
3390 futures::future::ready(())
3391 }
3392 });
3393 let root_fut = root_interfaces_stream
3394 .map(|res| res.expect("root interfaces stream error"))
3395 .for_each_concurrent(None, |req| {
3396 let (id, responder) = req.into_get_mac().expect("get_mac request");
3397 responder
3398 .send(
3399 mac_addresses
3400 .get(&id.try_into().unwrap())
3401 .map(Option::as_ref)
3402 .ok_or(froot::InterfacesGetMacError::NotFound),
3403 )
3404 .expect("send get_mac response");
3405 futures::future::ready(())
3406 });
3407 let ((), (), ()) = futures::future::join3(do_if_fut, watcher_fut, root_fut).await;
3408
3409 let got_output = buffers.into_stdout_str();
3410
3411 if json {
3412 let got: Value = serde_json::from_str(&got_output).unwrap();
3413 let want: Value = serde_json::from_str(&wanted_output).unwrap();
3414 pretty_assertions::assert_eq!(got, want);
3415 } else {
3416 pretty_assertions::assert_eq!(
3417 trim_whitespace_for_comparison(&got_output),
3418 trim_whitespace_for_comparison(&wanted_output),
3419 );
3420 }
3421 }
3422
3423 async fn test_do_dhcp(client: flex_client::ClientArg, cmd: opts::DhcpEnum) {
3424 let (stack, mut requests) = client.create_proxy_and_stream::<fstack::StackMarker>();
3425 let connector = TestConnector { stack: Some(stack), ..Default::default() };
3426 let op = do_dhcp(cmd.clone(), &connector);
3427 let op_succeeds = async move {
3428 let (expected_id, expected_enable) = match cmd {
3429 opts::DhcpEnum::Start(opts::DhcpStart { interface }) => (interface, true),
3430 opts::DhcpEnum::Stop(opts::DhcpStop { interface }) => (interface, false),
3431 };
3432 let request = requests
3433 .try_next()
3434 .await
3435 .expect("start FIDL error")
3436 .expect("request stream should not have ended");
3437 let (received_id, enable, responder) = request
3438 .into_set_dhcp_client_enabled()
3439 .expect("request should be of type StopDhcpClient");
3440 assert_eq!(
3441 opts::InterfaceIdentifier::Id(NonZeroU64::new(u64::from(received_id)).unwrap()),
3442 expected_id
3443 );
3444 assert_eq!(enable, expected_enable);
3445 responder.send(Ok(())).map_err(anyhow::Error::new)
3446 };
3447 let ((), ()) =
3448 futures::future::try_join(op, op_succeeds).await.expect("dhcp command should succeed");
3449 }
3450
3451 #[fuchsia::test]
3452 async fn dhcp_start() {
3453 let client = flex_local::local_client_empty();
3454
3455 test_do_dhcp(
3456 client,
3457 opts::DhcpEnum::Start(opts::DhcpStart {
3458 interface: NonZeroU64::new(1).unwrap().into(),
3459 }),
3460 )
3461 .await;
3462 }
3463
3464 #[fuchsia::test]
3465 async fn dhcp_stop() {
3466 let client = flex_local::local_client_empty();
3467
3468 test_do_dhcp(
3469 client,
3470 opts::DhcpEnum::Stop(opts::DhcpStop { interface: NonZeroU64::new(1).unwrap().into() }),
3471 )
3472 .await;
3473 }
3474
3475 async fn test_modify_route(client: flex_client::ClientArg, cmd: opts::RouteEnum) {
3476 let event_token = client.create_event();
3477 let expected_interface = match &cmd {
3478 opts::RouteEnum::List(_) => panic!("test_modify_route should not take a List command"),
3479 opts::RouteEnum::Add(opts::RouteAdd { interface, .. }) => interface,
3480 opts::RouteEnum::Del(opts::RouteDel { interface, .. }) => interface,
3481 }
3482 .clone();
3483 let expected_id = match expected_interface {
3484 opts::InterfaceIdentifier::Id(ref id) => *id,
3485 opts::InterfaceIdentifier::Name(_) => {
3486 panic!("expected test to work only with ids")
3487 }
3488 };
3489
3490 let (root_interfaces, mut interfaces_requests) =
3491 client.create_proxy_and_stream::<froot::InterfacesMarker>();
3492 let (routes_v4, mut routes_v4_requests) =
3493 client.create_proxy_and_stream::<froot::RoutesV4Marker>();
3494 let (routes_v6, _routes_v6_requests) =
3495 client.create_proxy_and_stream::<froot::RoutesV6Marker>();
3496
3497 let connector = TestConnector {
3498 root_interfaces: Some(root_interfaces),
3499 root_routes_v4: Some(routes_v4),
3500 root_routes_v6: Some(routes_v6),
3501 ..Default::default()
3502 };
3503 let buffers = writer::TestBuffers::default();
3504 let mut out = writer::JsonWriter::new_test(None, &buffers);
3505 let op = do_route(&mut out, cmd.clone(), &connector);
3506
3507 let op_succeeds = async move {
3508 let (nic_id, control_server, _get_admin_responder) = interfaces_requests
3510 .try_next()
3511 .await
3512 .expect("get_admin FIDL error")
3513 .expect("request stream should not have ended")
3514 .into_get_admin()
3515 .expect("request should be get_admin");
3516 assert_eq!(nic_id, expected_id.get());
3517
3518 let mut control_stream = control_server.into_stream();
3519 let auth_responder = control_stream
3520 .try_next()
3521 .await
3522 .expect("get_authorization_for_interface FIDL error")
3523 .expect("request stream should not have ended")
3524 .into_get_authorization_for_interface()
3525 .expect("request should be get_authorization_for_interface");
3526
3527 let grant = fnet_resources::GrantForInterfaceAuthorization {
3528 interface_id: nic_id,
3529 token: event_token,
3530 };
3531 auth_responder.send(grant).expect("failed to send grant");
3532
3533 match cmd {
3535 opts::RouteEnum::List(_) => {
3536 panic!("test_modify_route should not take a List command")
3537 }
3538 opts::RouteEnum::Add(route) => match route.destination {
3539 std::net::IpAddr::V4(addr) => {
3540 let (route_set_server, _routes_responder) = routes_v4_requests
3541 .try_next()
3542 .await
3543 .expect("global_route_set FIDL error")
3544 .expect("request stream should not have ended")
3545 .into_global_route_set()
3546 .expect("request should be global_route_set");
3547
3548 let mut route_set_stream = route_set_server.into_stream();
3549
3550 let (proof, auth_responder) = route_set_stream
3551 .try_next()
3552 .await
3553 .expect("authenticate FIDL error")
3554 .expect("request stream should not have ended")
3555 .into_authenticate_for_interface()
3556 .expect("request should be authenticate");
3557 assert_eq!(proof.interface_id, nic_id);
3558 auth_responder.send(Ok(())).expect("failed to send authenticate response");
3559
3560 let (route_v4, add_responder) = route_set_stream
3561 .try_next()
3562 .await
3563 .expect("add_route FIDL error")
3564 .expect("request stream should not have ended")
3565 .into_add_route()
3566 .expect("request should be add_route");
3567 assert_eq!(route_v4.destination.addr.addr, addr.octets());
3568 assert_eq!(route_v4.destination.prefix_len, route.prefix_len);
3569 assert_eq!(
3570 route_v4.action,
3571 froutes::RouteActionV4::Forward(froutes::RouteTargetV4 {
3572 outbound_interface: nic_id,
3573 next_hop: None,
3574 })
3575 );
3576 add_responder.send(Ok(true)).expect("failed to send add_route response");
3577 }
3578 _ => panic!("test only mocks IPv4 currently"),
3579 },
3580 opts::RouteEnum::Del(route) => match route.destination {
3581 std::net::IpAddr::V4(addr) => {
3582 let (route_set_server, _routes_responder) = routes_v4_requests
3583 .try_next()
3584 .await
3585 .expect("global_route_set FIDL error")
3586 .expect("request stream should not have ended")
3587 .into_global_route_set()
3588 .expect("request should be global_route_set");
3589
3590 let mut route_set_stream = route_set_server.into_stream();
3591
3592 let (proof, auth_responder) = route_set_stream
3593 .try_next()
3594 .await
3595 .expect("authenticate FIDL error")
3596 .expect("request stream should not have ended")
3597 .into_authenticate_for_interface()
3598 .expect("request should be authenticate");
3599 assert_eq!(proof.interface_id, nic_id);
3600 auth_responder.send(Ok(())).expect("failed to send authenticate response");
3601
3602 let (route_v4, del_responder) = route_set_stream
3603 .try_next()
3604 .await
3605 .expect("remove_route FIDL error")
3606 .expect("request stream should not have ended")
3607 .into_remove_route()
3608 .expect("request should be remove_route");
3609 assert_eq!(route_v4.destination.addr.addr, addr.octets());
3610 assert_eq!(route_v4.destination.prefix_len, route.prefix_len);
3611 assert_eq!(
3612 route_v4.action,
3613 froutes::RouteActionV4::Forward(froutes::RouteTargetV4 {
3614 outbound_interface: nic_id,
3615 next_hop: None,
3616 })
3617 );
3618 del_responder.send(Ok(true)).expect("failed to send remove_route response");
3619 }
3620 _ => panic!("test only mocks IPv4 currently"),
3621 },
3622 }
3623 Ok(())
3624 };
3625 let ((), ()) =
3626 futures::future::try_join(op, op_succeeds).await.expect("dhcp command should succeed");
3627 }
3628
3629 #[fuchsia::test]
3630 async fn route_add() {
3631 let client = flex_local::local_client_empty();
3632
3633 test_modify_route(
3635 client,
3636 opts::RouteEnum::Add(opts::RouteAdd {
3637 destination: std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 0)),
3638 prefix_len: 24,
3639 gateway: None,
3640 interface: opts::InterfaceIdentifier::Id(NonZeroU64::new(2).unwrap()),
3641 metric: Some(100),
3642 }),
3643 )
3644 .await;
3645 }
3646
3647 #[fuchsia::test]
3648 async fn route_del() {
3649 let client = flex_local::local_client_empty();
3650
3651 test_modify_route(
3653 client,
3654 opts::RouteEnum::Del(opts::RouteDel {
3655 destination: std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 0)),
3656 prefix_len: 24,
3657 gateway: None,
3658 interface: opts::InterfaceIdentifier::Id(NonZeroU64::new(2).unwrap()),
3659 metric: Some(100),
3660 }),
3661 )
3662 .await;
3663 }
3664
3665 fn wanted_route_list_json() -> String {
3666 json!([
3667 {
3668 "destination":{"addr":"0.0.0.0","prefix_len":0},
3669 "gateway":"127.0.0.1",
3670 "metric":4,
3671 "nicid":3,
3672 "table_id":0,
3673 },
3674 {
3675 "destination":{"addr":"1.1.1.0","prefix_len":24},
3676 "gateway":"1.1.1.2",
3677 "metric":4,
3678 "nicid":3,
3679 "table_id":0,
3680 },
3681 {
3682 "destination":{"addr":"10.10.10.0","prefix_len":24},
3683 "gateway":"10.10.10.20",
3684 "metric":40,
3685 "nicid":30,
3686 "table_id":1,
3687 },
3688 {
3689 "destination":{"addr":"11.11.11.0","prefix_len":28},
3690 "gateway":null,
3691 "metric":40,
3692 "nicid":30,
3693 "table_id":1,
3694 },
3695 {
3696 "destination":{"addr":"ff00::","prefix_len":8},
3697 "gateway":null,
3698 "metric":400,
3699 "nicid":300,
3700 "table_id":2,
3701 },
3702 {
3703 "destination":{"addr":"fe80::","prefix_len":64},
3704 "gateway":null,
3705 "metric":400,
3706 "nicid":300,
3707 "table_id":2,
3708 },
3709 ])
3710 .to_string()
3711 }
3712
3713 fn wanted_route_list_tabular() -> String {
3714 "Destination Gateway NICID Metric TableId
3715 0.0.0.0/0 127.0.0.1 3 4 0
3716 1.1.1.0/24 1.1.1.2 3 4 0
3717 10.10.10.0/24 10.10.10.20 30 40 1
3718 11.11.11.0/28 - 30 40 1
3719 ff00::/8 - 300 400 2
3720 fe80::/64 - 300 400 2
3721 "
3722 .to_string()
3723 }
3724
3725 #[test_case(true, wanted_route_list_json() ; "in json format")]
3726 #[test_case(false, wanted_route_list_tabular() ; "in tabular format")]
3727 #[fuchsia::test]
3728 async fn route_list(json: bool, wanted_output: String) {
3729 let client = flex_local::local_client_empty();
3730
3731 let (routes_v4_controller, mut routes_v4_state_stream) =
3732 client.create_proxy_and_stream::<froutes::StateV4Marker>();
3733 let (routes_v6_controller, mut routes_v6_state_stream) =
3734 client.create_proxy_and_stream::<froutes::StateV6Marker>();
3735 let connector = TestConnector {
3736 routes_v4: Some(routes_v4_controller),
3737 routes_v6: Some(routes_v6_controller),
3738 ..Default::default()
3739 };
3740
3741 let buffers = writer::TestBuffers::default();
3742 let mut output = if json {
3743 writer::JsonWriter::new_test(Some(writer::Format::Json), &buffers)
3744 } else {
3745 writer::JsonWriter::new_test(None, &buffers)
3746 };
3747
3748 let do_route_fut =
3749 do_route(&mut output, opts::RouteEnum::List(opts::RouteList {}), &connector);
3750
3751 let v4_route_events = vec![
3752 froutes::EventV4::Existing(froutes::InstalledRouteV4 {
3753 route: Some(froutes::RouteV4 {
3754 destination: net_declare::fidl_ip_v4_with_prefix!("1.1.1.0/24"),
3755 action: froutes::RouteActionV4::Forward(froutes::RouteTargetV4 {
3756 outbound_interface: 3,
3757 next_hop: Some(Box::new(net_declare::fidl_ip_v4!("1.1.1.2"))),
3758 }),
3759 properties: froutes::RoutePropertiesV4 {
3760 specified_properties: Some(froutes::SpecifiedRouteProperties {
3761 metric: Some(froutes::SpecifiedMetric::ExplicitMetric(4)),
3762 ..Default::default()
3763 }),
3764 ..Default::default()
3765 },
3766 }),
3767 effective_properties: Some(froutes::EffectiveRouteProperties {
3768 metric: Some(4),
3769 ..Default::default()
3770 }),
3771 table_id: Some(0),
3772 ..Default::default()
3773 }),
3774 froutes::EventV4::Existing(froutes::InstalledRouteV4 {
3775 route: Some(froutes::RouteV4 {
3776 destination: net_declare::fidl_ip_v4_with_prefix!("10.10.10.0/24"),
3777 action: froutes::RouteActionV4::Forward(froutes::RouteTargetV4 {
3778 outbound_interface: 30,
3779 next_hop: Some(Box::new(net_declare::fidl_ip_v4!("10.10.10.20"))),
3780 }),
3781 properties: froutes::RoutePropertiesV4 {
3782 specified_properties: Some(froutes::SpecifiedRouteProperties {
3783 metric: Some(froutes::SpecifiedMetric::ExplicitMetric(40)),
3784 ..Default::default()
3785 }),
3786 ..Default::default()
3787 },
3788 }),
3789 effective_properties: Some(froutes::EffectiveRouteProperties {
3790 metric: Some(40),
3791 ..Default::default()
3792 }),
3793 table_id: Some(1),
3794 ..Default::default()
3795 }),
3796 froutes::EventV4::Existing(froutes::InstalledRouteV4 {
3797 route: Some(froutes::RouteV4 {
3798 destination: net_declare::fidl_ip_v4_with_prefix!("0.0.0.0/0"),
3799 action: froutes::RouteActionV4::Forward(froutes::RouteTargetV4 {
3800 outbound_interface: 3,
3801 next_hop: Some(Box::new(net_declare::fidl_ip_v4!("127.0.0.1"))),
3802 }),
3803 properties: froutes::RoutePropertiesV4 {
3804 specified_properties: Some(froutes::SpecifiedRouteProperties {
3805 metric: Some(froutes::SpecifiedMetric::ExplicitMetric(4)),
3806 ..Default::default()
3807 }),
3808 ..Default::default()
3809 },
3810 }),
3811 effective_properties: Some(froutes::EffectiveRouteProperties {
3812 metric: Some(4),
3813 ..Default::default()
3814 }),
3815 table_id: Some(0),
3816 ..Default::default()
3817 }),
3818 froutes::EventV4::Existing(froutes::InstalledRouteV4 {
3819 route: Some(froutes::RouteV4 {
3820 destination: net_declare::fidl_ip_v4_with_prefix!("11.11.11.0/28"),
3821 action: froutes::RouteActionV4::Forward(froutes::RouteTargetV4 {
3822 outbound_interface: 30,
3823 next_hop: None,
3824 }),
3825 properties: froutes::RoutePropertiesV4 {
3826 specified_properties: Some(froutes::SpecifiedRouteProperties {
3827 metric: Some(froutes::SpecifiedMetric::ExplicitMetric(40)),
3828 ..Default::default()
3829 }),
3830 ..Default::default()
3831 },
3832 }),
3833 effective_properties: Some(froutes::EffectiveRouteProperties {
3834 metric: Some(40),
3835 ..Default::default()
3836 }),
3837 table_id: Some(1),
3838 ..Default::default()
3839 }),
3840 froutes::EventV4::Idle(froutes::Empty),
3841 ];
3842 let v6_route_events = vec![
3843 froutes::EventV6::Existing(froutes::InstalledRouteV6 {
3844 route: Some(froutes::RouteV6 {
3845 destination: net_declare::fidl_ip_v6_with_prefix!("fe80::/64"),
3846 action: froutes::RouteActionV6::Forward(froutes::RouteTargetV6 {
3847 outbound_interface: 300,
3848 next_hop: None,
3849 }),
3850 properties: froutes::RoutePropertiesV6 {
3851 specified_properties: Some(froutes::SpecifiedRouteProperties {
3852 metric: Some(froutes::SpecifiedMetric::ExplicitMetric(400)),
3853 ..Default::default()
3854 }),
3855 ..Default::default()
3856 },
3857 }),
3858 effective_properties: Some(froutes::EffectiveRouteProperties {
3859 metric: Some(400),
3860 ..Default::default()
3861 }),
3862 table_id: Some(2),
3863 ..Default::default()
3864 }),
3865 froutes::EventV6::Existing(froutes::InstalledRouteV6 {
3866 route: Some(froutes::RouteV6 {
3867 destination: net_declare::fidl_ip_v6_with_prefix!("ff00::/8"),
3868 action: froutes::RouteActionV6::Forward(froutes::RouteTargetV6 {
3869 outbound_interface: 300,
3870 next_hop: None,
3871 }),
3872 properties: froutes::RoutePropertiesV6 {
3873 specified_properties: Some(froutes::SpecifiedRouteProperties {
3874 metric: Some(froutes::SpecifiedMetric::ExplicitMetric(400)),
3875 ..Default::default()
3876 }),
3877 ..Default::default()
3878 },
3879 }),
3880 effective_properties: Some(froutes::EffectiveRouteProperties {
3881 metric: Some(400),
3882 ..Default::default()
3883 }),
3884 table_id: Some(2),
3885 ..Default::default()
3886 }),
3887 froutes::EventV6::Idle(froutes::Empty),
3888 ];
3889
3890 let route_v4_fut = routes_v4_state_stream.select_next_some().then(|request| {
3891 froutes_ext::testutil::serve_state_request::<Ipv4>(
3892 request,
3893 futures::stream::once(futures::future::ready(v4_route_events)),
3894 )
3895 });
3896 let route_v6_fut = routes_v6_state_stream.select_next_some().then(|request| {
3897 froutes_ext::testutil::serve_state_request::<Ipv6>(
3898 request,
3899 futures::stream::once(futures::future::ready(v6_route_events)),
3900 )
3901 });
3902
3903 let ((), (), ()) =
3904 futures::try_join!(do_route_fut, route_v4_fut.map(Ok), route_v6_fut.map(Ok))
3905 .expect("listing forwarding table entries should succeed");
3906
3907 let got_output = buffers.into_stdout_str();
3908
3909 if json {
3910 let got: Value = serde_json::from_str(&got_output).unwrap();
3911 let want: Value = serde_json::from_str(&wanted_output).unwrap();
3912 pretty_assertions::assert_eq!(got, want);
3913 } else {
3914 pretty_assertions::assert_eq!(
3915 trim_whitespace_for_comparison(&got_output),
3916 trim_whitespace_for_comparison(&wanted_output),
3917 );
3918 }
3919 }
3920
3921 #[test_case(false ; "providing nicids")]
3922 #[test_case(true ; "providing interface names")]
3923 #[fuchsia::test]
3924 async fn bridge(use_ifname: bool) {
3925 let client = flex_local::local_client_empty();
3926
3927 let (stack, mut stack_requests) = client.create_proxy_and_stream::<fstack::StackMarker>();
3928 let (interfaces_state, interfaces_state_requests) =
3929 client.create_proxy_and_stream::<finterfaces::StateMarker>();
3930 let connector = TestConnector {
3931 interfaces_state: Some(interfaces_state),
3932 stack: Some(stack),
3933 ..Default::default()
3934 };
3935
3936 let bridge_ifs = vec![
3937 TestInterface { nicid: 1, name: "interface1" },
3938 TestInterface { nicid: 2, name: "interface2" },
3939 TestInterface { nicid: 3, name: "interface3" },
3940 ];
3941
3942 let interface_fidls = bridge_ifs
3943 .iter()
3944 .map(|interface| {
3945 let (interface, _mac) = get_fake_interface(
3946 interface.nicid,
3947 interface.name,
3948 finterfaces_ext::PortClass::Ethernet,
3949 None,
3950 );
3951 interface.into()
3952 })
3953 .collect::<Vec<_>>();
3954
3955 let interfaces_fut =
3956 always_answer_with_interfaces(interfaces_state_requests, interface_fidls);
3957
3958 let bridge_id = 4;
3959 let buffers = writer::TestBuffers::default();
3960 let mut out = writer::JsonWriter::new_test(None, &buffers);
3961 let bridge = do_if(
3962 &mut out,
3963 opts::IfEnum::Bridge(opts::IfBridge {
3964 interfaces: bridge_ifs
3965 .iter()
3966 .map(|interface| interface.identifier(use_ifname))
3967 .collect(),
3968 }),
3969 &connector,
3970 );
3971
3972 let bridge_succeeds = async move {
3973 let (requested_ifs, bridge_server_end, _control_handle) = stack_requests
3974 .try_next()
3975 .await
3976 .expect("stack requests FIDL error")
3977 .expect("request stream should not have ended")
3978 .into_bridge_interfaces()
3979 .expect("request should be of type BridgeInterfaces");
3980 assert_eq!(
3981 requested_ifs,
3982 bridge_ifs.iter().map(|interface| interface.nicid).collect::<Vec<_>>()
3983 );
3984 let mut bridge_requests = bridge_server_end.into_stream();
3985 let responder = bridge_requests
3986 .try_next()
3987 .await
3988 .expect("bridge requests FIDL error")
3989 .expect("request stream should not have ended")
3990 .into_get_id()
3991 .expect("request should be get_id");
3992 responder.send(bridge_id).expect("responding with bridge ID should succeed");
3993 let _control_handle = bridge_requests
3994 .try_next()
3995 .await
3996 .expect("bridge requests FIDL error")
3997 .expect("request stream should not have ended")
3998 .into_detach()
3999 .expect("request should be detach");
4000 Ok(())
4001 };
4002 futures::select! {
4003 () = interfaces_fut.fuse() => panic!("interfaces_fut should never complete"),
4004 result = futures::future::try_join(bridge, bridge_succeeds).fuse() => {
4005 let ((), ()) = result.expect("if bridge should succeed");
4006 }
4007 }
4008 }
4009
4010 async fn test_get_neigh_entries(
4011 watch_for_changes: bool,
4012 batches: Vec<Vec<fneighbor::EntryIteratorItem>>,
4013 want: String,
4014 ) {
4015 let client = flex_local::local_client_empty();
4016
4017 let (it, mut requests) = client.create_proxy_and_stream::<fneighbor::EntryIteratorMarker>();
4018
4019 let server = async {
4020 for items in batches {
4021 let responder = requests
4022 .try_next()
4023 .await
4024 .expect("neigh FIDL error")
4025 .expect("request stream should not have ended")
4026 .into_get_next()
4027 .expect("request should be of type GetNext");
4028 responder.send(&items).expect("responder.send should succeed");
4029 }
4030 }
4031 .on_timeout(std::time::Duration::from_secs(60), || panic!("server responder timed out"));
4032
4033 let client = async {
4034 let mut stream = neigh_entry_stream(it, watch_for_changes);
4035
4036 let item_to_string = |item| {
4037 let buffers = writer::TestBuffers::default();
4038 let mut buf = writer::JsonWriter::new_test(None, &buffers);
4039 write_neigh_entry(&mut buf, item, watch_for_changes)
4040 .expect("write_neigh_entry should succeed");
4041 buffers.into_stdout_str()
4042 };
4043
4044 for want_line in want.lines() {
4046 let got = stream
4047 .next()
4048 .await
4049 .map(|item| item_to_string(item.expect("neigh_entry_stream should succeed")));
4050 assert_eq!(got, Some(format!("{}\n", want_line)));
4051 }
4052
4053 if !watch_for_changes {
4055 match stream.next().await {
4056 Some(Ok(item)) => {
4057 panic!("unexpected item from stream: {}", item_to_string(item))
4058 }
4059 Some(Err(err)) => panic!("unexpected error from stream: {}", err),
4060 None => {}
4061 }
4062 }
4063 };
4064
4065 let ((), ()) = futures::future::join(client, server).await;
4066 }
4067
4068 async fn test_neigh_none(watch_for_changes: bool, want: String) {
4069 test_get_neigh_entries(
4070 watch_for_changes,
4071 vec![vec![fneighbor::EntryIteratorItem::Idle(fneighbor::IdleEvent {})]],
4072 want,
4073 )
4074 .await
4075 }
4076
4077 #[fuchsia::test]
4078 async fn neigh_list_none() {
4079 test_neigh_none(false , "".to_string()).await
4080 }
4081
4082 #[fuchsia::test]
4083 async fn neigh_watch_none() {
4084 test_neigh_none(true , "IDLE".to_string()).await
4085 }
4086
4087 fn timestamp_60s_ago() -> i64 {
4088 let now = std::time::SystemTime::now()
4089 .duration_since(std::time::SystemTime::UNIX_EPOCH)
4090 .expect("failed to get duration since epoch");
4091 let past = now - std::time::Duration::from_secs(60);
4092 i64::try_from(past.as_nanos()).expect("failed to convert duration to i64")
4093 }
4094
4095 async fn test_neigh_one(watch_for_changes: bool, want: fn(fneighbor_ext::Entry) -> String) {
4096 fn new_entry(updated_at: i64) -> fneighbor::Entry {
4097 fneighbor::Entry {
4098 interface: Some(1),
4099 neighbor: Some(IF_ADDR_V4.addr),
4100 state: Some(fneighbor::EntryState::Reachable),
4101 mac: Some(MAC_1),
4102 updated_at: Some(updated_at),
4103 ..Default::default()
4104 }
4105 }
4106
4107 let updated_at = timestamp_60s_ago();
4108
4109 test_get_neigh_entries(
4110 watch_for_changes,
4111 vec![vec![
4112 fneighbor::EntryIteratorItem::Existing(new_entry(updated_at)),
4113 fneighbor::EntryIteratorItem::Idle(fneighbor::IdleEvent {}),
4114 ]],
4115 want(fneighbor_ext::Entry::try_from(new_entry(updated_at)).unwrap()),
4116 )
4117 .await
4118 }
4119
4120 #[fuchsia::test]
4121 async fn neigh_list_one() {
4122 test_neigh_one(false , |entry| format!("{}\n", entry)).await
4123 }
4124
4125 #[fuchsia::test]
4126 async fn neigh_watch_one() {
4127 test_neigh_one(true , |entry| {
4128 format!(
4129 "EXISTING | {}\n\
4130 IDLE\n",
4131 entry
4132 )
4133 })
4134 .await
4135 }
4136
4137 async fn test_neigh_many(
4138 watch_for_changes: bool,
4139 want: fn(fneighbor_ext::Entry, fneighbor_ext::Entry) -> String,
4140 ) {
4141 fn new_entry(
4142 ip: fnet::IpAddress,
4143 mac: fnet::MacAddress,
4144 updated_at: i64,
4145 ) -> fneighbor::Entry {
4146 fneighbor::Entry {
4147 interface: Some(1),
4148 neighbor: Some(ip),
4149 state: Some(fneighbor::EntryState::Reachable),
4150 mac: Some(mac),
4151 updated_at: Some(updated_at),
4152 ..Default::default()
4153 }
4154 }
4155
4156 let updated_at = timestamp_60s_ago();
4157 let offset = i64::try_from(std::time::Duration::from_secs(60).as_nanos())
4158 .expect("failed to convert duration to i64");
4159
4160 test_get_neigh_entries(
4161 watch_for_changes,
4162 vec![vec![
4163 fneighbor::EntryIteratorItem::Existing(new_entry(
4164 IF_ADDR_V4.addr,
4165 MAC_1,
4166 updated_at,
4167 )),
4168 fneighbor::EntryIteratorItem::Existing(new_entry(
4169 IF_ADDR_V6.addr,
4170 MAC_2,
4171 updated_at - offset,
4172 )),
4173 fneighbor::EntryIteratorItem::Idle(fneighbor::IdleEvent {}),
4174 ]],
4175 want(
4176 fneighbor_ext::Entry::try_from(new_entry(IF_ADDR_V4.addr, MAC_1, updated_at))
4177 .unwrap(),
4178 fneighbor_ext::Entry::try_from(new_entry(
4179 IF_ADDR_V6.addr,
4180 MAC_2,
4181 updated_at - offset,
4182 ))
4183 .unwrap(),
4184 ),
4185 )
4186 .await
4187 }
4188
4189 #[fuchsia::test]
4190 async fn neigh_list_many() {
4191 test_neigh_many(false , |a, b| format!("{}\n{}\n", a, b)).await
4192 }
4193
4194 #[fuchsia::test]
4195 async fn neigh_watch_many() {
4196 test_neigh_many(true , |a, b| {
4197 format!(
4198 "EXISTING | {}\n\
4199 EXISTING | {}\n\
4200 IDLE\n",
4201 a, b
4202 )
4203 })
4204 .await
4205 }
4206
4207 fn wanted_neigh_list_json() -> String {
4208 json!({
4209 "interface": 1,
4210 "neighbor": "192.168.0.1",
4211 "state": "REACHABLE",
4212 "mac": "01:02:03:04:05:06",
4213 })
4214 .to_string()
4215 }
4216
4217 fn wanted_neigh_watch_json() -> String {
4218 json!({
4219 "state_change_status": "EXISTING",
4220 "entry": {
4221 "interface": 1,
4222 "neighbor": "192.168.0.1",
4223 "state": "REACHABLE",
4224 "mac": "01:02:03:04:05:06",
4225 },
4226 })
4227 .to_string()
4228 }
4229
4230 #[test_case(true, false, &wanted_neigh_list_json() ; "in json format, not including entry state")]
4231 #[test_case(false, false, "Interface 1 | IP 192.168.0.1 | MAC 01:02:03:04:05:06 | REACHABLE" ; "in tabular format, not including entry state")]
4232 #[test_case(true, true, &wanted_neigh_watch_json() ; "in json format, including entry state")]
4233 #[test_case(false, true, "EXISTING | Interface 1 | IP 192.168.0.1 | MAC 01:02:03:04:05:06 | REACHABLE" ; "in tabular format, including entry state")]
4234 fn neigh_write_entry(json: bool, include_entry_state: bool, wanted_output: &str) {
4235 let entry = fneighbor::EntryIteratorItem::Existing(fneighbor::Entry {
4236 interface: Some(1),
4237 neighbor: Some(IF_ADDR_V4.addr),
4238 state: Some(fneighbor::EntryState::Reachable),
4239 mac: Some(MAC_1),
4240 updated_at: Some(timestamp_60s_ago()),
4241 ..Default::default()
4242 });
4243
4244 let buffers = writer::TestBuffers::default();
4245 let mut output = if json {
4246 writer::JsonWriter::new_test(Some(writer::Format::Json), &buffers)
4247 } else {
4248 writer::JsonWriter::new_test(None, &buffers)
4249 };
4250 write_neigh_entry(&mut output, entry, include_entry_state)
4251 .expect("write_neigh_entry should succeed");
4252 let got_output = buffers.into_stdout_str();
4253 pretty_assertions::assert_eq!(
4254 trim_whitespace_for_comparison(&got_output),
4255 trim_whitespace_for_comparison(wanted_output),
4256 );
4257 }
4258
4259 const INTERFACE_ID: u64 = 1;
4260 const IP_VERSION: fnet::IpVersion = fnet::IpVersion::V4;
4261
4262 #[fuchsia::test]
4263 async fn neigh_add() {
4264 let client = flex_local::local_client_empty();
4265
4266 let (controller, mut requests) =
4267 client.create_proxy_and_stream::<fneighbor::ControllerMarker>();
4268 let neigh = do_neigh_add(INTERFACE_ID, IF_ADDR_V4.addr, MAC_1, controller);
4269 let neigh_succeeds = async {
4270 let (got_interface_id, got_ip_address, got_mac, responder) = requests
4271 .try_next()
4272 .await
4273 .expect("neigh FIDL error")
4274 .expect("request stream should not have ended")
4275 .into_add_entry()
4276 .expect("request should be of type AddEntry");
4277 assert_eq!(got_interface_id, INTERFACE_ID);
4278 assert_eq!(got_ip_address, IF_ADDR_V4.addr);
4279 assert_eq!(got_mac, MAC_1);
4280 responder.send(Ok(())).expect("responder.send should succeed");
4281 Ok(())
4282 };
4283 let ((), ()) = futures::future::try_join(neigh, neigh_succeeds)
4284 .await
4285 .expect("neigh add should succeed");
4286 }
4287
4288 #[fuchsia::test]
4289 async fn neigh_clear() {
4290 let client = flex_local::local_client_empty();
4291
4292 let (controller, mut requests) =
4293 client.create_proxy_and_stream::<fneighbor::ControllerMarker>();
4294 let neigh = do_neigh_clear(INTERFACE_ID, IP_VERSION, controller);
4295 let neigh_succeeds = async {
4296 let (got_interface_id, got_ip_version, responder) = requests
4297 .try_next()
4298 .await
4299 .expect("neigh FIDL error")
4300 .expect("request stream should not have ended")
4301 .into_clear_entries()
4302 .expect("request should be of type ClearEntries");
4303 assert_eq!(got_interface_id, INTERFACE_ID);
4304 assert_eq!(got_ip_version, IP_VERSION);
4305 responder.send(Ok(())).expect("responder.send should succeed");
4306 Ok(())
4307 };
4308 let ((), ()) = futures::future::try_join(neigh, neigh_succeeds)
4309 .await
4310 .expect("neigh clear should succeed");
4311 }
4312
4313 #[fuchsia::test]
4314 async fn neigh_del() {
4315 let client = flex_local::local_client_empty();
4316
4317 let (controller, mut requests) =
4318 client.create_proxy_and_stream::<fneighbor::ControllerMarker>();
4319 let neigh = do_neigh_del(INTERFACE_ID, IF_ADDR_V4.addr, controller);
4320 let neigh_succeeds = async {
4321 let (got_interface_id, got_ip_address, responder) = requests
4322 .try_next()
4323 .await
4324 .expect("neigh FIDL error")
4325 .expect("request stream should not have ended")
4326 .into_remove_entry()
4327 .expect("request should be of type RemoveEntry");
4328 assert_eq!(got_interface_id, INTERFACE_ID);
4329 assert_eq!(got_ip_address, IF_ADDR_V4.addr);
4330 responder.send(Ok(())).expect("responder.send should succeed");
4331 Ok(())
4332 };
4333 let ((), ()) = futures::future::try_join(neigh, neigh_succeeds)
4334 .await
4335 .expect("neigh remove should succeed");
4336 }
4337
4338 #[test_case(opts::dhcpd::DhcpdEnum::Get(opts::dhcpd::Get {
4339 arg: opts::dhcpd::GetArg::Option(
4340 opts::dhcpd::OptionArg {
4341 name: opts::dhcpd::Option_::SubnetMask(
4342 opts::dhcpd::SubnetMask { mask: None }) }),
4343 }); "get option")]
4344 #[test_case(opts::dhcpd::DhcpdEnum::Get(opts::dhcpd::Get {
4345 arg: opts::dhcpd::GetArg::Parameter(opts::dhcpd::ParameterArg {
4346 name: opts::dhcpd::Parameter::LeaseLength(
4347 opts::dhcpd::LeaseLength { default: None, max: None }),
4348 }),
4349 }); "get parameter")]
4350 #[test_case(opts::dhcpd::DhcpdEnum::Set(opts::dhcpd::Set {
4351 arg: opts::dhcpd::SetArg::Option(opts::dhcpd::OptionArg {
4352 name: opts::dhcpd::Option_::SubnetMask(opts::dhcpd::SubnetMask {
4353 mask: Some(net_declare::std_ip_v4!("255.255.255.0")),
4354 }),
4355 }),
4356 }); "set option")]
4357 #[test_case(opts::dhcpd::DhcpdEnum::Set(opts::dhcpd::Set {
4358 arg: opts::dhcpd::SetArg::Parameter(opts::dhcpd::ParameterArg {
4359 name: opts::dhcpd::Parameter::LeaseLength(
4360 opts::dhcpd::LeaseLength { max: Some(42), default: Some(42) }),
4361 }),
4362 }); "set parameter")]
4363 #[test_case(opts::dhcpd::DhcpdEnum::List(opts::dhcpd::List { arg:
4364 opts::dhcpd::ListArg::Option(opts::dhcpd::OptionToken {}) }); "list option")]
4365 #[test_case(opts::dhcpd::DhcpdEnum::List(
4366 opts::dhcpd::List { arg: opts::dhcpd::ListArg::Parameter(opts::dhcpd::ParameterToken {}) });
4367 "list parameter")]
4368 #[test_case(opts::dhcpd::DhcpdEnum::Reset(opts::dhcpd::Reset {
4369 arg: opts::dhcpd::ResetArg::Option(opts::dhcpd::OptionToken {}) }); "reset option")]
4370 #[test_case(opts::dhcpd::DhcpdEnum::Reset(
4371 opts::dhcpd::Reset {
4372 arg: opts::dhcpd::ResetArg::Parameter(opts::dhcpd::ParameterToken {}) });
4373 "reset parameter")]
4374 #[test_case(opts::dhcpd::DhcpdEnum::ClearLeases(opts::dhcpd::ClearLeases {}); "clear leases")]
4375 #[test_case(opts::dhcpd::DhcpdEnum::Start(opts::dhcpd::Start {}); "start")]
4376 #[test_case(opts::dhcpd::DhcpdEnum::Stop(opts::dhcpd::Stop {}); "stop")]
4377 #[fuchsia::test]
4378 async fn test_do_dhcpd(cmd: opts::dhcpd::DhcpdEnum) {
4379 let client = flex_local::local_client_empty();
4380
4381 let (dhcpd, mut requests) = client.create_proxy_and_stream::<fdhcp::Server_Marker>();
4382
4383 let connector = TestConnector { dhcpd: Some(dhcpd), ..Default::default() };
4384 let op = do_dhcpd(cmd.clone(), &connector);
4385 let op_succeeds = async move {
4386 let req = requests
4387 .try_next()
4388 .await
4389 .expect("receiving request")
4390 .expect("request stream should not have ended");
4391 match cmd {
4392 opts::dhcpd::DhcpdEnum::Get(opts::dhcpd::Get { arg }) => match arg {
4393 opts::dhcpd::GetArg::Option(opts::dhcpd::OptionArg { name }) => {
4394 let (code, responder) =
4395 req.into_get_option().expect("request should be of type get option");
4396 assert_eq!(
4397 <opts::dhcpd::Option_ as Into<fdhcp::OptionCode>>::into(name),
4398 code
4399 );
4400 let dummy_result = fdhcp::Option_::SubnetMask(fidl_ip_v4!("255.255.255.0"));
4403 responder.send(Ok(&dummy_result)).expect("responder.send should succeed");
4404 Ok(())
4405 }
4406 opts::dhcpd::GetArg::Parameter(opts::dhcpd::ParameterArg { name }) => {
4407 let (param, responder) = req
4408 .into_get_parameter()
4409 .expect("request should be of type get parameter");
4410 assert_eq!(
4411 <opts::dhcpd::Parameter as Into<fdhcp::ParameterName>>::into(name),
4412 param
4413 );
4414 let dummy_result = fdhcp::Parameter::Lease(fdhcp::LeaseLength::default());
4417 responder.send(Ok(&dummy_result)).expect("responder.send should succeed");
4418 Ok(())
4419 }
4420 },
4421 opts::dhcpd::DhcpdEnum::Set(opts::dhcpd::Set { arg }) => match arg {
4422 opts::dhcpd::SetArg::Option(opts::dhcpd::OptionArg { name }) => {
4423 let (opt, responder) =
4424 req.into_set_option().expect("request should be of type set option");
4425 assert_eq!(<opts::dhcpd::Option_ as Into<fdhcp::Option_>>::into(name), opt);
4426 responder.send(Ok(())).expect("responder.send should succeed");
4427 Ok(())
4428 }
4429 opts::dhcpd::SetArg::Parameter(opts::dhcpd::ParameterArg { name }) => {
4430 let (opt, responder) = req
4431 .into_set_parameter()
4432 .expect("request should be of type set parameter");
4433 assert_eq!(
4434 <opts::dhcpd::Parameter as Into<fdhcp::Parameter>>::into(name),
4435 opt
4436 );
4437 responder.send(Ok(())).expect("responder.send should succeed");
4438 Ok(())
4439 }
4440 },
4441 opts::dhcpd::DhcpdEnum::List(opts::dhcpd::List { arg }) => match arg {
4442 opts::dhcpd::ListArg::Option(opts::dhcpd::OptionToken {}) => {
4443 let responder = req
4444 .into_list_options()
4445 .expect("request should be of type list options");
4446 responder.send(Ok(&[])).expect("responder.send should succeed");
4447 Ok(())
4448 }
4449 opts::dhcpd::ListArg::Parameter(opts::dhcpd::ParameterToken {}) => {
4450 let responder = req
4451 .into_list_parameters()
4452 .expect("request should be of type list options");
4453 responder.send(Ok(&[])).expect("responder.send should succeed");
4454 Ok(())
4455 }
4456 },
4457 opts::dhcpd::DhcpdEnum::Reset(opts::dhcpd::Reset { arg }) => match arg {
4458 opts::dhcpd::ResetArg::Option(opts::dhcpd::OptionToken {}) => {
4459 let responder = req
4460 .into_reset_options()
4461 .expect("request should be of type reset options");
4462 responder.send(Ok(())).expect("responder.send should succeed");
4463 Ok(())
4464 }
4465 opts::dhcpd::ResetArg::Parameter(opts::dhcpd::ParameterToken {}) => {
4466 let responder = req
4467 .into_reset_parameters()
4468 .expect("request should be of type reset parameters");
4469 responder.send(Ok(())).expect("responder.send should succeed");
4470 Ok(())
4471 }
4472 },
4473 opts::dhcpd::DhcpdEnum::ClearLeases(opts::dhcpd::ClearLeases {}) => {
4474 let responder =
4475 req.into_clear_leases().expect("request should be of type clear leases");
4476 responder.send(Ok(())).expect("responder.send should succeed");
4477 Ok(())
4478 }
4479 opts::dhcpd::DhcpdEnum::Start(opts::dhcpd::Start {}) => {
4480 let responder =
4481 req.into_start_serving().expect("request should be of type start serving");
4482 responder.send(Ok(())).expect("responder.send should succeed");
4483 Ok(())
4484 }
4485 opts::dhcpd::DhcpdEnum::Stop(opts::dhcpd::Stop {}) => {
4486 let responder =
4487 req.into_stop_serving().expect("request should be of type stop serving");
4488 responder.send().expect("responder.send should succeed");
4489 Ok(())
4490 }
4491 }
4492 };
4493 let ((), ()) = futures::future::try_join(op, op_succeeds)
4494 .await
4495 .expect("dhcp server command should succeed");
4496 }
4497
4498 #[fuchsia::test]
4499 async fn dns_lookup() {
4500 let client = flex_local::local_client_empty();
4501
4502 let (lookup, mut requests) = client.create_proxy_and_stream::<fname::LookupMarker>();
4503 let connector = TestConnector { name_lookup: Some(lookup), ..Default::default() };
4504
4505 let cmd = opts::dns::DnsEnum::Lookup(opts::dns::Lookup {
4506 hostname: "example.com".to_string(),
4507 ipv4: true,
4508 ipv6: true,
4509 sort: true,
4510 });
4511 let mut output = Vec::new();
4512 let dns_command = do_dns(&mut output, cmd.clone(), &connector)
4513 .map(|result| result.expect("dns command should succeed"));
4514
4515 let handle_request = async move {
4516 let (hostname, options, responder) = requests
4517 .try_next()
4518 .await
4519 .expect("FIDL error")
4520 .expect("request stream should not have ended")
4521 .into_lookup_ip()
4522 .expect("request should be of type LookupIp");
4523 let opts::dns::DnsEnum::Lookup(opts::dns::Lookup {
4524 hostname: want_hostname,
4525 ipv4,
4526 ipv6,
4527 sort,
4528 }) = cmd;
4529 let want_options = fname::LookupIpOptions {
4530 ipv4_lookup: Some(ipv4),
4531 ipv6_lookup: Some(ipv6),
4532 sort_addresses: Some(sort),
4533 ..Default::default()
4534 };
4535 assert_eq!(
4536 hostname, want_hostname,
4537 "received IP lookup request for unexpected hostname"
4538 );
4539 assert_eq!(options, want_options, "received unexpected IP lookup options");
4540
4541 responder
4542 .send(Ok(&fname::LookupResult {
4543 addresses: Some(vec![fidl_ip!("203.0.113.1"), fidl_ip!("2001:db8::1")]),
4544 ..Default::default()
4545 }))
4546 .expect("send response");
4547 };
4548 let ((), ()) = futures::future::join(dns_command, handle_request).await;
4549
4550 const WANT_OUTPUT: &str = "
4551203.0.113.1
45522001:db8::1
4553";
4554 let got_output = std::str::from_utf8(&output).unwrap();
4555 pretty_assertions::assert_eq!(
4556 trim_whitespace_for_comparison(got_output),
4557 trim_whitespace_for_comparison(WANT_OUTPUT),
4558 );
4559 }
4560}