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