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 std::collections::HashMap;
15use std::convert::TryInto as _;
16use std::num::NonZeroU64;
17
18pub(crate) mod capture;
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    Capture(capture::Capture),
56    FilterDeprecated(FilterDeprecated),
57    Filter(filter::Filter),
58    If(If),
59    Log(Log),
60    Neigh(Neigh),
61    Route(Route),
62    Rule(Rule),
63    Dhcp(Dhcp),
64    Dhcpd(dhcpd::Dhcpd),
65    Dns(dns::Dns),
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  ipv6.ndp.dad.transmits
521    u16
522    Number of DAD transmissions before an address is considered available for use.
523  ipv6.ndp.router_solicitations.max
524    u8
525    Maximum number of Router Solicitations to send.
526*/
527pub struct IfConfigSet {
528    /// the config parameter names and the
529    #[argh(positional)]
530    pub options: Vec<String>,
531}
532
533#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
534#[argh(subcommand, name = "get")]
535/// get interface configuration
536pub struct IfConfigGet {}
537
538#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
539#[argh(subcommand, name = "log")]
540/// commands for logging
541pub struct Log {
542    #[argh(subcommand)]
543    pub log_cmd: LogEnum,
544}
545
546#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
547#[argh(subcommand)]
548pub enum LogEnum {
549    SetPackets(LogSetPackets),
550}
551
552#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
553#[argh(subcommand, name = "set-packets")]
554/// log packets to stdout
555pub struct LogSetPackets {
556    #[argh(positional)]
557    pub enabled: bool,
558}
559
560#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
561#[argh(subcommand, name = "neigh")]
562/// commands for neighbor tables
563pub struct Neigh {
564    #[argh(subcommand)]
565    pub neigh_cmd: NeighEnum,
566}
567
568#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
569#[argh(subcommand)]
570pub enum NeighEnum {
571    Add(NeighAdd),
572    Clear(NeighClear),
573    Del(NeighDel),
574    List(NeighList),
575    Watch(NeighWatch),
576    Config(NeighConfig),
577}
578
579#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
580#[argh(subcommand, name = "add")]
581/// adds an entry to the neighbor table
582pub struct NeighAdd {
583    #[argh(positional, arg_name = "nicid or name:ifname")]
584    pub interface: InterfaceIdentifier,
585    #[argh(positional)]
586    pub ip: fnet_ext::IpAddress,
587    #[argh(positional)]
588    pub mac: fnet_ext::MacAddress,
589}
590
591#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
592#[argh(subcommand, name = "clear")]
593/// removes all entries associated with a network interface from the neighbor table
594pub struct NeighClear {
595    #[argh(positional, arg_name = "nicid or name:ifname")]
596    pub interface: InterfaceIdentifier,
597
598    #[argh(positional, from_str_fn(parse_ip_version_str))]
599    pub ip_version: fnet::IpVersion,
600}
601
602#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
603#[argh(subcommand, name = "list")]
604/// lists neighbor table entries (supports ffx machine output)
605pub struct NeighList {}
606
607#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
608#[argh(subcommand, name = "del")]
609/// removes an entry from the neighbor table
610pub struct NeighDel {
611    #[argh(positional, arg_name = "nicid or name:ifname")]
612    pub interface: InterfaceIdentifier,
613    #[argh(positional)]
614    pub ip: fnet_ext::IpAddress,
615}
616
617#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
618#[argh(subcommand, name = "watch")]
619/// watches neighbor table entries for state changes (supports ffx machine output)
620pub struct NeighWatch {}
621
622#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
623#[argh(subcommand, name = "config")]
624/// commands for the Neighbor Unreachability Detection configuration
625pub struct NeighConfig {
626    #[argh(subcommand)]
627    pub neigh_config_cmd: NeighConfigEnum,
628}
629
630#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
631#[argh(subcommand)]
632pub enum NeighConfigEnum {
633    Get(NeighGetConfig),
634    Update(NeighUpdateConfig),
635}
636
637#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
638#[argh(subcommand, name = "get")]
639/// returns the current NUD configuration options for the provided interface
640pub struct NeighGetConfig {
641    #[argh(positional, arg_name = "nicid or name:ifname")]
642    pub interface: InterfaceIdentifier,
643
644    #[argh(positional, from_str_fn(parse_ip_version_str))]
645    pub ip_version: fnet::IpVersion,
646}
647
648#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
649#[argh(subcommand, name = "update")]
650/// updates the current NUD configuration options for the provided interface
651pub struct NeighUpdateConfig {
652    #[argh(positional, arg_name = "nicid or name:ifname")]
653    pub interface: InterfaceIdentifier,
654
655    #[argh(positional, from_str_fn(parse_ip_version_str))]
656    pub ip_version: fnet::IpVersion,
657
658    /// a base duration, in nanoseconds, for computing the random reachable
659    /// time
660    #[argh(option)]
661    pub base_reachable_time: Option<i64>,
662}
663
664#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
665#[argh(subcommand, name = "route")]
666/// commands for routing tables
667pub struct Route {
668    #[argh(subcommand)]
669    pub route_cmd: RouteEnum,
670}
671
672#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
673#[argh(subcommand)]
674pub enum RouteEnum {
675    List(RouteList),
676    Add(RouteAdd),
677    Del(RouteDel),
678}
679
680fn parse_netmask_or_prefix_length(s: &str) -> Result<u8, String> {
681    let netmask_parse_result = s.parse::<std::net::IpAddr>();
682    let prefix_len_parse_result = s.parse::<u8>();
683    match (netmask_parse_result, prefix_len_parse_result) {
684        (Err(netmask_parse_error), Err(prefix_len_parse_error)) => Err(format!(
685            "Failed to parse as netmask (error: {}) or prefix length (error: {})",
686            netmask_parse_error, prefix_len_parse_error,
687        )),
688        (Ok(_), Ok(_)) => Err(format!(
689            "Input parses both as netmask and as prefix length. This should never happen."
690        )),
691        (Ok(netmask), Err(_)) => Ok(subnet_mask_to_prefix_length(netmask)),
692        (Err(_), Ok(prefix_len)) => Ok(prefix_len),
693    }
694}
695
696fn subnet_mask_to_prefix_length(addr: std::net::IpAddr) -> u8 {
697    match addr {
698        std::net::IpAddr::V4(addr) => (!u32::from_be_bytes(addr.octets())).leading_zeros(),
699        std::net::IpAddr::V6(addr) => (!u128::from_be_bytes(addr.octets())).leading_zeros(),
700    }
701    .try_into()
702    .unwrap()
703}
704
705#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
706#[argh(subcommand, name = "list")]
707/// lists routes (supports ffx machine output)
708pub struct RouteList {}
709
710macro_rules! route_struct {
711    ($ty_name:ident, $name:literal, $comment:expr) => {
712        #[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
713        #[argh(subcommand, name = $name)]
714        #[doc = $comment]
715        pub struct $ty_name {
716            #[argh(option)]
717            /// the network id of the destination network
718            pub destination: std::net::IpAddr,
719            #[argh(
720                option,
721                arg_name = "netmask or prefix length",
722                from_str_fn(parse_netmask_or_prefix_length),
723                long = "netmask"
724            )]
725            /// the netmask or prefix length corresponding to destination
726            pub prefix_len: u8,
727            #[argh(option)]
728            /// the ip address of the first hop router
729            pub gateway: Option<std::net::IpAddr>,
730            #[argh(option, arg_name = "nicid or name:ifname", long = "nicid")]
731            /// the outgoing network interface of the route
732            pub interface: InterfaceIdentifier,
733            #[argh(option)]
734            /// the metric for the route
735            pub metric: Option<u32>,
736        }
737    };
738}
739
740// TODO(https://github.com/google/argh/issues/48): do this more sanely.
741route_struct!(RouteAdd, "add", "adds a route to the route table");
742route_struct!(RouteDel, "del", "deletes a route from the route table");
743
744#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
745#[argh(subcommand, name = "rule")]
746/// commands for policy-based-routing rules
747pub struct Rule {
748    #[argh(subcommand)]
749    pub rule_cmd: RuleEnum,
750}
751
752#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
753#[argh(subcommand)]
754pub enum RuleEnum {
755    List(RuleList),
756}
757
758#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
759#[argh(subcommand, name = "list")]
760/// lists rules (supports ffx machine output)
761pub struct RuleList {}
762
763#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
764#[argh(subcommand, name = "dhcp")]
765/// commands for an interfaces dhcp client
766pub struct Dhcp {
767    #[argh(subcommand)]
768    pub dhcp_cmd: DhcpEnum,
769}
770
771#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
772#[argh(subcommand)]
773pub enum DhcpEnum {
774    Start(DhcpStart),
775    Stop(DhcpStop),
776}
777
778#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
779#[argh(subcommand, name = "start")]
780/// starts a dhcp client on the interface
781pub struct DhcpStart {
782    #[argh(positional, arg_name = "nicid or name:ifname")]
783    pub interface: InterfaceIdentifier,
784}
785
786#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
787#[argh(subcommand, name = "stop")]
788/// stops the dhcp client on the interface
789pub struct DhcpStop {
790    #[argh(positional, arg_name = "nicid or name:ifname")]
791    pub interface: InterfaceIdentifier,
792}
793
794#[cfg(test)]
795mod tests {
796    use super::*;
797    use assert_matches::assert_matches;
798    use test_case::test_case;
799
800    #[test_case("24", 24 ; "from prefix length")]
801    #[test_case("255.255.254.0", 23 ; "from ipv4 netmask")]
802    #[test_case("ffff:fff0::", 28 ; "from ipv6 netmask")]
803    fn parse_prefix_len(to_parse: &str, want: u8) {
804        let got = parse_netmask_or_prefix_length(to_parse).unwrap();
805        assert_eq!(got, want)
806    }
807
808    proptest::proptest! {
809        #[test]
810        fn cant_parse_as_both_netmask_and_prefix_len(s: String) {
811            let netmask_parse_result = s.parse::<std::net::IpAddr>();
812            let prefix_len_parse_result = s.parse::<u8>();
813            assert_matches!((netmask_parse_result, prefix_len_parse_result), (Ok(_), Err(_)) | (Err(_), Ok(_)) | (Err(_), Err(_)));
814        }
815    }
816
817    #[test_case("1", InterfaceIdentifier::Id(NonZeroU64::new(1).unwrap()) ; "as nicid")]
818    #[test_case("name:lo", InterfaceIdentifier::Name("lo".to_string()) ; "as ifname")]
819    #[test_case("name:name:lo", InterfaceIdentifier::Name("name:lo".to_string()) ; "as ifname with 'name:' as part of name")]
820    #[test_case("name:1", InterfaceIdentifier::Name("1".to_string()) ; "as numerical ifname")]
821    fn parse_interface(to_parse: &str, want: InterfaceIdentifier) {
822        let got = to_parse.parse::<InterfaceIdentifier>().unwrap();
823        assert_eq!(got, want)
824    }
825
826    #[test]
827    fn parse_interface_without_prefix_fails() {
828        assert_matches!("lo".parse::<InterfaceIdentifier>(), Err(_))
829    }
830
831    #[test]
832    fn parse_interface_zero_fails() {
833        assert_matches!("0".parse::<InterfaceIdentifier>(), Err(_))
834    }
835
836    #[test_case("0", Err("Invalid IGMP version (0). Valid values: 1, 2, 3".to_string()); "input_0")]
837    #[test_case("1", Ok(finterfaces_admin::IgmpVersion::V1); "input_1")]
838    #[test_case("2", Ok(finterfaces_admin::IgmpVersion::V2); "input_2")]
839    #[test_case("3", Ok(finterfaces_admin::IgmpVersion::V3); "input_3")]
840    #[test_case("4", Err("Invalid IGMP version (4). Valid values: 1, 2, 3".to_string()); "input_4")]
841    #[test_case("a", Err("Failed to parse IGMP version (error: invalid digit found in string)".to_string()); "input_a")]
842    fn igmp_version(s: &str, expected: Result<finterfaces_admin::IgmpVersion, String>) {
843        assert_eq!(parse_igmp_version(s), expected)
844    }
845
846    #[test_case("0", Err("Invalid MLD version (0). Valid values: 1, 2".to_string()); "input_0")]
847    #[test_case("1", Ok(finterfaces_admin::MldVersion::V1); "input_1")]
848    #[test_case("2", Ok(finterfaces_admin::MldVersion::V2); "input_2")]
849    #[test_case("3", Err("Invalid MLD version (3). Valid values: 1, 2".to_string()); "input_3")]
850    #[test_case("a", Err("Failed to parse MLD version (error: invalid digit found in string)".to_string()); "input_a")]
851    fn mld_version(s: &str, expected: Result<finterfaces_admin::MldVersion, String>) {
852        assert_eq!(parse_mld_version(s), expected)
853    }
854}