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 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)]
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 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")]
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 {
522 #[argh(positional)]
524 pub options: Vec<String>,
525}
526
527#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
528#[argh(subcommand, name = "get")]
529pub struct IfConfigGet {}
531
532#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
533#[argh(subcommand, name = "log")]
534pub 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")]
548pub struct LogSetPackets {
550 #[argh(positional)]
551 pub enabled: bool,
552}
553
554#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
555#[argh(subcommand, name = "neigh")]
556pub 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")]
575pub 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")]
587pub 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")]
598pub struct NeighList {}
600
601#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
602#[argh(subcommand, name = "del")]
603pub 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")]
613pub struct NeighWatch {}
615
616#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
617#[argh(subcommand, name = "config")]
618pub 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")]
633pub 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")]
644pub 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 #[argh(option)]
655 pub base_reachable_time: Option<i64>,
656}
657
658#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
659#[argh(subcommand, name = "route")]
660pub 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")]
701pub 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 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 pub prefix_len: u8,
721 #[argh(option)]
722 pub gateway: Option<std::net::IpAddr>,
724 #[argh(option, arg_name = "nicid or name:ifname", long = "nicid")]
725 pub interface: InterfaceIdentifier,
727 #[argh(option)]
728 pub metric: Option<u32>,
730 }
731 };
732}
733
734route_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")]
740pub 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")]
754pub struct RuleList {}
756
757#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
758#[argh(subcommand, name = "dhcp")]
759pub 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")]
774pub 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")]
782pub 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")]
790pub 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 _ => Err(format!("valid values are ns2 or ns3")),
811 }
812}
813
814#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
815#[argh(subcommand, name = "set")]
816pub struct NetstackMigrationSet {
818 #[argh(positional, from_str_fn(parse_netstack_version))]
819 pub version: fnet_migration::NetstackVersion,
821}
822
823#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
824#[argh(subcommand, name = "get")]
825pub struct NetstackMigrationGet {}
827
828#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
829#[argh(subcommand, name = "clear")]
830pub 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}