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