net_cli/
lib.rs

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