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