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