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