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