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