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