1use 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)]
46pub 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")]
70pub 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")]
89pub struct FilterGetNatRules {}
91
92#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
93#[argh(subcommand, name = "get-rdr-rules")]
94pub struct FilterGetRdrRules {}
96
97#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
98#[argh(subcommand, name = "get-rules")]
99pub struct FilterGetRules {}
101
102#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
103#[argh(subcommand, name = "set-nat-rules")]
104pub 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")]
112pub struct FilterSetRdrRules {
114 #[argh(positional)]
115 pub rules: String,
116}
117
118#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
119#[argh(subcommand, name = "set-rules")]
120pub struct FilterSetRules {
122 #[argh(positional)]
123 pub rules: String,
124}
125
126#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
127#[argh(subcommand, name = "if")]
128pub 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")]
217pub 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")]
233pub 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 pub no_subnet_route: bool,
244}
245
246#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
247#[argh(subcommand, name = "del")]
248pub 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")]
260pub struct IfAddrWait {
265 #[argh(positional, arg_name = "nicid or name:ifname")]
266 pub interface: InterfaceIdentifier,
267 #[argh(switch)]
269 pub ipv6: bool,
270}
271
272#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
273#[argh(subcommand, name = "bridge")]
274pub 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")]
282pub 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")]
290pub 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")]
298pub struct IfGet {
300 #[argh(positional, arg_name = "nicid or name:ifname")]
301 pub interface: InterfaceIdentifier,
302}
303
304#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
306#[argh(subcommand, name = "igmp")]
307pub struct IfIgmp {
309 #[argh(subcommand)]
310 pub cmd: IfIgmpEnum,
311}
312
313#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
315#[argh(subcommand)]
316pub enum IfIgmpEnum {
317 Get(IfIgmpGet),
318 Set(IfIgmpSet),
319}
320
321#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
323#[argh(subcommand, name = "get")]
324pub struct IfIgmpGet {
326 #[argh(positional, arg_name = "nicid or name:ifname")]
327 pub interface: InterfaceIdentifier,
328}
329
330#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
332#[argh(subcommand, name = "set")]
333pub struct IfIgmpSet {
335 #[argh(positional, arg_name = "nicid or name:ifname")]
336 pub interface: InterfaceIdentifier,
337
338 #[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#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
357#[argh(subcommand, name = "ip-forward")]
358pub struct IfIpForward {
360 #[argh(subcommand)]
361 pub cmd: IfIpForwardEnum,
362}
363
364#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
366#[argh(subcommand)]
367pub enum IfIpForwardEnum {
368 Get(IfIpForwardGet),
369 Set(IfIpForwardSet),
370}
371
372#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
374#[argh(subcommand, name = "get")]
375pub 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#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
386#[argh(subcommand, name = "set")]
387pub 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")]
401pub 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")]
409pub 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")]
423pub struct IfBlackholeAdd {
425 #[argh(positional)]
426 pub name: String,
427}
428
429#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
430#[argh(subcommand, name = "remove")]
431pub struct IfRemove {
433 #[argh(positional, arg_name = "nicid or name:ifname")]
434 pub interface: InterfaceIdentifier,
435}
436
437#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
439#[argh(subcommand, name = "mld")]
440pub struct IfMld {
442 #[argh(subcommand)]
443 pub cmd: IfMldEnum,
444}
445
446#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
448#[argh(subcommand)]
449pub enum IfMldEnum {
450 Get(IfMldGet),
451 Set(IfMldSet),
452}
453
454#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
456#[argh(subcommand, name = "get")]
457pub struct IfMldGet {
459 #[argh(positional, arg_name = "nicid or name:ifname")]
460 pub interface: InterfaceIdentifier,
461}
462
463#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
465#[argh(subcommand, name = "set")]
466pub struct IfMldSet {
468 #[argh(positional, arg_name = "nicid or name:ifname")]
469 pub interface: InterfaceIdentifier,
470
471 #[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")]
489pub 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)]
500pub enum IfConfigEnum {
502 Set(IfConfigSet),
503 Get(IfConfigGet),
504}
505
506#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
507#[argh(subcommand, name = "set")]
508pub struct IfConfigSet {
528 #[argh(positional)]
530 pub options: Vec<String>,
531}
532
533#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
534#[argh(subcommand, name = "get")]
535pub struct IfConfigGet {}
537
538#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
539#[argh(subcommand, name = "log")]
540pub 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")]
554pub struct LogSetPackets {
556 #[argh(positional)]
557 pub enabled: bool,
558}
559
560#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
561#[argh(subcommand, name = "neigh")]
562pub 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")]
581pub 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")]
593pub 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")]
604pub struct NeighList {}
606
607#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
608#[argh(subcommand, name = "del")]
609pub 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")]
619pub struct NeighWatch {}
621
622#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
623#[argh(subcommand, name = "config")]
624pub 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")]
639pub 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")]
650pub 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 #[argh(option)]
661 pub base_reachable_time: Option<i64>,
662}
663
664#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
665#[argh(subcommand, name = "route")]
666pub 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")]
707pub 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 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 pub prefix_len: u8,
727 #[argh(option)]
728 pub gateway: Option<std::net::IpAddr>,
730 #[argh(option, arg_name = "nicid or name:ifname", long = "nicid")]
731 pub interface: InterfaceIdentifier,
733 #[argh(option)]
734 pub metric: Option<u32>,
736 }
737 };
738}
739
740route_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")]
746pub 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")]
760pub struct RuleList {}
762
763#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
764#[argh(subcommand, name = "dhcp")]
765pub 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")]
780pub 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")]
788pub 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}