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