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