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