| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! This modules contains the definition of the Network Manager CLI |
| //! |
| //! This module defines all the commands and subcommands that are implemented |
| //! for the CLI. This is done using the `structopt` crate. |
| use anyhow::{format_err, Error}; |
| use eui48::MacAddress; |
| use fidl_fuchsia_router_config::{CidrAddress, Id}; |
| use std::convert::TryInto; |
| use std::net::IpAddr; |
| use std::net::Ipv4Addr; |
| use std::str::FromStr; |
| pub use structopt::StructOpt; |
| |
| // Custom parser used for creating interface identifiers |
| fn construct_id(src: &str) -> Result<Id, Error> { |
| let mut id = Id { uuid: [0; 16], version: 0 }; |
| id.uuid[0] = match src.parse() { |
| Ok(n) => n, |
| Err(_err) => return Err(format_err!("Invalid ID format")), |
| }; |
| // id.uuid[0] = src.parse().unwrap(); |
| Ok(id) |
| } |
| |
| // This struct is used to hold IP addresses in CIDR format |
| #[derive(Debug, Clone, PartialEq)] |
| pub struct Ipv4AddrPrefix { |
| pub address: std::net::Ipv4Addr, |
| pub prefix: u8, |
| } |
| |
| // This trait is used by structopt to parse IP addresses in CIDR notation |
| impl FromStr for Ipv4AddrPrefix { |
| type Err = Error; |
| // Custom parser used for IPv4 in CIDR format |
| fn from_str(src: &str) -> Result<Self, Self::Err> { |
| let ipv4_string_vector: Vec<&str> = src.split('/').collect(); |
| if ipv4_string_vector.len() != 2 { |
| return Err(format_err!("Invalid IP format. Please use CIDR notation")); |
| } |
| let prefix = match ipv4_string_vector[1].parse() { |
| Ok(n) => n, |
| Err(_err) => return Err(format_err!("Cannot parse IP prefix")), |
| }; |
| if prefix > 32 { |
| return Err(format_err!("Prefix cannot be greater than 32")); |
| } |
| let ipv4_addr = match ipv4_string_vector[0].parse() { |
| Ok(n) => n, |
| Err(_err) => return Err(format_err!("Error parsing IP address")), |
| }; |
| let ipv4_addr_prefix = Ipv4AddrPrefix { address: ipv4_addr, prefix }; |
| Ok(ipv4_addr_prefix) |
| } |
| } |
| |
| /// Converts an `Ipv4AddrPrefix` to a `router_config::CidrAddress`. |
| impl TryInto<CidrAddress> for Ipv4AddrPrefix { |
| type Error = anyhow::Error; |
| fn try_into(self) -> Result<CidrAddress, anyhow::Error> { |
| let (ipv4_address, prefix_length) = |
| (fidl_fuchsia_net::Ipv4Address { addr: self.address.octets() }, self.prefix); |
| Ok(CidrAddress { |
| address: Some(fidl_fuchsia_net::IpAddress::Ipv4(ipv4_address)), |
| prefix_length: Some(prefix_length), |
| }) |
| } |
| } |
| |
| #[derive(StructOpt, Debug)] |
| pub struct Opt { |
| #[structopt(long, short)] |
| pub overnet: bool, |
| #[structopt(subcommand)] |
| pub cmd: Command, |
| } |
| |
| #[derive(StructOpt, Debug)] |
| #[structopt( |
| name = "Network Manager CLI", |
| about = "This CLI is used to invoke the FIDL interface for the network manager app", |
| version = "1.0" |
| )] |
| pub enum Command { |
| #[structopt(name = "add")] |
| /// Create a new LAN or WAN interface |
| ADD(Add), |
| #[structopt(name = "remove")] |
| /// Remove a LAN or WAN interface |
| REMOVE(Remove), |
| #[structopt(name = "show")] |
| /// Read device configuration |
| SHOW(Show), |
| #[structopt(name = "set")] |
| /// Write device configuration |
| SET(Set), |
| } |
| |
| #[derive(StructOpt, Clone, Debug, PartialEq)] |
| pub enum SecurityFeature { |
| #[structopt(name = "nat")] |
| NAT, |
| } |
| impl FromStr for SecurityFeature { |
| type Err = Error; |
| fn from_str(feature: &str) -> Result<Self, Self::Err> { |
| match feature.to_lowercase().as_str() { |
| "nat" => Ok(SecurityFeature::NAT), |
| _ => Err(format_err!("Invalid security feature: '{}'", feature)), |
| } |
| } |
| } |
| |
| #[derive(StructOpt, Clone, Debug)] |
| pub enum Add { |
| #[structopt(name = "wan")] |
| /// Add a WAN interface |
| Wan { |
| #[structopt(raw(required = "true"))] |
| name: String, |
| #[structopt(short, long, raw(required = "true"))] |
| ports: Vec<u32>, |
| #[structopt(short, long)] |
| vlan: Option<u16>, |
| }, |
| #[structopt(name = "lan")] |
| /// Add a LAN interface |
| Lan { |
| #[structopt(raw(required = "true"))] |
| name: String, |
| #[structopt(short, long, raw(required = "true"))] |
| ports: Vec<u32>, |
| #[structopt(short, long)] |
| vlan: Option<u16>, |
| }, |
| } |
| |
| #[derive(StructOpt, Clone, Debug)] |
| pub enum Remove { |
| #[structopt(name = "wan")] |
| /// Remove a WAN interface |
| Wan { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target WAN interface |
| wan_id: Id, |
| }, |
| #[structopt(name = "lan")] |
| /// Remove a LAN interface |
| Lan { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target LAN interface |
| lan_id: Id, |
| }, |
| } |
| |
| #[derive(StructOpt, Clone, Debug)] |
| pub enum Show { |
| #[structopt(name = "wan")] |
| /// Show a WAN interface |
| Wan { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| wan_id: Id, |
| }, |
| #[structopt(name = "lan")] |
| /// Show a LAN interface |
| Lan { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| lan_id: Id, |
| }, |
| #[structopt(name = "wanconfig")] |
| /// Show configuration for a WAN interface |
| WanConfig { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| wan_id: Id, |
| }, |
| #[structopt(name = "lanconfig")] |
| /// Show configuration for a LAN interface |
| LanConfig { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| lan_id: Id, |
| }, |
| #[structopt(name = "wans")] |
| /// List all WAN interfaces |
| Wans {}, |
| #[structopt(name = "lans")] |
| /// List all LAN interfaces |
| Lans {}, |
| #[structopt(name = "wanports")] |
| /// List all ports for a WAN interface |
| WanPorts { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| wan_id: Id, |
| }, |
| |
| #[structopt(name = "filterstate")] |
| /// Show active Packet Filter rules |
| FilterState {}, |
| |
| #[structopt(name = "lanports")] |
| /// List all ports for a LAN interface |
| LanPorts { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| lan_id: Id, |
| }, |
| #[structopt(name = "ports")] |
| /// List all the ports |
| Ports {}, |
| #[structopt(name = "routes")] |
| /// List all the routes |
| Routes {}, |
| #[structopt(name = "security-config")] |
| /// Shows the security configuration. |
| Security {}, |
| #[structopt(name = "port")] |
| /// Show a port |
| Port { |
| #[structopt(raw(required = "true"))] |
| port: u32, |
| }, |
| #[structopt(name = "dnsconfig")] |
| /// Show DNS configuration |
| DnsConfig {}, |
| #[structopt(name = "dhcpconfig")] |
| /// Show DHCP configuration |
| DhcpConfig { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target LAN interface |
| lan_id: Id, |
| }, |
| #[structopt(name = "forwardstate")] |
| /// Show forward state for a LAN interface |
| ForwardState { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target WAN interface |
| lan_id: Id, |
| }, |
| } |
| |
| #[derive(StructOpt, Clone, Debug)] |
| pub enum Set { |
| #[structopt(name = "wan-state")] |
| /// Enable/Disable a WAN interface |
| WanState { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target WAN interface |
| wan_id: Id, |
| #[structopt(raw(required = "true"))] |
| /// Use "up" or "down" keywords to change the specified WAN's state |
| state: String, |
| }, |
| |
| #[structopt(name = "wan-connection")] |
| /// Configure the connection type for a WAN interface: {pppoe, pptp, l2tp}, set mtu or metric |
| WanConnection { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target WAN interface |
| wan_id: Id, |
| /// Use "direct", "pppoe", "pptp" and "l2tp" keywords to set the connection type. Pay attention to required args that follow for each connection type |
| connection: Option<String>, |
| /// Required for "pppoe", "pptp" and "l2tp" connection types |
| username: Option<String>, |
| /// Required for "pppoe", "pptp" and "l2tp" connection types |
| password: Option<String>, |
| /// Required for "pptp" and "l2tp" connection types. Format: X.X.X.X where 0 < X < 255 |
| server: Option<Ipv4Addr>, |
| #[structopt(short, long)] |
| /// Optional flag for setting metric |
| metric: Option<u32>, |
| #[structopt(short = "t", long)] |
| /// Optional flag for setting mtu |
| mtu: Option<u32>, |
| }, |
| |
| #[structopt(name = "wan-ip")] |
| /// Set IP settings to manual or DHCP for a WAN interface or set an optional hostname |
| WanIp { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target WAN interface |
| wan_id: Id, |
| /// The IP setting can be set to either "dhcp" or "manual". "manual" should be followed by ipv4 and gateway. |
| mode: Option<String>, |
| // TODO: Use a data struct for ipv4 with cidr format |
| /// IPv4 address for manual address method. Format: X.X.X.X/X where 0 < X < 255 |
| ipv4: Option<Ipv4AddrPrefix>, |
| /// Gateway address for manual address method. Format: X.X.X.X where 0 < X < 255 |
| gateway: Option<Ipv4Addr>, |
| #[structopt(short = "n", long)] |
| /// Optional flag for setting a hostname |
| hostname: Option<String>, |
| }, |
| |
| #[structopt(name = "wan-mac")] |
| /// Provice a MAC address for a WAN interface |
| WanCloneMac { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target WAN interface |
| wan_id: Id, |
| #[structopt(raw(required = "true"))] |
| /// MAC address. Format: X:X:X:X:X:X where 00 < X < FF (Hexadecimal) |
| mac: MacAddress, |
| }, |
| |
| #[structopt(name = "lan-state")] |
| /// Enable/Disable a LAN interface |
| LanState { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target LAN interface |
| lan_id: Id, |
| #[structopt(raw(required = "true"))] |
| /// Use "up" or "down" keywords to change the specified LAN's state |
| state: String, |
| }, |
| |
| #[structopt(name = "lan-ip")] |
| /// Set a manual IPv4 address for a LAN interface |
| LanIp { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target LAN interface |
| lan_id: Id, |
| /// Manual IPv4 address. Format: X.X.X.X/X where 0 < X < 255 |
| ipv4: Option<Ipv4AddrPrefix>, |
| // ipv4: Option<[u8; 5]>, |
| }, |
| |
| #[structopt(name = "lan-dhcp")] |
| /// Configure a LAN interface |
| LanDhcp { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| /// ID of the target LAN interface |
| lan_id: Id, |
| #[structopt(raw(required = "true"))] |
| /// Use "up" or "down" keywords to change the DHCP server state |
| state: String, |
| #[structopt(short, long = "lease-time")] |
| /// Lease time in seconds |
| lease_time_sec: Option<u32>, |
| #[structopt(short, long = "gateway")] |
| /// Deafault gateway that is advertised |
| gateway: Option<Ipv4Addr>, |
| }, |
| |
| #[structopt(name = "dhcp-config")] |
| /// Configure DHCP settings for a LAN interface |
| DhcpConfig { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| lan_id: Id, |
| dhcp_config: String, |
| }, |
| |
| #[structopt(name = "dns-config")] |
| /// Configure DNS settings |
| DnsConfig { |
| #[structopt(raw(required = "true"))] |
| server: IpAddr, |
| }, |
| |
| #[structopt(name = "dns-forwarder")] |
| /// Configure DNS forwarder for a LAN interface |
| DnsForwarder { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| lan_id: Id, |
| enabled: bool, |
| }, |
| |
| #[structopt(name = "route")] |
| /// Create a route |
| Route { |
| #[structopt(raw(required = "true"))] |
| route: String, |
| }, |
| |
| #[structopt(name = "security-config")] |
| /// Set security configuration |
| SecurityConfig { |
| #[structopt(raw(required = "true"))] |
| feature: SecurityFeature, |
| enabled: bool, |
| }, |
| |
| #[structopt(name = "port-forward")] |
| /// Configure port forwarding rules |
| PortForward { |
| #[structopt(raw(required = "true"))] |
| rule: String, |
| }, |
| |
| #[structopt(name = "filter")] |
| /// Add a new packet filter rule. |
| Filter { |
| action: String, |
| src_address: Option<Ipv4AddrPrefix>, |
| src_port_range: Option<String>, |
| dst_address: Option<Ipv4AddrPrefix>, |
| dst_port_range: Option<String>, |
| protocol: Option<String>, |
| }, |
| |
| #[structopt(name = "delete-filter")] |
| /// Clears all packet filter rules. |
| DeleteFilter { |
| #[structopt(parse(try_from_str = "construct_id"), raw(required = "true"))] |
| rule_id: Id, |
| }, |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::opts::Ipv4AddrPrefix; |
| use fidl_fuchsia_net::Ipv4Address; |
| use std::convert::TryInto; |
| use std::net::Ipv4Addr; |
| use std::str::FromStr; |
| |
| #[test] |
| fn construct_id_test() { |
| // TODO: Add testing for this |
| } |
| |
| #[test] |
| fn ipv4_prefix_from_str_test() { |
| assert_eq!( |
| // TODO: Add more tests here |
| Ipv4AddrPrefix::from_str(&r"1.1.1.1/1").unwrap(), |
| Ipv4AddrPrefix { address: Ipv4Addr::new(1, 1, 1, 1), prefix: 1 } |
| ); |
| } |
| |
| #[test] |
| fn test_into_cidr_address() { |
| let addr: std::net::Ipv4Addr = "169.254.0.0".parse().unwrap(); |
| let ipv4addr: Ipv4AddrPrefix = Ipv4AddrPrefix { address: addr.clone(), prefix: 16 }; |
| let expected = fidl_fuchsia_router_config::CidrAddress { |
| address: Some(fidl_fuchsia_net::IpAddress::Ipv4(Ipv4Address { addr: addr.octets() })), |
| prefix_length: Some(16), |
| }; |
| let actual: fidl_fuchsia_router_config::CidrAddress = ipv4addr.try_into().unwrap(); |
| assert_eq!(actual, expected); |
| } |
| |
| #[test] |
| fn test_security_feature_from_str() { |
| assert_eq!(SecurityFeature::from_str("nat").unwrap(), SecurityFeature::NAT); |
| assert_eq!(SecurityFeature::from_str("NAT").unwrap(), SecurityFeature::NAT); |
| assert_eq!(SecurityFeature::from_str("NaT").unwrap(), SecurityFeature::NAT); |
| assert_eq!( |
| SecurityFeature::from_str("nnat").unwrap_err().to_string(), |
| format!("Invalid security feature: 'nnat'") |
| ); |
| } |
| } |