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 >(
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")]
226pub 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")]
242pub 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 pub no_subnet_route: bool,
253}
254
255#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
256#[argh(subcommand, name = "del")]
257pub 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")]
269pub struct IfAddrWait {
274 #[argh(positional, arg_name = "nicid or name:ifname")]
275 pub interface: InterfaceIdentifier,
276 #[argh(switch)]
278 pub ipv6: bool,
279}
280
281#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
282#[argh(subcommand, name = "bridge")]
283pub 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")]
291pub 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")]
299pub 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")]
307pub struct IfGet {
309 #[argh(positional, arg_name = "nicid or name:ifname")]
310 pub interface: InterfaceIdentifier,
311}
312
313#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
315#[argh(subcommand, name = "igmp")]
316pub struct IfIgmp {
318 #[argh(subcommand)]
319 pub cmd: IfIgmpEnum,
320}
321
322#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
324#[argh(subcommand)]
325pub enum IfIgmpEnum {
326 Get(IfIgmpGet),
327 Set(IfIgmpSet),
328}
329
330#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
332#[argh(subcommand, name = "get")]
333pub struct IfIgmpGet {
335 #[argh(positional, arg_name = "nicid or name:ifname")]
336 pub interface: InterfaceIdentifier,
337}
338
339#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
341#[argh(subcommand, name = "set")]
342pub struct IfIgmpSet {
344 #[argh(positional, arg_name = "nicid or name:ifname")]
345 pub interface: InterfaceIdentifier,
346
347 #[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#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
366#[argh(subcommand, name = "ip-forward")]
367pub struct IfIpForward {
369 #[argh(subcommand)]
370 pub cmd: IfIpForwardEnum,
371}
372
373#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
375#[argh(subcommand)]
376pub enum IfIpForwardEnum {
377 Get(IfIpForwardGet),
378 Set(IfIpForwardSet),
379}
380
381#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
383#[argh(subcommand, name = "get")]
384pub 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#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
395#[argh(subcommand, name = "set")]
396pub 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")]
410pub 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")]
418pub 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")]
432pub struct IfBlackholeAdd {
434 #[argh(positional)]
435 pub name: String,
436}
437
438#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
439#[argh(subcommand, name = "remove")]
440pub struct IfRemove {
442 #[argh(positional, arg_name = "nicid or name:ifname")]
443 pub interface: InterfaceIdentifier,
444}
445
446#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
448#[argh(subcommand, name = "mld")]
449pub struct IfMld {
451 #[argh(subcommand)]
452 pub cmd: IfMldEnum,
453}
454
455#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
457#[argh(subcommand)]
458pub enum IfMldEnum {
459 Get(IfMldGet),
460 Set(IfMldSet),
461}
462
463#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
465#[argh(subcommand, name = "get")]
466pub struct IfMldGet {
468 #[argh(positional, arg_name = "nicid or name:ifname")]
469 pub interface: InterfaceIdentifier,
470}
471
472#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
474#[argh(subcommand, name = "set")]
475pub struct IfMldSet {
477 #[argh(positional, arg_name = "nicid or name:ifname")]
478 pub interface: InterfaceIdentifier,
479
480 #[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")]
498pub 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)]
509pub enum IfConfigEnum {
511 Set(IfConfigSet),
512 Get(IfConfigGet),
513}
514
515#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
516#[argh(subcommand, name = "set")]
517pub struct IfConfigSet {
531 #[argh(positional)]
533 pub options: Vec<String>,
534}
535
536#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
537#[argh(subcommand, name = "get")]
538pub struct IfConfigGet {}
540
541#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
542#[argh(subcommand, name = "log")]
543pub 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")]
557pub struct LogSetPackets {
559 #[argh(positional)]
560 pub enabled: bool,
561}
562
563#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
564#[argh(subcommand, name = "neigh")]
565pub 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")]
584pub 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")]
596pub 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")]
607pub struct NeighList {}
609
610#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
611#[argh(subcommand, name = "del")]
612pub 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")]
622pub struct NeighWatch {}
624
625#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
626#[argh(subcommand, name = "config")]
627pub 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")]
642pub 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")]
653pub 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 #[argh(option)]
664 pub base_reachable_time: Option<i64>,
665}
666
667#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
668#[argh(subcommand, name = "route")]
669pub 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")]
710pub 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 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 pub prefix_len: u8,
730 #[argh(option)]
731 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 pub interface: InterfaceIdentifier,
741 #[argh(option, default = "0")]
742 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
763route_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")]
769pub 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")]
783pub struct RuleList {}
785
786#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
787#[argh(subcommand, name = "dhcp")]
788pub 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")]
803pub 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")]
811pub 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")]
819pub 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 _ => Err(format!("valid values are ns2 or ns3")),
840 }
841}
842
843#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
844#[argh(subcommand, name = "set")]
845pub struct NetstackMigrationSet {
847 #[argh(positional, from_str_fn(parse_netstack_version))]
848 pub version: fnet_migration::NetstackVersion,
850}
851
852#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
853#[argh(subcommand, name = "get")]
854pub struct NetstackMigrationGet {}
856
857#[derive(ArgsInfo, FromArgs, Clone, Debug, PartialEq)]
858#[argh(subcommand, name = "clear")]
859pub 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 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 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}