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