1use 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)]
45pub 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")]
69pub 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")]
88pub struct FilterGetNatRules {}
90
91#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
92#[argh(subcommand, name = "get-rdr-rules")]
93pub struct FilterGetRdrRules {}
95
96#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
97#[argh(subcommand, name = "get-rules")]
98pub struct FilterGetRules {}
100
101#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
102#[argh(subcommand, name = "set-nat-rules")]
103pub 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")]
111pub struct FilterSetRdrRules {
113 #[argh(positional)]
114 pub rules: String,
115}
116
117#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
118#[argh(subcommand, name = "set-rules")]
119pub struct FilterSetRules {
121 #[argh(positional)]
122 pub rules: String,
123}
124
125#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
126#[argh(subcommand, name = "if")]
127pub 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 >(&interfaces_state, Default::default())?;
168 let response = finterfaces_ext::existing(
169 stream,
170 HashMap::<NonZeroU64, finterfaces_ext::PropertiesAndState<(), _>>::new(),
171 )
172 .await?;
173 response
174 .values()
175 .find_map(|properties_and_state| {
176 (&properties_and_state.properties.name == name)
177 .then(|| properties_and_state.properties.id.get())
178 })
179 .ok_or_else(|| user_facing_error(format!("No interface with name {}", name)))
180 }
181 }
182 }
183
184 pub async fn find_u32_nicid<C>(&self, connector: &C) -> Result<u32, anyhow::Error>
185 where
186 C: crate::ServiceConnector<finterfaces::StateMarker>,
187 {
188 let id = self.find_nicid(connector).await?;
189 u32::try_from(id).with_context(|| format!("nicid {} does not fit in u32", id))
190 }
191}
192
193impl core::str::FromStr for InterfaceIdentifier {
194 type Err = anyhow::Error;
195
196 fn from_str(s: &str) -> Result<Self, Self::Err> {
197 let nicid_parse_result = s.parse::<u64>();
198 nicid_parse_result.map_or_else(
199 |nicid_parse_error| {
200 if !s.starts_with("name:") {
201 Err(user_facing_error(format!(
202 "Failed to parse as NICID (error: {}) or as interface name \
203 (error: interface names must be specified as `name:ifname`, where \
204 ifname is the actual interface name in this example)",
205 nicid_parse_error
206 )))
207 } else {
208 Ok(Self::Name(s["name:".len()..].to_string()))
209 }
210 },
211 |nicid| Ok(Self::Id(nicid)),
212 )
213 }
214}
215
216impl From<u64> for InterfaceIdentifier {
217 fn from(nicid: u64) -> InterfaceIdentifier {
218 InterfaceIdentifier::Id(nicid)
219 }
220}
221
222#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
223#[argh(subcommand, name = "addr")]
224pub struct IfAddr {
226 #[argh(subcommand)]
227 pub addr_cmd: IfAddrEnum,
228}
229
230#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
231#[argh(subcommand)]
232pub enum IfAddrEnum {
233 Add(IfAddrAdd),
234 Del(IfAddrDel),
235 Wait(IfAddrWait),
236}
237
238#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
239#[argh(subcommand, name = "add")]
240pub struct IfAddrAdd {
242 #[argh(positional, arg_name = "nicid or name:ifname")]
243 pub interface: InterfaceIdentifier,
244 #[argh(positional)]
245 pub addr: String,
246 #[argh(positional, from_str_fn(parse_netmask_or_prefix_length))]
247 pub prefix: u8,
248 #[argh(switch)]
249 pub no_subnet_route: bool,
251}
252
253#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
254#[argh(subcommand, name = "del")]
255pub struct IfAddrDel {
257 #[argh(positional, arg_name = "nicid or name:ifname")]
258 pub interface: InterfaceIdentifier,
259 #[argh(positional)]
260 pub addr: String,
261 #[argh(positional, from_str_fn(parse_netmask_or_prefix_length))]
262 pub prefix: Option<u8>,
263}
264
265#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
266#[argh(subcommand, name = "wait")]
267pub struct IfAddrWait {
272 #[argh(positional, arg_name = "nicid or name:ifname")]
273 pub interface: InterfaceIdentifier,
274 #[argh(switch)]
276 pub ipv6: bool,
277}
278
279#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
280#[argh(subcommand, name = "bridge")]
281pub struct IfBridge {
283 #[argh(positional, arg_name = "nicid or name:ifname")]
284 pub interfaces: Vec<InterfaceIdentifier>,
285}
286
287#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
288#[argh(subcommand, name = "disable")]
289pub struct IfDisable {
291 #[argh(positional, arg_name = "nicid or name:ifname")]
292 pub interface: InterfaceIdentifier,
293}
294
295#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
296#[argh(subcommand, name = "enable")]
297pub struct IfEnable {
299 #[argh(positional, arg_name = "nicid or name:ifname")]
300 pub interface: InterfaceIdentifier,
301}
302
303#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
304#[argh(subcommand, name = "get")]
305pub struct IfGet {
307 #[argh(positional, arg_name = "nicid or name:ifname")]
308 pub interface: InterfaceIdentifier,
309}
310
311#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
313#[argh(subcommand, name = "igmp")]
314pub struct IfIgmp {
316 #[argh(subcommand)]
317 pub cmd: IfIgmpEnum,
318}
319
320#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
322#[argh(subcommand)]
323pub enum IfIgmpEnum {
324 Get(IfIgmpGet),
325 Set(IfIgmpSet),
326}
327
328#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
330#[argh(subcommand, name = "get")]
331pub struct IfIgmpGet {
333 #[argh(positional, arg_name = "nicid or name:ifname")]
334 pub interface: InterfaceIdentifier,
335}
336
337#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
339#[argh(subcommand, name = "set")]
340pub struct IfIgmpSet {
342 #[argh(positional, arg_name = "nicid or name:ifname")]
343 pub interface: InterfaceIdentifier,
344
345 #[argh(option, from_str_fn(parse_igmp_version))]
347 pub version: Option<finterfaces_admin::IgmpVersion>,
348}
349
350fn parse_igmp_version(s: &str) -> Result<finterfaces_admin::IgmpVersion, String> {
351 match s.parse::<u8>() {
352 Err(err) => Err(format!("Failed to parse IGMP version (error: {})", err)),
353 Ok(v) => match v {
354 1 => Ok(finterfaces_admin::IgmpVersion::V1),
355 2 => Ok(finterfaces_admin::IgmpVersion::V2),
356 3 => Ok(finterfaces_admin::IgmpVersion::V3),
357 v => Err(format!("Invalid IGMP version ({}). Valid values: 1, 2, 3", v)),
358 },
359 }
360}
361
362#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
364#[argh(subcommand, name = "ip-forward")]
365pub struct IfIpForward {
367 #[argh(subcommand)]
368 pub cmd: IfIpForwardEnum,
369}
370
371#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
373#[argh(subcommand)]
374pub enum IfIpForwardEnum {
375 Get(IfIpForwardGet),
376 Set(IfIpForwardSet),
377}
378
379#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
381#[argh(subcommand, name = "get")]
382pub struct IfIpForwardGet {
384 #[argh(positional, arg_name = "nicid or name:ifname")]
385 pub interface: InterfaceIdentifier,
386
387 #[argh(positional, from_str_fn(parse_ip_version_str))]
388 pub ip_version: fnet::IpVersion,
389}
390
391#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
393#[argh(subcommand, name = "set")]
394pub struct IfIpForwardSet {
396 #[argh(positional, arg_name = "nicid or name:ifname")]
397 pub interface: InterfaceIdentifier,
398
399 #[argh(positional, from_str_fn(parse_ip_version_str))]
400 pub ip_version: fnet::IpVersion,
401
402 #[argh(positional)]
403 pub enable: bool,
404}
405
406#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
407#[argh(subcommand, name = "list")]
408pub struct IfList {
410 #[argh(positional)]
411 pub name_pattern: Option<String>,
412}
413
414#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
415#[argh(subcommand, name = "add")]
416pub struct IfAdd {
418 #[argh(subcommand)]
419 pub cmd: IfAddEnum,
420}
421
422#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
423#[argh(subcommand)]
424pub enum IfAddEnum {
425 Blackhole(IfBlackholeAdd),
426}
427
428#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
429#[argh(subcommand, name = "blackhole")]
430pub struct IfBlackholeAdd {
432 #[argh(positional)]
433 pub name: String,
434}
435
436#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
437#[argh(subcommand, name = "remove")]
438pub struct IfRemove {
440 #[argh(positional, arg_name = "nicid or name:ifname")]
441 pub interface: InterfaceIdentifier,
442}
443
444#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
446#[argh(subcommand, name = "mld")]
447pub struct IfMld {
449 #[argh(subcommand)]
450 pub cmd: IfMldEnum,
451}
452
453#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
455#[argh(subcommand)]
456pub enum IfMldEnum {
457 Get(IfMldGet),
458 Set(IfMldSet),
459}
460
461#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
463#[argh(subcommand, name = "get")]
464pub struct IfMldGet {
466 #[argh(positional, arg_name = "nicid or name:ifname")]
467 pub interface: InterfaceIdentifier,
468}
469
470#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
472#[argh(subcommand, name = "set")]
473pub struct IfMldSet {
475 #[argh(positional, arg_name = "nicid or name:ifname")]
476 pub interface: InterfaceIdentifier,
477
478 #[argh(option, from_str_fn(parse_mld_version))]
480 pub version: Option<finterfaces_admin::MldVersion>,
481}
482
483fn parse_mld_version(s: &str) -> Result<finterfaces_admin::MldVersion, String> {
484 match s.parse::<u8>() {
485 Err(err) => Err(format!("Failed to parse MLD version (error: {})", err)),
486 Ok(v) => match v {
487 1 => Ok(finterfaces_admin::MldVersion::V1),
488 2 => Ok(finterfaces_admin::MldVersion::V2),
489 v => Err(format!("Invalid MLD version ({}). Valid values: 1, 2", v)),
490 },
491 }
492}
493
494#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
495#[argh(subcommand, name = "config")]
496pub struct IfConfig {
498 #[argh(positional, arg_name = "nicid or name:ifname")]
499 pub interface: InterfaceIdentifier,
500
501 #[argh(subcommand)]
502 pub cmd: IfConfigEnum,
503}
504
505#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
506#[argh(subcommand)]
507pub enum IfConfigEnum {
509 Set(IfConfigSet),
510 Get(IfConfigGet),
511}
512
513#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
514#[argh(subcommand, name = "set")]
515pub struct IfConfigSet {
529 #[argh(positional)]
531 pub options: Vec<String>,
532}
533
534#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
535#[argh(subcommand, name = "get")]
536pub struct IfConfigGet {}
538
539#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
540#[argh(subcommand, name = "log")]
541pub struct Log {
543 #[argh(subcommand)]
544 pub log_cmd: LogEnum,
545}
546
547#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
548#[argh(subcommand)]
549pub enum LogEnum {
550 SetPackets(LogSetPackets),
551}
552
553#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
554#[argh(subcommand, name = "set-packets")]
555pub struct LogSetPackets {
557 #[argh(positional)]
558 pub enabled: bool,
559}
560
561#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
562#[argh(subcommand, name = "neigh")]
563pub struct Neigh {
565 #[argh(subcommand)]
566 pub neigh_cmd: NeighEnum,
567}
568
569#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
570#[argh(subcommand)]
571pub enum NeighEnum {
572 Add(NeighAdd),
573 Clear(NeighClear),
574 Del(NeighDel),
575 List(NeighList),
576 Watch(NeighWatch),
577 Config(NeighConfig),
578}
579
580#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
581#[argh(subcommand, name = "add")]
582pub struct NeighAdd {
584 #[argh(positional, arg_name = "nicid or name:ifname")]
585 pub interface: InterfaceIdentifier,
586 #[argh(positional)]
587 pub ip: fnet_ext::IpAddress,
588 #[argh(positional)]
589 pub mac: fnet_ext::MacAddress,
590}
591
592#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
593#[argh(subcommand, name = "clear")]
594pub struct NeighClear {
596 #[argh(positional, arg_name = "nicid or name:ifname")]
597 pub interface: InterfaceIdentifier,
598
599 #[argh(positional, from_str_fn(parse_ip_version_str))]
600 pub ip_version: fnet::IpVersion,
601}
602
603#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
604#[argh(subcommand, name = "list")]
605pub struct NeighList {}
607
608#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
609#[argh(subcommand, name = "del")]
610pub struct NeighDel {
612 #[argh(positional, arg_name = "nicid or name:ifname")]
613 pub interface: InterfaceIdentifier,
614 #[argh(positional)]
615 pub ip: fnet_ext::IpAddress,
616}
617
618#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
619#[argh(subcommand, name = "watch")]
620pub struct NeighWatch {}
622
623#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
624#[argh(subcommand, name = "config")]
625pub struct NeighConfig {
627 #[argh(subcommand)]
628 pub neigh_config_cmd: NeighConfigEnum,
629}
630
631#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
632#[argh(subcommand)]
633pub enum NeighConfigEnum {
634 Get(NeighGetConfig),
635 Update(NeighUpdateConfig),
636}
637
638#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
639#[argh(subcommand, name = "get")]
640pub struct NeighGetConfig {
642 #[argh(positional, arg_name = "nicid or name:ifname")]
643 pub interface: InterfaceIdentifier,
644
645 #[argh(positional, from_str_fn(parse_ip_version_str))]
646 pub ip_version: fnet::IpVersion,
647}
648
649#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
650#[argh(subcommand, name = "update")]
651pub struct NeighUpdateConfig {
653 #[argh(positional, arg_name = "nicid or name:ifname")]
654 pub interface: InterfaceIdentifier,
655
656 #[argh(positional, from_str_fn(parse_ip_version_str))]
657 pub ip_version: fnet::IpVersion,
658
659 #[argh(option)]
662 pub base_reachable_time: Option<i64>,
663}
664
665#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
666#[argh(subcommand, name = "route")]
667pub struct Route {
669 #[argh(subcommand)]
670 pub route_cmd: RouteEnum,
671}
672
673#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
674#[argh(subcommand)]
675pub enum RouteEnum {
676 List(RouteList),
677 Add(RouteAdd),
678 Del(RouteDel),
679}
680
681fn parse_netmask_or_prefix_length(s: &str) -> Result<u8, String> {
682 let netmask_parse_result = s.parse::<std::net::IpAddr>();
683 let prefix_len_parse_result = s.parse::<u8>();
684 match (netmask_parse_result, prefix_len_parse_result) {
685 (Err(netmask_parse_error), Err(prefix_len_parse_error)) => Err(format!(
686 "Failed to parse as netmask (error: {}) or prefix length (error: {})",
687 netmask_parse_error, prefix_len_parse_error,
688 )),
689 (Ok(_), Ok(_)) => Err(format!(
690 "Input parses both as netmask and as prefix length. This should never happen."
691 )),
692 (Ok(netmask), Err(_)) => Ok(subnet_mask_to_prefix_length(netmask)),
693 (Err(_), Ok(prefix_len)) => Ok(prefix_len),
694 }
695}
696
697fn subnet_mask_to_prefix_length(addr: std::net::IpAddr) -> u8 {
698 match addr {
699 std::net::IpAddr::V4(addr) => (!u32::from_be_bytes(addr.octets())).leading_zeros(),
700 std::net::IpAddr::V6(addr) => (!u128::from_be_bytes(addr.octets())).leading_zeros(),
701 }
702 .try_into()
703 .unwrap()
704}
705
706#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
707#[argh(subcommand, name = "list")]
708pub struct RouteList {}
710
711macro_rules! route_struct {
712 ($ty_name:ident, $name:literal, $comment:expr) => {
713 #[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
714 #[argh(subcommand, name = $name)]
715 #[doc = $comment]
716 pub struct $ty_name {
717 #[argh(option)]
718 pub destination: std::net::IpAddr,
720 #[argh(
721 option,
722 arg_name = "netmask or prefix length",
723 from_str_fn(parse_netmask_or_prefix_length),
724 long = "netmask"
725 )]
726 pub prefix_len: u8,
728 #[argh(option)]
729 pub gateway: Option<std::net::IpAddr>,
731 #[argh(
732 option,
733 arg_name = "nicid or name:ifname",
734 default = "InterfaceIdentifier::Id(0)",
735 long = "nicid"
736 )]
737 pub interface: InterfaceIdentifier,
739 #[argh(option, default = "0")]
740 pub metric: u32,
742 }
743
744 impl $ty_name {
745 pub fn into_route_table_entry(self, nicid: u32) -> fnet_stack::ForwardingEntry {
746 let Self { destination, prefix_len, gateway, interface: _, metric } = self;
747 fnet_stack::ForwardingEntry {
748 subnet: fnet_ext::apply_subnet_mask(fnet::Subnet {
749 addr: fnet_ext::IpAddress(destination).into(),
750 prefix_len,
751 }),
752 device_id: nicid.into(),
753 next_hop: gateway.map(|gateway| Box::new(fnet_ext::IpAddress(gateway).into())),
754 metric,
755 }
756 }
757 }
758 };
759}
760
761route_struct!(RouteAdd, "add", "adds a route to the route table");
763route_struct!(RouteDel, "del", "deletes a route from the route table");
764
765#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
766#[argh(subcommand, name = "rule")]
767pub struct Rule {
769 #[argh(subcommand)]
770 pub rule_cmd: RuleEnum,
771}
772
773#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
774#[argh(subcommand)]
775pub enum RuleEnum {
776 List(RuleList),
777}
778
779#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
780#[argh(subcommand, name = "list")]
781pub struct RuleList {}
783
784#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
785#[argh(subcommand, name = "dhcp")]
786pub struct Dhcp {
788 #[argh(subcommand)]
789 pub dhcp_cmd: DhcpEnum,
790}
791
792#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
793#[argh(subcommand)]
794pub enum DhcpEnum {
795 Start(DhcpStart),
796 Stop(DhcpStop),
797}
798
799#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
800#[argh(subcommand, name = "start")]
801pub struct DhcpStart {
803 #[argh(positional, arg_name = "nicid or name:ifname")]
804 pub interface: InterfaceIdentifier,
805}
806
807#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
808#[argh(subcommand, name = "stop")]
809pub struct DhcpStop {
811 #[argh(positional, arg_name = "nicid or name:ifname")]
812 pub interface: InterfaceIdentifier,
813}
814
815#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
816#[argh(subcommand, name = "migration")]
817pub struct NetstackMigration {
819 #[argh(subcommand)]
820 pub cmd: NetstackMigrationEnum,
821}
822
823#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
824#[argh(subcommand)]
825pub enum NetstackMigrationEnum {
826 Set(NetstackMigrationSet),
827 Get(NetstackMigrationGet),
828 Clear(NetstackMigrationClear),
829}
830
831fn parse_netstack_version(s: &str) -> Result<fnet_migration::NetstackVersion, String> {
832 match s {
833 "ns2" => Ok(fnet_migration::NetstackVersion::Netstack2),
834 "ns3" => Ok(fnet_migration::NetstackVersion::Netstack3),
835 _ => Err(format!("valid values are ns2 or ns3")),
838 }
839}
840
841#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
842#[argh(subcommand, name = "set")]
843pub struct NetstackMigrationSet {
845 #[argh(positional, from_str_fn(parse_netstack_version))]
846 pub version: fnet_migration::NetstackVersion,
848}
849
850#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
851#[argh(subcommand, name = "get")]
852pub struct NetstackMigrationGet {}
854
855#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
856#[argh(subcommand, name = "clear")]
857pub struct NetstackMigrationClear {}
859
860#[cfg(test)]
861mod tests {
862 use super::*;
863 use assert_matches::assert_matches;
864 use test_case::test_case;
865
866 #[test_case("24", 24 ; "from prefix length")]
867 #[test_case("255.255.254.0", 23 ; "from ipv4 netmask")]
868 #[test_case("ffff:fff0::", 28 ; "from ipv6 netmask")]
869 fn parse_prefix_len(to_parse: &str, want: u8) {
870 let got = parse_netmask_or_prefix_length(to_parse).unwrap();
871 assert_eq!(got, want)
872 }
873
874 proptest::proptest! {
875 #[test]
876 fn cant_parse_as_both_netmask_and_prefix_len(s: String) {
877 let netmask_parse_result = s.parse::<std::net::IpAddr>();
878 let prefix_len_parse_result = s.parse::<u8>();
879 assert_matches!((netmask_parse_result, prefix_len_parse_result), (Ok(_), Err(_)) | (Err(_), Ok(_)) | (Err(_), Err(_)));
880 }
881 }
882
883 #[test_case("1", InterfaceIdentifier::Id(1) ; "as nicid")]
884 #[test_case("name:lo", InterfaceIdentifier::Name("lo".to_string()) ; "as ifname")]
885 #[test_case("name:name:lo", InterfaceIdentifier::Name("name:lo".to_string()) ; "as ifname with 'name:' as part of name")]
886 #[test_case("name:1", InterfaceIdentifier::Name("1".to_string()) ; "as numerical ifname")]
887 fn parse_interface(to_parse: &str, want: InterfaceIdentifier) {
888 let got = to_parse.parse::<InterfaceIdentifier>().unwrap();
889 assert_eq!(got, want)
890 }
891
892 #[test]
893 fn parse_interface_without_prefix_fails() {
894 assert_matches!("lo".parse::<InterfaceIdentifier>(), Err(_))
895 }
896
897 #[test]
898 fn into_route_table_entry_applies_subnet_mask() {
899 const PREFIX_LEN: u8 = 128 - 32;
901 const ORIGINAL_NICID: u32 = 1;
902 const NICID_TO_OVERWRITE_WITH: u32 = 2;
903 assert_eq!(
904 RouteAdd {
905 destination: std::net::IpAddr::V6(std::net::Ipv6Addr::new(
906 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
907 )),
908 prefix_len: PREFIX_LEN,
909 gateway: None,
910 interface: u64::from(ORIGINAL_NICID).into(),
911 metric: 100,
912 }
913 .into_route_table_entry(NICID_TO_OVERWRITE_WITH),
914 fnet_stack::ForwardingEntry {
915 subnet: fnet::Subnet {
916 addr: net_declare::fidl_ip!("ffff:ffff:ffff:ffff:ffff:ffff:0:0"),
917 prefix_len: PREFIX_LEN,
918 },
919 device_id: NICID_TO_OVERWRITE_WITH.into(),
922 next_hop: None,
923 metric: 100,
924 }
925 )
926 }
927
928 #[test_case("0", Err("Invalid IGMP version (0). Valid values: 1, 2, 3".to_string()); "input_0")]
929 #[test_case("1", Ok(finterfaces_admin::IgmpVersion::V1); "input_1")]
930 #[test_case("2", Ok(finterfaces_admin::IgmpVersion::V2); "input_2")]
931 #[test_case("3", Ok(finterfaces_admin::IgmpVersion::V3); "input_3")]
932 #[test_case("4", Err("Invalid IGMP version (4). Valid values: 1, 2, 3".to_string()); "input_4")]
933 #[test_case("a", Err("Failed to parse IGMP version (error: invalid digit found in string)".to_string()); "input_a")]
934 fn igmp_version(s: &str, expected: Result<finterfaces_admin::IgmpVersion, String>) {
935 assert_eq!(parse_igmp_version(s), expected)
936 }
937
938 #[test_case("0", Err("Invalid MLD version (0). Valid values: 1, 2".to_string()); "input_0")]
939 #[test_case("1", Ok(finterfaces_admin::MldVersion::V1); "input_1")]
940 #[test_case("2", Ok(finterfaces_admin::MldVersion::V2); "input_2")]
941 #[test_case("3", Err("Invalid MLD version (3). Valid values: 1, 2".to_string()); "input_3")]
942 #[test_case("a", Err("Failed to parse MLD version (error: invalid digit found in string)".to_string()); "input_a")]
943 fn mld_version(s: &str, expected: Result<finterfaces_admin::MldVersion, String>) {
944 assert_eq!(parse_mld_version(s), expected)
945 }
946}