net_cli/opts/
mod.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
5use anyhow::Context as _;
6use argh::{ArgsInfo, FromArgs};
7use std::collections::HashMap;
8use std::convert::{TryFrom as _, TryInto as _};
9use std::num::NonZeroU64;
10use {
11    fidl_fuchsia_net as fnet, fidl_fuchsia_net_ext as fnet_ext,
12    fidl_fuchsia_net_interfaces as finterfaces,
13    fidl_fuchsia_net_interfaces_admin as finterfaces_admin,
14    fidl_fuchsia_net_interfaces_ext as finterfaces_ext, fidl_fuchsia_net_stack as fnet_stack,
15    fidl_fuchsia_net_stackmigrationdeprecated as fnet_migration,
16};
17
18pub(crate) mod dhcpd;
19pub(crate) mod dns;
20pub(crate) mod filter;
21
22#[derive(thiserror::Error, Clone, Debug)]
23#[error("{msg}")]
24pub struct UserFacingError {
25    pub msg: String,
26}
27
28pub fn user_facing_error(cause: impl Into<String>) -> anyhow::Error {
29    UserFacingError { msg: cause.into() }.into()
30}
31
32pub fn underlying_user_facing_error(error: &anyhow::Error) -> Option<UserFacingError> {
33    error.root_cause().downcast_ref::<UserFacingError>().cloned()
34}
35
36fn parse_ip_version_str(value: &str) -> Result<fnet::IpVersion, String> {
37    match &value.to_lowercase()[..] {
38        "ipv4" => Ok(fnet::IpVersion::V4),
39        "ipv6" => Ok(fnet::IpVersion::V6),
40        _ => Err("invalid IP version".to_string()),
41    }
42}
43
44#[derive(ArgsInfo, FromArgs, Debug)]
45/// commands for net-cli
46pub struct Command {
47    #[argh(subcommand)]
48    pub cmd: CommandEnum,
49}
50
51#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
52#[argh(subcommand)]
53pub enum CommandEnum {
54    FilterDeprecated(FilterDeprecated),
55    Filter(filter::Filter),
56    If(If),
57    Log(Log),
58    Neigh(Neigh),
59    Route(Route),
60    Rule(Rule),
61    Dhcp(Dhcp),
62    Dhcpd(dhcpd::Dhcpd),
63    Dns(dns::Dns),
64    NetstackMigration(NetstackMigration),
65}
66
67#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
68#[argh(subcommand, name = "filter-deprecated")]
69/// commands for configuring packet filtering with the deprecated API
70pub struct FilterDeprecated {
71    #[argh(subcommand)]
72    pub filter_cmd: FilterDeprecatedEnum,
73}
74
75#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
76#[argh(subcommand)]
77pub enum FilterDeprecatedEnum {
78    GetNatRules(FilterGetNatRules),
79    GetRdrRules(FilterGetRdrRules),
80    GetRules(FilterGetRules),
81    SetNatRules(FilterSetNatRules),
82    SetRdrRules(FilterSetRdrRules),
83    SetRules(FilterSetRules),
84}
85
86#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
87#[argh(subcommand, name = "get-nat-rules")]
88/// gets nat rules
89pub struct FilterGetNatRules {}
90
91#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
92#[argh(subcommand, name = "get-rdr-rules")]
93/// gets rdr rules
94pub struct FilterGetRdrRules {}
95
96#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
97#[argh(subcommand, name = "get-rules")]
98/// gets filter rules
99pub struct FilterGetRules {}
100
101#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
102#[argh(subcommand, name = "set-nat-rules")]
103/// sets nat rules (see the netfilter::parser library for the NAT rules format)
104pub struct FilterSetNatRules {
105    #[argh(positional)]
106    pub rules: String,
107}
108
109#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
110#[argh(subcommand, name = "set-rdr-rules")]
111/// sets rdr rules (see the netfilter::parser library for the RDR rules format)
112pub struct FilterSetRdrRules {
113    #[argh(positional)]
114    pub rules: String,
115}
116
117#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
118#[argh(subcommand, name = "set-rules")]
119/// sets filter rules (see the netfilter::parser library for the rules format)
120pub struct FilterSetRules {
121    #[argh(positional)]
122    pub rules: String,
123}
124
125#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
126#[argh(subcommand, name = "if")]
127/// commands for network interfaces
128pub struct If {
129    #[argh(subcommand)]
130    pub if_cmd: IfEnum,
131}
132
133#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
134#[argh(subcommand)]
135pub enum IfEnum {
136    Addr(IfAddr),
137    Bridge(IfBridge),
138    Config(IfConfig),
139    Disable(IfDisable),
140    Enable(IfEnable),
141    Get(IfGet),
142    Igmp(IfIgmp),
143    IpForward(IfIpForward),
144    List(IfList),
145    Mld(IfMld),
146    Add(IfAdd),
147    Remove(IfRemove),
148}
149
150#[derive(Clone, Debug, PartialEq)]
151pub enum InterfaceIdentifier {
152    Id(u64),
153    Name(String),
154}
155
156impl InterfaceIdentifier {
157    pub async fn find_nicid<C>(&self, connector: &C) -> Result<u64, anyhow::Error>
158    where
159        C: crate::ServiceConnector<finterfaces::StateMarker>,
160    {
161        match self {
162            Self::Id(id) => Ok(*id),
163            Self::Name(name) => {
164                let interfaces_state = crate::connect_with_context(connector).await?;
165                let stream = finterfaces_ext::event_stream_from_state::<
166                    finterfaces_ext::AllInterest,
167                >(&interfaces_state, Default::default())?;
168                let response = finterfaces_ext::existing(
169                    stream,
170                    HashMap::<NonZeroU64, finterfaces_ext::PropertiesAndState<(), _>>::new(),
171                )
172                .await?;
173                response
174                    .values()
175                    .find_map(|properties_and_state| {
176                        (&properties_and_state.properties.name == name)
177                            .then(|| properties_and_state.properties.id.get())
178                    })
179                    .ok_or_else(|| user_facing_error(format!("No interface with name {}", name)))
180            }
181        }
182    }
183
184    pub async fn find_u32_nicid<C>(&self, connector: &C) -> Result<u32, anyhow::Error>
185    where
186        C: crate::ServiceConnector<finterfaces::StateMarker>,
187    {
188        let id = self.find_nicid(connector).await?;
189        u32::try_from(id).with_context(|| format!("nicid {} does not fit in u32", id))
190    }
191}
192
193impl core::str::FromStr for InterfaceIdentifier {
194    type Err = anyhow::Error;
195
196    fn from_str(s: &str) -> Result<Self, Self::Err> {
197        let nicid_parse_result = s.parse::<u64>();
198        nicid_parse_result.map_or_else(
199            |nicid_parse_error| {
200                if !s.starts_with("name:") {
201                    Err(user_facing_error(format!(
202                        "Failed to parse as NICID (error: {}) or as interface name \
203                        (error: interface names must be specified as `name:ifname`, where \
204                        ifname is the actual interface name in this example)",
205                        nicid_parse_error
206                    )))
207                } else {
208                    Ok(Self::Name(s["name:".len()..].to_string()))
209                }
210            },
211            |nicid| Ok(Self::Id(nicid)),
212        )
213    }
214}
215
216impl From<u64> for InterfaceIdentifier {
217    fn from(nicid: u64) -> InterfaceIdentifier {
218        InterfaceIdentifier::Id(nicid)
219    }
220}
221
222#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
223#[argh(subcommand, name = "addr")]
224/// commands for updating network interface addresses
225pub struct IfAddr {
226    #[argh(subcommand)]
227    pub addr_cmd: IfAddrEnum,
228}
229
230#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
231#[argh(subcommand)]
232pub enum IfAddrEnum {
233    Add(IfAddrAdd),
234    Del(IfAddrDel),
235    Wait(IfAddrWait),
236}
237
238#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
239#[argh(subcommand, name = "add")]
240/// adds an address to the network interface
241pub struct IfAddrAdd {
242    #[argh(positional, arg_name = "nicid or name:ifname")]
243    pub interface: InterfaceIdentifier,
244    #[argh(positional)]
245    pub addr: String,
246    #[argh(positional, from_str_fn(parse_netmask_or_prefix_length))]
247    pub prefix: u8,
248    #[argh(switch)]
249    /// skip adding a local subnet route for this interface and address
250    pub no_subnet_route: bool,
251}
252
253#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
254#[argh(subcommand, name = "del")]
255/// deletes an address from the network interface
256pub struct IfAddrDel {
257    #[argh(positional, arg_name = "nicid or name:ifname")]
258    pub interface: InterfaceIdentifier,
259    #[argh(positional)]
260    pub addr: String,
261    #[argh(positional, from_str_fn(parse_netmask_or_prefix_length))]
262    pub prefix: Option<u8>,
263}
264
265#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
266#[argh(subcommand, name = "wait")]
267/// waits for an address to be assigned on the network interface.
268///
269/// by default waits for any address; if --ipv6 is specified, waits for an IPv6
270/// address.
271pub struct IfAddrWait {
272    #[argh(positional, arg_name = "nicid or name:ifname")]
273    pub interface: InterfaceIdentifier,
274    /// wait for an IPv6 address
275    #[argh(switch)]
276    pub ipv6: bool,
277}
278
279#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
280#[argh(subcommand, name = "bridge")]
281/// creates a bridge between network interfaces
282pub struct IfBridge {
283    #[argh(positional, arg_name = "nicid or name:ifname")]
284    pub interfaces: Vec<InterfaceIdentifier>,
285}
286
287#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
288#[argh(subcommand, name = "disable")]
289/// disables a network interface
290pub struct IfDisable {
291    #[argh(positional, arg_name = "nicid or name:ifname")]
292    pub interface: InterfaceIdentifier,
293}
294
295#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
296#[argh(subcommand, name = "enable")]
297/// enables a network interface
298pub struct IfEnable {
299    #[argh(positional, arg_name = "nicid or name:ifname")]
300    pub interface: InterfaceIdentifier,
301}
302
303#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
304#[argh(subcommand, name = "get")]
305/// queries a network interface
306pub struct IfGet {
307    #[argh(positional, arg_name = "nicid or name:ifname")]
308    pub interface: InterfaceIdentifier,
309}
310
311// TODO(https://fxbug.dev/371584272): Replace this with if config.
312#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
313#[argh(subcommand, name = "igmp")]
314/// get or set IGMP configuration
315pub struct IfIgmp {
316    #[argh(subcommand)]
317    pub cmd: IfIgmpEnum,
318}
319
320// TODO(https://fxbug.dev/371584272): Replace this with if config.
321#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
322#[argh(subcommand)]
323pub enum IfIgmpEnum {
324    Get(IfIgmpGet),
325    Set(IfIgmpSet),
326}
327
328// TODO(https://fxbug.dev/371584272): Replace this with if config get.
329#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
330#[argh(subcommand, name = "get")]
331/// get IGMP configuration for an interface
332pub struct IfIgmpGet {
333    #[argh(positional, arg_name = "nicid or name:ifname")]
334    pub interface: InterfaceIdentifier,
335}
336
337// TODO(https://fxbug.dev/371584272): Replace this with if config set.
338#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
339#[argh(subcommand, name = "set")]
340/// set IGMP configuration for an interface
341pub struct IfIgmpSet {
342    #[argh(positional, arg_name = "nicid or name:ifname")]
343    pub interface: InterfaceIdentifier,
344
345    /// the version of IGMP to perform.
346    #[argh(option, from_str_fn(parse_igmp_version))]
347    pub version: Option<finterfaces_admin::IgmpVersion>,
348}
349
350fn parse_igmp_version(s: &str) -> Result<finterfaces_admin::IgmpVersion, String> {
351    match s.parse::<u8>() {
352        Err(err) => Err(format!("Failed to parse IGMP version (error: {})", err)),
353        Ok(v) => match v {
354            1 => Ok(finterfaces_admin::IgmpVersion::V1),
355            2 => Ok(finterfaces_admin::IgmpVersion::V2),
356            3 => Ok(finterfaces_admin::IgmpVersion::V3),
357            v => Err(format!("Invalid IGMP version ({}). Valid values: 1, 2, 3", v)),
358        },
359    }
360}
361
362// TODO(https://fxbug.dev/371584272): Replace this with if config.
363#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
364#[argh(subcommand, name = "ip-forward")]
365/// get or set IP forwarding for an interface
366pub struct IfIpForward {
367    #[argh(subcommand)]
368    pub cmd: IfIpForwardEnum,
369}
370
371// TODO(https://fxbug.dev/371584272): Replace this with if config.
372#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
373#[argh(subcommand)]
374pub enum IfIpForwardEnum {
375    Get(IfIpForwardGet),
376    Set(IfIpForwardSet),
377}
378
379// TODO(https://fxbug.dev/371584272): Replace this with if config get.
380#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
381#[argh(subcommand, name = "get")]
382/// get IP forwarding for an interface
383pub struct IfIpForwardGet {
384    #[argh(positional, arg_name = "nicid or name:ifname")]
385    pub interface: InterfaceIdentifier,
386
387    #[argh(positional, from_str_fn(parse_ip_version_str))]
388    pub ip_version: fnet::IpVersion,
389}
390
391// TODO(https://fxbug.dev/371584272): Replace this with if config set.
392#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
393#[argh(subcommand, name = "set")]
394/// set IP forwarding for an interface
395pub struct IfIpForwardSet {
396    #[argh(positional, arg_name = "nicid or name:ifname")]
397    pub interface: InterfaceIdentifier,
398
399    #[argh(positional, from_str_fn(parse_ip_version_str))]
400    pub ip_version: fnet::IpVersion,
401
402    #[argh(positional)]
403    pub enable: bool,
404}
405
406#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
407#[argh(subcommand, name = "list")]
408/// lists network interfaces (supports ffx machine output)
409pub struct IfList {
410    #[argh(positional)]
411    pub name_pattern: Option<String>,
412}
413
414#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
415#[argh(subcommand, name = "add")]
416/// add interfaces
417pub struct IfAdd {
418    #[argh(subcommand)]
419    pub cmd: IfAddEnum,
420}
421
422#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
423#[argh(subcommand)]
424pub enum IfAddEnum {
425    Blackhole(IfBlackholeAdd),
426}
427
428#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
429#[argh(subcommand, name = "blackhole")]
430/// add a blackhole interface
431pub struct IfBlackholeAdd {
432    #[argh(positional)]
433    pub name: String,
434}
435
436#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
437#[argh(subcommand, name = "remove")]
438/// remove interfaces
439pub struct IfRemove {
440    #[argh(positional, arg_name = "nicid or name:ifname")]
441    pub interface: InterfaceIdentifier,
442}
443
444// TODO(https://fxbug.dev/371584272): Replace this with if config.
445#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
446#[argh(subcommand, name = "mld")]
447/// get or set MLD configuration
448pub struct IfMld {
449    #[argh(subcommand)]
450    pub cmd: IfMldEnum,
451}
452
453// TODO(https://fxbug.dev/371584272): Replace this with if config.
454#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
455#[argh(subcommand)]
456pub enum IfMldEnum {
457    Get(IfMldGet),
458    Set(IfMldSet),
459}
460
461// TODO(https://fxbug.dev/371584272): Replace this with if config get.
462#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
463#[argh(subcommand, name = "get")]
464/// get MLD configuration for an interface
465pub struct IfMldGet {
466    #[argh(positional, arg_name = "nicid or name:ifname")]
467    pub interface: InterfaceIdentifier,
468}
469
470// TODO(https://fxbug.dev/371584272): Replace this with if config set.
471#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
472#[argh(subcommand, name = "set")]
473/// set MLD configuration for an interface
474pub struct IfMldSet {
475    #[argh(positional, arg_name = "nicid or name:ifname")]
476    pub interface: InterfaceIdentifier,
477
478    /// the version of MLD to perform.
479    #[argh(option, from_str_fn(parse_mld_version))]
480    pub version: Option<finterfaces_admin::MldVersion>,
481}
482
483fn parse_mld_version(s: &str) -> Result<finterfaces_admin::MldVersion, String> {
484    match s.parse::<u8>() {
485        Err(err) => Err(format!("Failed to parse MLD version (error: {})", err)),
486        Ok(v) => match v {
487            1 => Ok(finterfaces_admin::MldVersion::V1),
488            2 => Ok(finterfaces_admin::MldVersion::V2),
489            v => Err(format!("Invalid MLD version ({}). Valid values: 1, 2", v)),
490        },
491    }
492}
493
494#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
495#[argh(subcommand, name = "config")]
496/// get or set interface configuration
497pub struct IfConfig {
498    #[argh(positional, arg_name = "nicid or name:ifname")]
499    pub interface: InterfaceIdentifier,
500
501    #[argh(subcommand)]
502    pub cmd: IfConfigEnum,
503}
504
505#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
506#[argh(subcommand)]
507/// get configuration for an interface
508pub enum IfConfigEnum {
509    Set(IfConfigSet),
510    Get(IfConfigGet),
511}
512
513#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
514#[argh(subcommand, name = "set")]
515// TODO(https://fxbug.dev/370850762): Generate the help string programmatically
516// so that it can live next to the parsing logic.
517/** set interface configuration
518
519Configuration parameters and the values to be set to should be passed
520in pairs. The names of the configuration parameters are taken from
521the structure of fuchsia.net.interfaces.admin/Configuration.
522
523The list of supported parameters are:
524  ipv6.ndp.slaac.temporary_address_enabled
525    bool
526    Whether temporary addresses should be generated.
527*/
528pub struct IfConfigSet {
529    /// the config parameter names and the
530    #[argh(positional)]
531    pub options: Vec<String>,
532}
533
534#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
535#[argh(subcommand, name = "get")]
536/// get interface configuration
537pub struct IfConfigGet {}
538
539#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
540#[argh(subcommand, name = "log")]
541/// commands for logging
542pub struct Log {
543    #[argh(subcommand)]
544    pub log_cmd: LogEnum,
545}
546
547#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
548#[argh(subcommand)]
549pub enum LogEnum {
550    SetPackets(LogSetPackets),
551}
552
553#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
554#[argh(subcommand, name = "set-packets")]
555/// log packets to stdout
556pub struct LogSetPackets {
557    #[argh(positional)]
558    pub enabled: bool,
559}
560
561#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
562#[argh(subcommand, name = "neigh")]
563/// commands for neighbor tables
564pub struct Neigh {
565    #[argh(subcommand)]
566    pub neigh_cmd: NeighEnum,
567}
568
569#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
570#[argh(subcommand)]
571pub enum NeighEnum {
572    Add(NeighAdd),
573    Clear(NeighClear),
574    Del(NeighDel),
575    List(NeighList),
576    Watch(NeighWatch),
577    Config(NeighConfig),
578}
579
580#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
581#[argh(subcommand, name = "add")]
582/// adds an entry to the neighbor table
583pub struct NeighAdd {
584    #[argh(positional, arg_name = "nicid or name:ifname")]
585    pub interface: InterfaceIdentifier,
586    #[argh(positional)]
587    pub ip: fnet_ext::IpAddress,
588    #[argh(positional)]
589    pub mac: fnet_ext::MacAddress,
590}
591
592#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
593#[argh(subcommand, name = "clear")]
594/// removes all entries associated with a network interface from the neighbor table
595pub struct NeighClear {
596    #[argh(positional, arg_name = "nicid or name:ifname")]
597    pub interface: InterfaceIdentifier,
598
599    #[argh(positional, from_str_fn(parse_ip_version_str))]
600    pub ip_version: fnet::IpVersion,
601}
602
603#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
604#[argh(subcommand, name = "list")]
605/// lists neighbor table entries (supports ffx machine output)
606pub struct NeighList {}
607
608#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
609#[argh(subcommand, name = "del")]
610/// removes an entry from the neighbor table
611pub struct NeighDel {
612    #[argh(positional, arg_name = "nicid or name:ifname")]
613    pub interface: InterfaceIdentifier,
614    #[argh(positional)]
615    pub ip: fnet_ext::IpAddress,
616}
617
618#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
619#[argh(subcommand, name = "watch")]
620/// watches neighbor table entries for state changes (supports ffx machine output)
621pub struct NeighWatch {}
622
623#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
624#[argh(subcommand, name = "config")]
625/// commands for the Neighbor Unreachability Detection configuration
626pub struct NeighConfig {
627    #[argh(subcommand)]
628    pub neigh_config_cmd: NeighConfigEnum,
629}
630
631#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
632#[argh(subcommand)]
633pub enum NeighConfigEnum {
634    Get(NeighGetConfig),
635    Update(NeighUpdateConfig),
636}
637
638#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
639#[argh(subcommand, name = "get")]
640/// returns the current NUD configuration options for the provided interface
641pub struct NeighGetConfig {
642    #[argh(positional, arg_name = "nicid or name:ifname")]
643    pub interface: InterfaceIdentifier,
644
645    #[argh(positional, from_str_fn(parse_ip_version_str))]
646    pub ip_version: fnet::IpVersion,
647}
648
649#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
650#[argh(subcommand, name = "update")]
651/// updates the current NUD configuration options for the provided interface
652pub struct NeighUpdateConfig {
653    #[argh(positional, arg_name = "nicid or name:ifname")]
654    pub interface: InterfaceIdentifier,
655
656    #[argh(positional, from_str_fn(parse_ip_version_str))]
657    pub ip_version: fnet::IpVersion,
658
659    /// a base duration, in nanoseconds, for computing the random reachable
660    /// time
661    #[argh(option)]
662    pub base_reachable_time: Option<i64>,
663}
664
665#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
666#[argh(subcommand, name = "route")]
667/// commands for routing tables
668pub struct Route {
669    #[argh(subcommand)]
670    pub route_cmd: RouteEnum,
671}
672
673#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
674#[argh(subcommand)]
675pub enum RouteEnum {
676    List(RouteList),
677    Add(RouteAdd),
678    Del(RouteDel),
679}
680
681fn parse_netmask_or_prefix_length(s: &str) -> Result<u8, String> {
682    let netmask_parse_result = s.parse::<std::net::IpAddr>();
683    let prefix_len_parse_result = s.parse::<u8>();
684    match (netmask_parse_result, prefix_len_parse_result) {
685        (Err(netmask_parse_error), Err(prefix_len_parse_error)) => Err(format!(
686            "Failed to parse as netmask (error: {}) or prefix length (error: {})",
687            netmask_parse_error, prefix_len_parse_error,
688        )),
689        (Ok(_), Ok(_)) => Err(format!(
690            "Input parses both as netmask and as prefix length. This should never happen."
691        )),
692        (Ok(netmask), Err(_)) => Ok(subnet_mask_to_prefix_length(netmask)),
693        (Err(_), Ok(prefix_len)) => Ok(prefix_len),
694    }
695}
696
697fn subnet_mask_to_prefix_length(addr: std::net::IpAddr) -> u8 {
698    match addr {
699        std::net::IpAddr::V4(addr) => (!u32::from_be_bytes(addr.octets())).leading_zeros(),
700        std::net::IpAddr::V6(addr) => (!u128::from_be_bytes(addr.octets())).leading_zeros(),
701    }
702    .try_into()
703    .unwrap()
704}
705
706#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
707#[argh(subcommand, name = "list")]
708/// lists routes (supports ffx machine output)
709pub struct RouteList {}
710
711macro_rules! route_struct {
712    ($ty_name:ident, $name:literal, $comment:expr) => {
713        #[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
714        #[argh(subcommand, name = $name)]
715        #[doc = $comment]
716        pub struct $ty_name {
717            #[argh(option)]
718            /// the network id of the destination network
719            pub destination: std::net::IpAddr,
720            #[argh(
721                option,
722                arg_name = "netmask or prefix length",
723                from_str_fn(parse_netmask_or_prefix_length),
724                long = "netmask"
725            )]
726            /// the netmask or prefix length corresponding to destination
727            pub prefix_len: u8,
728            #[argh(option)]
729            /// the ip address of the first hop router
730            pub gateway: Option<std::net::IpAddr>,
731            #[argh(
732                option,
733                arg_name = "nicid or name:ifname",
734                default = "InterfaceIdentifier::Id(0)",
735                long = "nicid"
736            )]
737            /// the outgoing network interface of the route
738            pub interface: InterfaceIdentifier,
739            #[argh(option, default = "0")]
740            /// the metric for the route
741            pub metric: u32,
742        }
743
744        impl $ty_name {
745            pub fn into_route_table_entry(self, nicid: u32) -> fnet_stack::ForwardingEntry {
746                let Self { destination, prefix_len, gateway, interface: _, metric } = self;
747                fnet_stack::ForwardingEntry {
748                    subnet: fnet_ext::apply_subnet_mask(fnet::Subnet {
749                        addr: fnet_ext::IpAddress(destination).into(),
750                        prefix_len,
751                    }),
752                    device_id: nicid.into(),
753                    next_hop: gateway.map(|gateway| Box::new(fnet_ext::IpAddress(gateway).into())),
754                    metric,
755                }
756            }
757        }
758    };
759}
760
761// TODO(https://github.com/google/argh/issues/48): do this more sanely.
762route_struct!(RouteAdd, "add", "adds a route to the route table");
763route_struct!(RouteDel, "del", "deletes a route from the route table");
764
765#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
766#[argh(subcommand, name = "rule")]
767/// commands for policy-based-routing rules
768pub struct Rule {
769    #[argh(subcommand)]
770    pub rule_cmd: RuleEnum,
771}
772
773#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
774#[argh(subcommand)]
775pub enum RuleEnum {
776    List(RuleList),
777}
778
779#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
780#[argh(subcommand, name = "list")]
781/// lists rules (supports ffx machine output)
782pub struct RuleList {}
783
784#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
785#[argh(subcommand, name = "dhcp")]
786/// commands for an interfaces dhcp client
787pub struct Dhcp {
788    #[argh(subcommand)]
789    pub dhcp_cmd: DhcpEnum,
790}
791
792#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
793#[argh(subcommand)]
794pub enum DhcpEnum {
795    Start(DhcpStart),
796    Stop(DhcpStop),
797}
798
799#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
800#[argh(subcommand, name = "start")]
801/// starts a dhcp client on the interface
802pub struct DhcpStart {
803    #[argh(positional, arg_name = "nicid or name:ifname")]
804    pub interface: InterfaceIdentifier,
805}
806
807#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
808#[argh(subcommand, name = "stop")]
809/// stops the dhcp client on the interface
810pub struct DhcpStop {
811    #[argh(positional, arg_name = "nicid or name:ifname")]
812    pub interface: InterfaceIdentifier,
813}
814
815#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
816#[argh(subcommand, name = "migration")]
817/// controls netstack selection for migration from netstack2 to netstack3
818pub struct NetstackMigration {
819    #[argh(subcommand)]
820    pub cmd: NetstackMigrationEnum,
821}
822
823#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
824#[argh(subcommand)]
825pub enum NetstackMigrationEnum {
826    Set(NetstackMigrationSet),
827    Get(NetstackMigrationGet),
828    Clear(NetstackMigrationClear),
829}
830
831fn parse_netstack_version(s: &str) -> Result<fnet_migration::NetstackVersion, String> {
832    match s {
833        "ns2" => Ok(fnet_migration::NetstackVersion::Netstack2),
834        "ns3" => Ok(fnet_migration::NetstackVersion::Netstack3),
835        // NB: argh already prints the bad value to the user, no need to repeat
836        // it.
837        _ => Err(format!("valid values are ns2 or ns3")),
838    }
839}
840
841#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
842#[argh(subcommand, name = "set")]
843/// sets the netstack version at next boot to |ns2| or |ns3|.
844pub struct NetstackMigrationSet {
845    #[argh(positional, from_str_fn(parse_netstack_version))]
846    /// ns2 or ns3
847    pub version: fnet_migration::NetstackVersion,
848}
849
850#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
851#[argh(subcommand, name = "get")]
852/// prints the currently configured netstack version for migration.
853pub struct NetstackMigrationGet {}
854
855#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
856#[argh(subcommand, name = "clear")]
857/// clears netstack version for migration configuration.
858pub struct NetstackMigrationClear {}
859
860#[cfg(test)]
861mod tests {
862    use super::*;
863    use assert_matches::assert_matches;
864    use test_case::test_case;
865
866    #[test_case("24", 24 ; "from prefix length")]
867    #[test_case("255.255.254.0", 23 ; "from ipv4 netmask")]
868    #[test_case("ffff:fff0::", 28 ; "from ipv6 netmask")]
869    fn parse_prefix_len(to_parse: &str, want: u8) {
870        let got = parse_netmask_or_prefix_length(to_parse).unwrap();
871        assert_eq!(got, want)
872    }
873
874    proptest::proptest! {
875        #[test]
876        fn cant_parse_as_both_netmask_and_prefix_len(s: String) {
877            let netmask_parse_result = s.parse::<std::net::IpAddr>();
878            let prefix_len_parse_result = s.parse::<u8>();
879            assert_matches!((netmask_parse_result, prefix_len_parse_result), (Ok(_), Err(_)) | (Err(_), Ok(_)) | (Err(_), Err(_)));
880        }
881    }
882
883    #[test_case("1", InterfaceIdentifier::Id(1) ; "as nicid")]
884    #[test_case("name:lo", InterfaceIdentifier::Name("lo".to_string()) ; "as ifname")]
885    #[test_case("name:name:lo", InterfaceIdentifier::Name("name:lo".to_string()) ; "as ifname with 'name:' as part of name")]
886    #[test_case("name:1", InterfaceIdentifier::Name("1".to_string()) ; "as numerical ifname")]
887    fn parse_interface(to_parse: &str, want: InterfaceIdentifier) {
888        let got = to_parse.parse::<InterfaceIdentifier>().unwrap();
889        assert_eq!(got, want)
890    }
891
892    #[test]
893    fn parse_interface_without_prefix_fails() {
894        assert_matches!("lo".parse::<InterfaceIdentifier>(), Err(_))
895    }
896
897    #[test]
898    fn into_route_table_entry_applies_subnet_mask() {
899        // Leave off last two 16-bit segments.
900        const PREFIX_LEN: u8 = 128 - 32;
901        const ORIGINAL_NICID: u32 = 1;
902        const NICID_TO_OVERWRITE_WITH: u32 = 2;
903        assert_eq!(
904            RouteAdd {
905                destination: std::net::IpAddr::V6(std::net::Ipv6Addr::new(
906                    0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
907                )),
908                prefix_len: PREFIX_LEN,
909                gateway: None,
910                interface: u64::from(ORIGINAL_NICID).into(),
911                metric: 100,
912            }
913            .into_route_table_entry(NICID_TO_OVERWRITE_WITH),
914            fnet_stack::ForwardingEntry {
915                subnet: fnet::Subnet {
916                    addr: net_declare::fidl_ip!("ffff:ffff:ffff:ffff:ffff:ffff:0:0"),
917                    prefix_len: PREFIX_LEN,
918                },
919                // The interface ID in the RouteAdd struct should be overwritten by the NICID
920                // parameter passed to `into_route_table_entry`.
921                device_id: NICID_TO_OVERWRITE_WITH.into(),
922                next_hop: None,
923                metric: 100,
924            }
925        )
926    }
927
928    #[test_case("0", Err("Invalid IGMP version (0). Valid values: 1, 2, 3".to_string()); "input_0")]
929    #[test_case("1", Ok(finterfaces_admin::IgmpVersion::V1); "input_1")]
930    #[test_case("2", Ok(finterfaces_admin::IgmpVersion::V2); "input_2")]
931    #[test_case("3", Ok(finterfaces_admin::IgmpVersion::V3); "input_3")]
932    #[test_case("4", Err("Invalid IGMP version (4). Valid values: 1, 2, 3".to_string()); "input_4")]
933    #[test_case("a", Err("Failed to parse IGMP version (error: invalid digit found in string)".to_string()); "input_a")]
934    fn igmp_version(s: &str, expected: Result<finterfaces_admin::IgmpVersion, String>) {
935        assert_eq!(parse_igmp_version(s), expected)
936    }
937
938    #[test_case("0", Err("Invalid MLD version (0). Valid values: 1, 2".to_string()); "input_0")]
939    #[test_case("1", Ok(finterfaces_admin::MldVersion::V1); "input_1")]
940    #[test_case("2", Ok(finterfaces_admin::MldVersion::V2); "input_2")]
941    #[test_case("3", Err("Invalid MLD version (3). Valid values: 1, 2".to_string()); "input_3")]
942    #[test_case("a", Err("Failed to parse MLD version (error: invalid digit found in string)".to_string()); "input_a")]
943    fn mld_version(s: &str, expected: Result<finterfaces_admin::MldVersion, String>) {
944        assert_eq!(parse_mld_version(s), expected)
945    }
946}