net_cli/
lib.rs

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