Skip to main content

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