nispor/SOURCES/0001-npc-Show-brief-network...

376 lines
13 KiB
Diff

From 69cc9aaf259d4c55b74d7b75037992431136ba42 Mon Sep 17 00:00:00 2001
From: Gris Ge <fge@redhat.com>
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: <LOOPBACK,LOWERUP,RUNNING,UP> state unknown mtu 65536
mac 00:00:00:00:00:00
ipv4 127.0.0.1/8
ipv6 ::1/128
2: eth1: <BROADCAST,LOWERUP,MULTICAST,RUNNING,UP> 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 <fge@redhat.com>
---
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<String>,
+ mac: String,
+ permanent_mac: String,
+ mtu: i64,
+ ipv4: Vec<String>,
+ ipv6: Vec<String>,
+ gw4: Vec<String>,
+ gw6: Vec<String>,
+}
+
+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<Self> {
+ let mut ret = Vec::new();
+ let mut iface_to_gw4: HashMap<String, Vec<String>> = HashMap::new();
+ let mut iface_to_gw6: HashMap<String, Vec<String>> = 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<CliIfaceBrief>),
Full(NetState),
Ifaces(Vec<Iface>),
Routes(Vec<Route>),
@@ -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