Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/ip/link/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ impl LinkCommand {
.unwrap_or_default()
.map(String::as_str)
.collect();
handle_show(&opts).await
handle_show(&opts, matches.get_flag("DETAILS")).await
} else {
handle_show(&[]).await
handle_show(&[], matches.get_flag("DETAILS")).await
}
}
}
226 changes: 219 additions & 7 deletions src/ip/link/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,188 @@
use std::collections::HashMap;

use futures_util::stream::TryStreamExt;
use rtnetlink::packet_route::link::{LinkAttribute, LinkMessage};
use rtnetlink::packet_route::link::{
AfSpecInet6, AfSpecUnspec, LinkAttribute, LinkLayerType, LinkMessage,
};
use serde::Serialize;

use super::flags::link_flags_to_string;
use iproute_rs::{
CanDisplay, CanOutput, CliColor, CliError, mac_to_string, write_with_color,
};

#[derive(Serialize)]
#[serde(untagged)]
enum CliLinkTypeDetails {
Loopback,
}

impl CliLinkTypeDetails {
fn new(link_type: LinkLayerType, _nl_attrs: &[LinkAttribute]) -> Self {
match link_type {
LinkLayerType::Loopback => CliLinkTypeDetails::Loopback,
LinkLayerType::Ether => todo!(),
LinkLayerType::Netrom => todo!(),
LinkLayerType::Eether => todo!(),
LinkLayerType::Ax25 => todo!(),
LinkLayerType::Pronet => todo!(),
LinkLayerType::Chaos => todo!(),
LinkLayerType::Ieee802 => todo!(),
LinkLayerType::Arcnet => todo!(),
LinkLayerType::Appletlk => todo!(),
LinkLayerType::Dlci => todo!(),
LinkLayerType::Atm => todo!(),
LinkLayerType::Metricom => todo!(),
LinkLayerType::Ieee1394 => todo!(),
LinkLayerType::Eui64 => todo!(),
LinkLayerType::Infiniband => todo!(),
LinkLayerType::Slip => todo!(),
LinkLayerType::Cslip => todo!(),
LinkLayerType::Slip6 => todo!(),
LinkLayerType::Cslip6 => todo!(),
LinkLayerType::Rsrvd => todo!(),
LinkLayerType::Adapt => todo!(),
LinkLayerType::Rose => todo!(),
LinkLayerType::X25 => todo!(),
LinkLayerType::Hwx25 => todo!(),
LinkLayerType::Can => todo!(),
LinkLayerType::Ppp => todo!(),
LinkLayerType::Hdlc => todo!(),
LinkLayerType::Lapb => todo!(),
LinkLayerType::Ddcmp => todo!(),
LinkLayerType::Rawhdlc => todo!(),
LinkLayerType::Rawip => todo!(),
LinkLayerType::Tunnel => todo!(),
LinkLayerType::Tunnel6 => todo!(),
LinkLayerType::Frad => todo!(),
LinkLayerType::Skip => todo!(),
LinkLayerType::Localtlk => todo!(),
LinkLayerType::Fddi => todo!(),
LinkLayerType::Bif => todo!(),
LinkLayerType::Sit => todo!(),
LinkLayerType::Ipddp => todo!(),
LinkLayerType::Ipgre => todo!(),
LinkLayerType::Pimreg => todo!(),
LinkLayerType::Hippi => todo!(),
LinkLayerType::Ash => todo!(),
LinkLayerType::Econet => todo!(),
LinkLayerType::Irda => todo!(),
LinkLayerType::Fcpp => todo!(),
LinkLayerType::Fcal => todo!(),
LinkLayerType::Fcpl => todo!(),
LinkLayerType::Fcfabric => todo!(),
LinkLayerType::Ieee802Tr => todo!(),
LinkLayerType::Ieee80211 => todo!(),
LinkLayerType::Ieee80211Prism => todo!(),
LinkLayerType::Ieee80211Radiotap => todo!(),
LinkLayerType::Ieee802154 => todo!(),
LinkLayerType::Ieee802154Monitor => todo!(),
LinkLayerType::Phonet => todo!(),
LinkLayerType::PhonetPipe => todo!(),
LinkLayerType::Caif => todo!(),
LinkLayerType::Ip6gre => todo!(),
LinkLayerType::Netlink => todo!(),
LinkLayerType::Sixlowpan => todo!(),
LinkLayerType::Vsockmon => todo!(),
LinkLayerType::Void => todo!(),
LinkLayerType::None => todo!(),
_ => todo!(),
}
}
}

impl std::fmt::Display for CliLinkTypeDetails {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CliLinkTypeDetails::Loopback => (),
}

Ok(())
}
}

#[derive(Serialize)]
pub(crate) struct CliLinkInfoDetails {
promiscuity: u32,
min_mtu: u32,
max_mtu: u32,
#[serde(skip_serializing_if = "String::is_empty")]
inet6_addr_gen_mode: String,
num_tx_queues: u32,
num_rx_queues: u32,
gso_max_size: u32,
gso_max_segs: u32,
#[serde(flatten)]
link_type_details: CliLinkTypeDetails,
}

impl CliLinkInfoDetails {
fn new_with_type(
link_type: LinkLayerType,
nl_attrs: &[LinkAttribute],
) -> Self {
let link_type_details = CliLinkTypeDetails::new(link_type, nl_attrs);

let mut promiscuity = 0;
let mut min_mtu = 0;
let mut max_mtu = 0;
let mut num_tx_queues = 0;
let mut num_rx_queues = 0;
let mut gso_max_size = 0;
let mut gso_max_segs = 0;
let mut inet6_addr_gen_mode = String::new();

for nl_attr in nl_attrs {
match nl_attr {
LinkAttribute::Promiscuity(p) => promiscuity = *p,
LinkAttribute::MinMtu(m) => min_mtu = *m,
LinkAttribute::MaxMtu(m) => max_mtu = *m,
LinkAttribute::AfSpecUnspec(a) => {
inet6_addr_gen_mode = get_addr_gen_mode(a)
}
LinkAttribute::NumTxQueues(n) => num_tx_queues = *n,
LinkAttribute::NumRxQueues(n) => num_rx_queues = *n,
LinkAttribute::GsoMaxSize(g) => gso_max_size = *g,
LinkAttribute::GsoMaxSegs(g) => gso_max_segs = *g,
_ => {
// println!("Remains {:?}", nl_attr);
}
}
}

Self {
promiscuity,
min_mtu,
max_mtu,
inet6_addr_gen_mode,
num_tx_queues,
num_rx_queues,
gso_max_size,
gso_max_segs,
link_type_details,
}
}
}

impl std::fmt::Display for CliLinkInfoDetails {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
" promiscuity {} minmtu {} maxmtu {} addrgenmode {} numtxqueues {} numrxqueues {} gso_max_size {} gso_max_segs {} {}",
self.promiscuity,
self.min_mtu,
self.max_mtu,
self.inet6_addr_gen_mode,
self.num_tx_queues,
self.num_rx_queues,
self.gso_max_size,
self.gso_max_segs,
self.link_type_details
)?;
Ok(())
}
}

#[derive(Serialize, Default)]
pub(crate) struct CliLinkInfo {
ifindex: u32,
Expand All @@ -32,6 +206,9 @@ pub(crate) struct CliLinkInfo {
address: String,
#[serde(skip_serializing_if = "String::is_empty")]
broadcast: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
details: Option<CliLinkInfoDetails>,
}

impl std::fmt::Display for CliLinkInfo {
Expand Down Expand Up @@ -67,6 +244,10 @@ impl std::fmt::Display for CliLinkInfo {
write!(f, " brd ")?;
write_with_color!(f, CliColor::Mac, "{}", self.broadcast)?;
}

if let Some(details) = &self.details {
write!(f, "{details}",)?;
}
Ok(())
}
}
Expand All @@ -80,25 +261,24 @@ impl CanDisplay for CliLinkInfo {
impl CanOutput for CliLinkInfo {}

pub(crate) async fn handle_show(
_opts: &[&str],
opts: &[&str],
include_details: bool,
) -> Result<Vec<CliLinkInfo>, CliError> {
let (connection, handle, _) = rtnetlink::new_connection()?;

tokio::spawn(connection);

let link_get_handle = handle.link().get();
let mut link_get_handle = handle.link().get();

/*
if let Some(iface_name) = filter.iface_name.as_ref() {
if let Some(iface_name) = opts.first() {
link_get_handle = link_get_handle.match_name(iface_name.to_string());
}
*/

let mut links = link_get_handle.execute();
let mut ifaces: Vec<CliLinkInfo> = Vec::new();

while let Some(nl_msg) = links.try_next().await? {
ifaces.push(parse_nl_msg_to_iface(nl_msg)?);
ifaces.push(parse_nl_msg_to_iface(nl_msg, include_details)?);
}

resolve_controller_name(&mut ifaces);
Expand All @@ -108,6 +288,7 @@ pub(crate) async fn handle_show(

pub(crate) fn parse_nl_msg_to_iface(
nl_msg: LinkMessage,
include_details: bool,
) -> Result<CliLinkInfo, CliError> {
let mut ret = CliLinkInfo {
ifindex: nl_msg.header.index,
Expand All @@ -116,6 +297,13 @@ pub(crate) fn parse_nl_msg_to_iface(
..Default::default()
};

ret.details = include_details.then(|| {
CliLinkInfoDetails::new_with_type(
nl_msg.header.link_layer_type,
&nl_msg.attributes,
)
});

for nl_attr in nl_msg.attributes {
match nl_attr {
LinkAttribute::IfName(name) => ret.ifname = name,
Expand Down Expand Up @@ -144,9 +332,33 @@ pub(crate) fn parse_nl_msg_to_iface(
}
}
}

Ok(ret)
}

fn get_addr_gen_mode(af_spec_unspec: &[AfSpecUnspec]) -> String {
af_spec_unspec
.iter()
.filter_map(|s| {
let AfSpecUnspec::Inet6(v) = s else {
return None;
};
v.iter()
.filter_map(|i| {
if let AfSpecInet6::AddrGenMode(mode) = i {
Some(mode)
} else {
None
}
})
.next()
})
.next()
.copied()
.unwrap_or_default()
.to_string()
}

fn resolve_ip_link_group_name(id: u32) -> String {
// TODO: Read `/usr/share/iproute2/group` and `/etc/iproute2/group`
match id {
Expand Down
23 changes: 23 additions & 0 deletions src/ip/link/tests/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ fn test_link_show() {
pretty_assertions::assert_eq!(expected_output, our_output);
}

#[test]
fn test_link_detailed_show() {
let cli_path = get_ip_cli_path();

let expected_output = exec_cmd(&["ip", "-d", "link", "show", "lo"]);

let our_output = exec_cmd(&[cli_path.as_str(), "-d", "link", "show", "lo"]);

pretty_assertions::assert_eq!(expected_output, our_output);
}

#[test]
fn test_link_show_json() {
let cli_path = get_ip_cli_path();
Expand All @@ -23,3 +34,15 @@ fn test_link_show_json() {

pretty_assertions::assert_eq!(expected_output, our_output);
}

#[test]
fn test_link_detailed_show_json() {
let cli_path = get_ip_cli_path();

let expected_output = exec_cmd(&["ip", "-d", "-j", "link", "show", "lo"]);

let our_output =
exec_cmd(&[cli_path.as_str(), "-d", "-j", "link", "show", "lo"]);

pretty_assertions::assert_eq!(expected_output, our_output);
}
7 changes: 7 additions & 0 deletions src/ip/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ async fn main() -> Result<(), CliError> {
.action(clap::ArgAction::SetTrue)
.global(true),
)
.arg(
clap::Arg::new("DETAILS")
.short('d')
.help("Interface details")
.action(clap::ArgAction::SetTrue)
.global(true),
)
.subcommand_required(true)
.subcommand(LinkCommand::gen_command());

Expand Down
Loading