From 69cc9aaf259d4c55b74d7b75037992431136ba42 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Fri, 28 May 2021 18:18:13 +0800 Subject: [PATCH 1/3] npc: Show brief network state when no argument When running `npc` without any argument, instead of dumping all information, this patch changed to show brief network status. Added new sub command `iface` to `npc` for old behaviour: * `npc eth1` -- show eth1 interface info. * `npc iface eth1` -- the same as `npc eth1`. * `npc iface` -- show full information in yaml format. * `npc` -- show brief network state. * `npc --json` -- show brief network state in json format. Example on running `npc` without argument: ``` 1: lo: state unknown mtu 65536 mac 00:00:00:00:00:00 ipv4 127.0.0.1/8 ipv6 ::1/128 2: eth1: state up mtu 1500 mac 00:01:02:03:04:05 ipv4 192.0.2.6/24 gw4 192.0.2.1 ipv6 2001:db8::6/64 fe80::6/64 gw6 fe80::1 ``` This patch contains many memory copy done by `to_string()`, `clone()`, `to_vec()`. We can improve it in future if that cause any problem. Signed-off-by: Gris Ge --- src/cli/npc.rs | 232 +++++++++++++++++++++++++++++++++++++--- src/lib/ifaces/iface.rs | 17 +++ 2 files changed, 237 insertions(+), 12 deletions(-) diff --git a/src/cli/npc.rs b/src/cli/npc.rs index b7171bb..41ddbd8 100644 --- a/src/cli/npc.rs +++ b/src/cli/npc.rs @@ -13,19 +13,188 @@ // limitations under the License. use clap::{clap_app, crate_authors, crate_version}; -use nispor::{Iface, NetConf, NetState, NisporError, Route, RouteRule}; +use nispor::{ + Iface, IfaceState, NetConf, NetState, NisporError, Route, RouteRule, +}; use serde_derive::Serialize; use serde_json; use serde_yaml; +use std::collections::HashMap; use std::fmt; use std::io::{stderr, stdout, Write}; use std::process; -#[derive(Serialize)] +const INDENT: &str = " "; + +#[derive(Serialize, Debug)] pub struct CliError { pub msg: String, } +#[derive(Serialize, Default)] +struct CliIfaceBrief { + index: u32, + name: String, + state: IfaceState, + flags: Vec, + mac: String, + permanent_mac: String, + mtu: i64, + ipv4: Vec, + ipv6: Vec, + gw4: Vec, + gw6: Vec, +} + +impl CliIfaceBrief { + fn to_string(briefs: &[CliIfaceBrief]) -> String { + let mut ret = Vec::new(); + for brief in briefs { + ret.push(format!( + "{}: {}: <{}> state {} mtu {}", + brief.index, + brief.name, + brief.flags.join(","), + brief.state, + brief.mtu, + )); + if &brief.mac != "" { + ret.push(format!( + "{}mac {}{}", + INDENT, + brief.mac, + if &brief.permanent_mac != "" + && &brief.permanent_mac != &brief.mac + { + format!(" permanent_mac: {}", brief.permanent_mac) + } else { + "".into() + } + )); + } + if brief.ipv4.len() > 0 { + ret.push(format!( + "{}ipv4 {}{}", + INDENT, + brief.ipv4.join(" "), + if brief.gw4.len() > 0 { + format!(" gw4 {}", brief.gw4.join(" ")) + } else { + "".into() + }, + )); + } + if brief.ipv6.len() > 0 { + ret.push(format!( + "{}ipv6 {}{}", + INDENT, + brief.ipv6.join(" "), + if brief.gw6.len() > 0 { + format!(" gw6 {}", brief.gw6.join(" ")) + } else { + "".into() + } + )); + } + } + ret.join("\n") + } + + fn from_net_state(netstate: &NetState) -> Vec { + let mut ret = Vec::new(); + let mut iface_to_gw4: HashMap> = HashMap::new(); + let mut iface_to_gw6: HashMap> = HashMap::new(); + + for route in &netstate.routes { + if let Route { + dst: None, + gateway: Some(gw), + oif: Some(iface_name), + .. + } = route + { + if gw.contains(":") { + match iface_to_gw6.get_mut(iface_name) { + Some(gateways) => { + gateways.push(gw.to_string()); + } + None => { + iface_to_gw6.insert( + iface_name.to_string(), + vec![gw.to_string()], + ); + } + } + } else { + match iface_to_gw4.get_mut(iface_name) { + Some(gateways) => { + gateways.push(gw.to_string()); + } + None => { + iface_to_gw4.insert( + iface_name.to_string(), + vec![gw.to_string()], + ); + } + } + } + } + } + + for iface in netstate.ifaces.values() { + ret.push(CliIfaceBrief { + index: iface.index, + name: iface.name.clone(), + flags: (&iface.flags) + .into_iter() + .map(|flag| format!("{:?}", flag).to_uppercase()) + .collect(), + state: iface.state.clone(), + mac: iface.mac_address.clone(), + permanent_mac: iface.permanent_mac_address.clone(), + mtu: iface.mtu, + ipv4: match &iface.ipv4 { + Some(ip_info) => { + let mut addr_strs = Vec::new(); + for addr in &ip_info.addresses { + addr_strs.push(format!( + "{}/{}", + addr.address, addr.prefix_len + )); + } + addr_strs + } + None => Vec::new(), + }, + ipv6: match &iface.ipv6 { + Some(ip_info) => { + let mut addr_strs = Vec::new(); + for addr in &ip_info.addresses { + addr_strs.push(format!( + "{}/{}", + addr.address, addr.prefix_len + )); + } + addr_strs + } + None => Vec::new(), + }, + gw4: match &iface_to_gw4.get(&iface.name) { + Some(gws) => gws.to_vec(), + None => Vec::new(), + }, + gw6: match &iface_to_gw6.get(&iface.name) { + Some(gws) => gws.to_vec(), + None => Vec::new(), + }, + ..Default::default() + }) + } + ret.sort_by(|a, b| a.index.cmp(&b.index)); + ret + } +} + impl fmt::Display for CliError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.msg) @@ -34,6 +203,7 @@ impl fmt::Display for CliError { enum CliResult { Pass, + Brief(Vec), Full(NetState), Ifaces(Vec), Routes(Vec), @@ -42,6 +212,7 @@ enum CliResult { NisporError(NisporError), } +#[derive(PartialEq)] enum CliOutputType { Json, Yaml, @@ -53,6 +224,7 @@ macro_rules! npc_print { CliResult::Pass => { process::exit(0); } + CliResult::Brief(_) => unreachable!(), CliResult::Full(netstate) => { writeln!(stdout(), "{}", $display_func(&netstate).unwrap()) .ok(); @@ -83,9 +255,24 @@ macro_rules! npc_print { } fn print_result(result: &CliResult, output_type: CliOutputType) { - match output_type { - CliOutputType::Json => npc_print!(serde_json::to_string_pretty, result), - CliOutputType::Yaml => npc_print!(serde_yaml::to_string, result), + if let CliResult::Brief(briefs) = result { + if output_type == CliOutputType::Json { + writeln!( + stdout(), + "{}", + serde_json::to_string_pretty(&briefs).unwrap() + ) + .ok(); + } else { + writeln!(stdout(), "{}", CliIfaceBrief::to_string(&briefs)).ok(); + } + } else { + match output_type { + CliOutputType::Json => { + npc_print!(serde_json::to_string_pretty, result) + } + CliOutputType::Yaml => npc_print!(serde_yaml::to_string, result), + } } } @@ -133,6 +320,11 @@ fn main() { (about: "Nispor CLI") (@arg ifname: [INTERFACE_NAME] "interface name") (@arg json: -j --json "Show in json format") + (@subcommand iface => + (@arg json: -j --json "Show in json format") + (@arg ifname: [INTERFACE_NAME] "Show only specified interface") + (about: "Show interface") + ) (@subcommand route => (@arg json: -j --json "Show in json format") (@arg dev: -d --dev [OIF] "Show only route entries with output to the specified interface") @@ -162,13 +354,21 @@ fn main() { } else { let result = match NetState::retrieve() { Ok(mut state) => { - if let Some(ifname) = matches.value_of("ifname") { - if let Some(iface) = state.ifaces.remove(ifname) { - CliResult::Ifaces(vec![iface]) + if let Some(m) = matches.subcommand_matches("iface") { + output_format = parse_arg_output_format(m); + if let Some(ifname) = m.value_of("ifname") { + if let Some(iface) = state.ifaces.remove(ifname) { + CliResult::Ifaces(vec![iface]) + } else { + CliResult::CliError(CliError { + msg: format!( + "Interface '{}' not found", + ifname + ), + }) + } } else { - CliResult::CliError(CliError { - msg: format!("Interface '{}' not found", ifname), - }) + CliResult::Full(state) } } else if let Some(m) = matches.subcommand_matches("route") { output_format = parse_arg_output_format(m); @@ -176,9 +376,17 @@ fn main() { } else if let Some(m) = matches.subcommand_matches("rule") { output_format = parse_arg_output_format(m); CliResult::RouteRules(state.rules) + } else if let Some(ifname) = matches.value_of("ifname") { + if let Some(iface) = state.ifaces.remove(ifname) { + CliResult::Ifaces(vec![iface]) + } else { + CliResult::CliError(CliError { + msg: format!("Interface '{}' not found", ifname), + }) + } } else { /* Show everything if no cmdline arg has been supplied */ - CliResult::Full(state) + CliResult::Brief(CliIfaceBrief::from_net_state(&state)) } } Err(e) => CliResult::NisporError(e), diff --git a/src/lib/ifaces/iface.rs b/src/lib/ifaces/iface.rs index ee71b0f..60afacd 100644 --- a/src/lib/ifaces/iface.rs +++ b/src/lib/ifaces/iface.rs @@ -102,6 +102,23 @@ impl Default for IfaceState { } } +impl std::fmt::Display for IfaceState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Up => "up", + Self::Dormant => "dormant", + Self::Down => "down", + Self::LowerLayerDown => "lower_layer_down", + Self::Other(s) => s.as_str(), + Self::Unknown => "unknown", + } + ) + } +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[serde(rename_all = "snake_case")] pub enum IfaceFlags { -- 2.32.0