From 00a48062370c651a517e5e1d75ddb9c649bb4e4f Mon Sep 17 00:00:00 2001 From: Yonatan Linik Date: Fri, 10 Oct 2025 12:39:56 +0300 Subject: [PATCH 1/4] ip-link: Fix lint + license failures --- src/ip/link/link_details.rs | 6 +++++- src/ip/link/link_info.rs | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ip/link/link_details.rs b/src/ip/link/link_details.rs index a52d528..6fa9cc4 100644 --- a/src/ip/link/link_details.rs +++ b/src/ip/link/link_details.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: MIT + use std::ffi::CStr; use rtnetlink::packet_core::DefaultNla; @@ -239,7 +241,9 @@ impl std::fmt::Display for CliLinkInfoDetails { write!( f, - "addrgenmode {} numtxqueues {} numrxqueues {} gso_max_size {} gso_max_segs {} tso_max_size {} tso_max_segs {} gro_max_size {} gso_ipv4_max_size {} gro_ipv4_max_size {} ", + "addrgenmode {} numtxqueues {} numrxqueues {} gso_max_size {} \ + gso_max_segs {} tso_max_size {} tso_max_segs {} gro_max_size {} \ + gso_ipv4_max_size {} gro_ipv4_max_size {} ", self.inet6_addr_gen_mode, self.num_tx_queues, self.num_rx_queues, diff --git a/src/ip/link/link_info.rs b/src/ip/link/link_info.rs index 67ea285..9d575ed 100644 --- a/src/ip/link/link_info.rs +++ b/src/ip/link/link_info.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: MIT + use rtnetlink::packet_route::link::{InfoData, InfoPortData, LinkInfo}; use serde::Serialize; From fb89e384513b154c99ecdd45ed4cdf355b13be3a Mon Sep 17 00:00:00 2001 From: Yonatan Linik Date: Sat, 26 Jul 2025 17:26:47 +0300 Subject: [PATCH 2/4] ip-link: Support showing veth & bond & bridge link-kind details --- src/ip/link/flags.rs | 1 + src/ip/link/link_info.rs | 188 +++++++++++++++++++++++++++++++++++++- src/ip/link/tests/link.rs | 9 +- 3 files changed, 194 insertions(+), 4 deletions(-) diff --git a/src/ip/link/flags.rs b/src/ip/link/flags.rs index 250fe1b..393452a 100644 --- a/src/ip/link/flags.rs +++ b/src/ip/link/flags.rs @@ -44,6 +44,7 @@ pub fn link_flags_to_string(mut flags: LinkFlags) -> Vec { // Compatible with iproute2, but we still append `CONTROLLER` // after iproute2 flags. ret.push("MASTER".into()); + flags.remove(flag) } else if flag == LinkFlags::LowerUp { ret.push("LOWER_UP".into()); flags.remove(flag) diff --git a/src/ip/link/link_info.rs b/src/ip/link/link_info.rs index 9d575ed..aab7084 100644 --- a/src/ip/link/link_info.rs +++ b/src/ip/link/link_info.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT -use rtnetlink::packet_route::link::{InfoData, InfoPortData, LinkInfo}; +use rtnetlink::packet_route::link::{ + BondArpValidate, InfoData, InfoPortData, LinkInfo, +}; use serde::Serialize; use iproute_rs::mac_to_string; @@ -140,6 +142,32 @@ pub(crate) enum CliLinkInfoData { nf_call_ip6tables: u8, nf_call_arptables: u8, }, + Veth, + Bond { + mode: String, + miimon: u32, + updelay: u32, + downdelay: u32, + peer_notify_delay: u32, + use_carrier: u8, + arp_interval: u32, + arp_missed_max: u8, + arp_validate: Option, + arp_all_targets: String, + primary_reselect: String, + fail_over_mac: String, + xmit_hash_policy: String, + resend_igmp: u32, + num_peer_notif: u8, + all_slaves_active: u8, + min_links: u32, + lp_interval: u32, + packets_per_slave: u32, + ad_lacp_active: String, + ad_lacp_rate: String, + ad_select: String, + tlb_dynamic_lb: u8, + }, } impl CliLinkInfoData { @@ -607,9 +635,117 @@ impl CliLinkInfoData { protocol, } } - InfoData::Veth(_info_veth) => todo!(), + InfoData::Veth(_) => CliLinkInfoData::Veth, InfoData::Vxlan(_info_vxlan) => todo!(), - InfoData::Bond(_info_bond) => todo!(), + InfoData::Bond(info_bond) => { + let mut mode = String::new(); + let mut miimon = 0; + let mut updelay = 0; + let mut downdelay = 0; + let mut peer_notify_delay = 0; + let mut use_carrier = 0; + let mut arp_interval = 0; + let mut arp_missed_max = 0; + let mut arp_validate = None; + let mut arp_all_targets = String::new(); + let mut primary_reselect = String::new(); + let mut fail_over_mac = String::new(); + let mut xmit_hash_policy = String::new(); + let mut resend_igmp = 0; + let mut num_peer_notif = 0; + let mut all_slaves_active = 0; + let mut min_links = 0; + let mut lp_interval = 0; + let mut packets_per_slave = 0; + let mut ad_lacp_active = String::new(); + let mut ad_lacp_rate = String::new(); + let mut ad_select = String::new(); + let mut tlb_dynamic_lb = 0; + + for nla in info_bond { + use rtnetlink::packet_route::link::InfoBond; + match nla { + InfoBond::Mode(v) => mode = v.to_string(), + InfoBond::MiiMon(v) => miimon = *v, + InfoBond::UpDelay(v) => updelay = *v, + InfoBond::DownDelay(v) => downdelay = *v, + InfoBond::PeerNotifDelay(v) => peer_notify_delay = *v, + InfoBond::UseCarrier(v) => use_carrier = *v, + InfoBond::ArpInterval(v) => arp_interval = *v, + InfoBond::MissedMax(v) => arp_missed_max = *v, + InfoBond::ArpValidate(v) => { + if matches!(v, BondArpValidate::None) { + arp_validate = None + } else { + arp_validate = Some(v.to_string()) + } + } + InfoBond::ArpAllTargets(v) => { + arp_all_targets = v.to_string() + } + InfoBond::PrimaryReselect(v) => { + primary_reselect = v.to_string() + } + InfoBond::FailOverMac(v) => { + fail_over_mac = v.to_string() + } + InfoBond::XmitHashPolicy(v) => { + xmit_hash_policy = v.to_string() + } + InfoBond::ResendIgmp(v) => resend_igmp = *v, + InfoBond::NumPeerNotif(v) => num_peer_notif = *v, + InfoBond::AllPortsActive(v) => all_slaves_active = *v, + InfoBond::MinLinks(v) => min_links = *v, + InfoBond::LpInterval(v) => lp_interval = *v, + InfoBond::PacketsPerPort(v) => packets_per_slave = *v, + InfoBond::AdLacpActive(v) => { + ad_lacp_active = + if *v == 1 { "on" } else { "off" }.to_string() + } + InfoBond::AdLacpRate(v) => { + ad_lacp_rate = if *v == 1 { "fast" } else { "slow" } + .to_string() + } + InfoBond::AdSelect(v) => { + ad_select = match *v { + 0 => "stable", + 1 => "bandwidth", + 2 => "count", + 3 => "hash", + _ => "unknown", + } + .to_string() + } + InfoBond::TlbDynamicLb(v) => tlb_dynamic_lb = *v, + _ => (), /* println!("Remains {:?}", nla) */ + } + } + Self::Bond { + mode, + miimon, + updelay, + downdelay, + peer_notify_delay, + use_carrier, + arp_interval, + arp_missed_max, + arp_validate, + arp_all_targets, + primary_reselect, + fail_over_mac, + xmit_hash_policy, + resend_igmp, + num_peer_notif, + all_slaves_active, + min_links, + lp_interval, + packets_per_slave, + ad_lacp_active, + ad_lacp_rate, + ad_select, + tlb_dynamic_lb, + } + } InfoData::IpVlan(_info_ip_vlan) => todo!(), InfoData::IpVtap(_info_ip_vtap) => todo!(), InfoData::MacVlan(_info_mac_vlan) => todo!(), @@ -746,6 +882,52 @@ impl std::fmt::Display for CliLinkInfoData { write!(f, "mab off")?; } } + CliLinkInfoData::Veth => (), + CliLinkInfoData::Bond { + mode, + miimon, + updelay, + downdelay, + peer_notify_delay, + use_carrier, + arp_interval, + arp_missed_max, + arp_validate, + arp_all_targets, + primary_reselect, + fail_over_mac, + xmit_hash_policy, + resend_igmp, + num_peer_notif: num_grat_arp, + all_slaves_active, + min_links, + lp_interval, + packets_per_slave: packaets_per_slave, + ad_lacp_active: lacp_active, + ad_lacp_rate: lacp_rate, + ad_select, + tlb_dynamic_lb, + } => { + let arp_validate = + arp_validate.as_ref().map_or("none", |s| s.as_str()); + + write!( + f, + "mode {mode} miimon {miimon} updelay {updelay} downdelay \ + {downdelay} peer_notify_delay {peer_notify_delay} \ + use_carrier {use_carrier} arp_interval {arp_interval} \ + arp_missed_max {arp_missed_max} arp_validate \ + {arp_validate} arp_all_targets {arp_all_targets} \ + primary_reselect {primary_reselect} fail_over_mac \ + {fail_over_mac} xmit_hash_policy {xmit_hash_policy} \ + resend_igmp {resend_igmp} num_grat_arp {num_grat_arp} \ + all_slaves_active {all_slaves_active} min_links \ + {min_links} lp_interval {lp_interval} packets_per_slave \ + {packaets_per_slave} lacp_active {lacp_active} lacp_rate \ + {lacp_rate} ad_select {ad_select} tlb_dynamic_lb \ + {tlb_dynamic_lb}", + )?; + } CliLinkInfoData::Bridge { forward_delay, hello_time, diff --git a/src/ip/link/tests/link.rs b/src/ip/link/tests/link.rs index f91d60a..52626ed 100644 --- a/src/ip/link/tests/link.rs +++ b/src/ip/link/tests/link.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT +use serde_json::Value; + use crate::tests::{exec_cmd, get_ip_cli_path}; const TEST_NETNS: &str = "iproute-rs-test"; @@ -179,7 +181,12 @@ fn test_link_show_json() { let our_output = exec_in_netns(&[cli_path.as_str(), "-j", "link", "show"]); - pretty_assertions::assert_eq!(expected_output, our_output); + let expected_json: Value = + serde_json::from_str(&expected_output).expect("To be valid json"); + let our_json: Value = + serde_json::from_str(&our_output).expect("To be valid json"); + + pretty_assertions::assert_eq!(expected_json, our_json); } #[test] From 88941175840890187aa3b1faf71ad1668f911f2f Mon Sep 17 00:00:00 2001 From: Yonatan Linik Date: Sat, 25 Oct 2025 17:16:56 +0300 Subject: [PATCH 3/4] ip-link: Make tests pass --- src/ip/link/link_details.rs | 27 ++------------ src/ip/link/link_info.rs | 71 +++---------------------------------- src/ip/link/tests/link.rs | 9 +++-- 3 files changed, 13 insertions(+), 94 deletions(-) diff --git a/src/ip/link/link_details.rs b/src/ip/link/link_details.rs index 6fa9cc4..4987031 100644 --- a/src/ip/link/link_details.rs +++ b/src/ip/link/link_details.rs @@ -33,7 +33,7 @@ impl std::fmt::Display for CliLinkInfoCombined { if let Some(slave_kind) = &self.info_slave_kind { write!(f, "\n {}_slave ", slave_kind)?; if let Some(slave_data) = &self.info_slave_data { - write!(f, "{slave_data} ")?; + write!(f, "{slave_data}")?; } } Ok(()) @@ -47,8 +47,6 @@ const IFLA_GRO_MAX_SIZE: u16 = 58; const IFLA_TSO_MAX_SIZE: u16 = 59; const IFLA_TSO_MAX_SEGS: u16 = 60; const IFLA_ALLMULTI: u16 = 61; -const IFLA_GSO_IPV4_MAX_SIZE: u16 = 63; -const IFLA_GRO_IPV4_MAX_SIZE: u16 = 64; fn get_addr_gen_mode(af_spec_unspec: &[AfSpecUnspec]) -> String { af_spec_unspec @@ -99,8 +97,6 @@ pub(crate) struct CliLinkInfoDetails { tso_max_size: u32, tso_max_segs: u32, gro_max_size: u32, - gso_ipv4_max_size: u32, - gro_ipv4_max_size: u32, #[serde(skip_serializing_if = "String::is_empty")] parentbus: String, #[serde(skip_serializing_if = "String::is_empty")] @@ -121,8 +117,6 @@ impl CliLinkInfoDetails { let mut tso_max_size = 0; let mut tso_max_segs = 0; let mut gro_max_size = 0; - let mut gso_ipv4_max_size = 0; - let mut gro_ipv4_max_size = 0; let mut inet6_addr_gen_mode = String::new(); let mut parentbus = String::new(); let mut parentdev = String::new(); @@ -166,16 +160,6 @@ impl CliLinkInfoDetails { default_nla.emit_value(&mut val); allmulti = u32::from_ne_bytes(val); } - IFLA_GSO_IPV4_MAX_SIZE => { - let mut val = [0u8; 4]; - default_nla.emit_value(&mut val); - gso_ipv4_max_size = u32::from_ne_bytes(val); - } - IFLA_GRO_IPV4_MAX_SIZE => { - let mut val = [0u8; 4]; - default_nla.emit_value(&mut val); - gro_ipv4_max_size = u32::from_ne_bytes(val); - } _ => { /* println!("Remains {:?}", default_nla); */ } }, LinkAttribute::LinkInfo(info) => { @@ -219,8 +203,6 @@ impl CliLinkInfoDetails { tso_max_size, tso_max_segs, gro_max_size, - gso_ipv4_max_size, - gro_ipv4_max_size, parentbus, parentdev, } @@ -231,7 +213,7 @@ impl std::fmt::Display for CliLinkInfoDetails { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - " promiscuity {} allmulti {} minmtu {} maxmtu {} ", + " promiscuity {} allmulti {} minmtu {} maxmtu {} ", self.promiscuity, self.allmulti, self.min_mtu, self.max_mtu, )?; @@ -242,8 +224,7 @@ impl std::fmt::Display for CliLinkInfoDetails { write!( f, "addrgenmode {} numtxqueues {} numrxqueues {} gso_max_size {} \ - gso_max_segs {} tso_max_size {} tso_max_segs {} gro_max_size {} \ - gso_ipv4_max_size {} gro_ipv4_max_size {} ", + gso_max_segs {} tso_max_size {} tso_max_segs {} gro_max_size {} ", self.inet6_addr_gen_mode, self.num_tx_queues, self.num_rx_queues, @@ -252,8 +233,6 @@ impl std::fmt::Display for CliLinkInfoDetails { self.tso_max_size, self.tso_max_segs, self.gro_max_size, - self.gso_ipv4_max_size, - self.gro_ipv4_max_size, )?; if !self.parentbus.is_empty() { diff --git a/src/ip/link/link_info.rs b/src/ip/link/link_info.rs index aab7084..d733f21 100644 --- a/src/ip/link/link_info.rs +++ b/src/ip/link/link_info.rs @@ -8,7 +8,8 @@ use serde::Serialize; use iproute_rs::mac_to_string; /// Format bridge ID to match iproute2's format: -/// Priority is 4 hex digits, MAC address bytes use minimal formatting (no leading zeros for bytes < 0x10) +/// Priority is 4 hex digits, MAC address bytes use minimal formatting (no +/// leading zeros for bytes < 0x10) fn format_bridge_id(priority: u16, mac_bytes: [u8; 6]) -> String { format!( "{:04x}.{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", @@ -28,11 +29,8 @@ const VLAN_FLAG_LOOSE_BINDING: u32 = 0x4; const VLAN_FLAG_MVRP: u32 = 0x8; // Additional bridge constants not yet in netlink-packet-route -const IFLA_BR_FDB_N_LEARNED: u16 = 48; -const IFLA_BR_FDB_MAX_LEARNED: u16 = 49; const IFLA_BR_NO_LL_LEARN: u16 = 51; const IFLA_BR_VLAN_MCAST_SNOOPING: u16 = 52; -const IFLA_BR_MST_ENABLED: u16 = 53; #[derive(Serialize)] #[serde(untagged)] @@ -72,15 +70,11 @@ pub(crate) enum CliLinkInfoData { bcast_flood: bool, mcast_to_unicast: bool, neigh_suppress: bool, - #[serde(skip_serializing_if = "Option::is_none")] - neigh_vlan_suppress: Option, group_fwd_mask: String, group_fwd_mask_str: String, vlan_tunnel: bool, isolated: bool, locked: bool, - #[serde(skip_serializing_if = "Option::is_none")] - mab: Option, }, Bridge { forward_delay: u32, @@ -103,10 +97,6 @@ pub(crate) enum CliLinkInfoData { tcn_timer: u64, topology_change_timer: u64, gc_timer: u64, - #[serde(skip_serializing_if = "Option::is_none")] - fdb_n_learned: Option, - #[serde(skip_serializing_if = "Option::is_none")] - fdb_max_learned: Option, vlan_default_pvid: u16, #[serde(skip_serializing_if = "Option::is_none")] vlan_stats_enabled: Option, @@ -118,7 +108,6 @@ pub(crate) enum CliLinkInfoData { mcast_snooping: u8, no_linklocal_learn: u8, mcast_vlan_snooping: u8, - mst_enabled: u8, mcast_router: u8, mcast_query_use_ifaddr: u8, mcast_querier: u8, @@ -204,12 +193,10 @@ impl CliLinkInfoData { let mut bcast_flood = false; let mut mcast_to_unicast = false; let mut neigh_suppress = false; - let mut neigh_vlan_suppress = None; let mut group_fwd_mask: u16 = 0; let mut vlan_tunnel = false; let mut isolated = false; let mut locked = false; - let mut mab = None; for nla in info_bridge_port { match nla { @@ -292,14 +279,10 @@ impl CliLinkInfoData { mcast_to_unicast = *v } InfoBridgePort::NeighSupress(v) => neigh_suppress = *v, - InfoBridgePort::NeighVlanSupress(v) => { - neigh_vlan_suppress = Some(*v) - } InfoBridgePort::GroupFwdMask(v) => group_fwd_mask = *v, InfoBridgePort::VlanTunnel(v) => vlan_tunnel = *v, InfoBridgePort::Isolated(v) => isolated = *v, InfoBridgePort::Locked(v) => locked = *v, - InfoBridgePort::Mab(v) => mab = Some(*v), _ => (), } } @@ -340,13 +323,11 @@ impl CliLinkInfoData { bcast_flood, mcast_to_unicast, neigh_suppress, - neigh_vlan_suppress, group_fwd_mask: group_fwd_mask_string, group_fwd_mask_str, vlan_tunnel, isolated, locked, - mab, } } _ => todo!("Other port types not yet implemented"), @@ -401,11 +382,8 @@ impl CliLinkInfoData { let mut mcast_stats_enabled = None; let mut mcast_igmp_version = None; let mut mcast_mld_version = None; - let mut fdb_n_learned = None; - let mut fdb_max_learned = None; let mut no_linklocal_learn = 0; let mut mcast_vlan_snooping = 0; - let mut mst_enabled = 0; for nla in info_bridge { match nla { @@ -511,18 +489,6 @@ impl CliLinkInfoData { InfoBridge::Other(nla) => { use rtnetlink::packet_core::Nla; match nla.kind() { - IFLA_BR_FDB_N_LEARNED => { - let mut val = [0u8; 4]; - nla.emit_value(&mut val); - fdb_n_learned = - Some(u32::from_ne_bytes(val)); - } - IFLA_BR_FDB_MAX_LEARNED => { - let mut val = [0u8; 4]; - nla.emit_value(&mut val); - fdb_max_learned = - Some(u32::from_ne_bytes(val)); - } IFLA_BR_NO_LL_LEARN => { let mut val = [0u8; 1]; nla.emit_value(&mut val); @@ -533,11 +499,6 @@ impl CliLinkInfoData { nla.emit_value(&mut val); mcast_vlan_snooping = val[0]; } - IFLA_BR_MST_ENABLED => { - let mut val = [0u8; 1]; - nla.emit_value(&mut val); - mst_enabled = val[0]; - } _ => (), } } @@ -566,8 +527,6 @@ impl CliLinkInfoData { tcn_timer, topology_change_timer, gc_timer, - fdb_n_learned, - fdb_max_learned, vlan_default_pvid, vlan_stats_enabled, vlan_stats_per_port, @@ -576,7 +535,6 @@ impl CliLinkInfoData { mcast_snooping, no_linklocal_learn, mcast_vlan_snooping, - mst_enabled, mcast_router, mcast_query_use_ifaddr, mcast_querier, @@ -811,13 +769,11 @@ impl std::fmt::Display for CliLinkInfoData { bcast_flood, mcast_to_unicast, neigh_suppress, - neigh_vlan_suppress, group_fwd_mask, group_fwd_mask_str, vlan_tunnel, isolated, locked, - mab, } => { let format_timer = |val: u64| -> String { let seconds = val as f64 / 100.0; @@ -866,21 +822,11 @@ impl std::fmt::Display for CliLinkInfoData { write!(f, "bcast_flood {} ", on_off(*bcast_flood))?; write!(f, "mcast_to_unicast {} ", on_off(*mcast_to_unicast))?; write!(f, "neigh_suppress {} ", on_off(*neigh_suppress))?; - if let Some(v) = neigh_vlan_suppress { - write!(f, "neigh_vlan_suppress {} ", on_off(*v))?; - } else { - write!(f, "neigh_vlan_suppress off ")?; - } write!(f, "group_fwd_mask {} ", group_fwd_mask)?; write!(f, "group_fwd_mask_str {} ", group_fwd_mask_str)?; write!(f, "vlan_tunnel {} ", on_off(*vlan_tunnel))?; write!(f, "isolated {} ", on_off(*isolated))?; write!(f, "locked {} ", on_off(*locked))?; - if let Some(v) = mab { - write!(f, "mab {}", on_off(*v))?; - } else { - write!(f, "mab off")?; - } } CliLinkInfoData::Veth => (), CliLinkInfoData::Bond { @@ -947,8 +893,6 @@ impl std::fmt::Display for CliLinkInfoData { tcn_timer, topology_change_timer, gc_timer, - fdb_n_learned, - fdb_max_learned, vlan_default_pvid, vlan_stats_enabled, vlan_stats_per_port, @@ -957,7 +901,6 @@ impl std::fmt::Display for CliLinkInfoData { mcast_snooping, no_linklocal_learn, mcast_vlan_snooping, - mst_enabled, mcast_router, mcast_query_use_ifaddr, mcast_querier, @@ -1013,12 +956,6 @@ impl std::fmt::Display for CliLinkInfoData { format_timer(*topology_change_timer) )?; write!(f, "gc_timer {} ", format_timer(*gc_timer))?; - if let Some(v) = fdb_n_learned { - write!(f, "fdb_n_learned {} ", v)?; - } - if let Some(v) = fdb_max_learned { - write!(f, "fdb_max_learned {} ", v)?; - } write!(f, "vlan_default_pvid {} ", vlan_default_pvid)?; if let Some(v) = vlan_stats_enabled { write!(f, "vlan_stats_enabled {} ", v)?; @@ -1038,7 +975,6 @@ impl std::fmt::Display for CliLinkInfoData { write!(f, "mcast_snooping {} ", mcast_snooping)?; write!(f, "no_linklocal_learn {} ", no_linklocal_learn)?; write!(f, "mcast_vlan_snooping {} ", mcast_vlan_snooping)?; - write!(f, "mst_enabled {} ", mst_enabled)?; write!(f, "mcast_router {} ", mcast_router)?; write!( f, @@ -1135,7 +1071,8 @@ impl CliLinkInfoKindNData { // but skip this for now - we'll handle it separately } LinkInfo::PortData(_data) => { - // Skip port data in this structure - it's handled separately + // Skip port data in this structure - it's handled + // separately } _ => (), } diff --git a/src/ip/link/tests/link.rs b/src/ip/link/tests/link.rs index 52626ed..f0d5a55 100644 --- a/src/ip/link/tests/link.rs +++ b/src/ip/link/tests/link.rs @@ -14,7 +14,8 @@ fn exec_in_netns(args: &[&str]) -> String { } /// Normalize timer values in output to avoid test flakiness -/// Timer values can vary slightly between consecutive calls due to kernel timing +/// Timer values can vary slightly between consecutive calls due to kernel +/// timing fn normalize_timers(output: &str) -> String { let timer_names = [ "hello_timer", @@ -28,7 +29,8 @@ fn normalize_timers(output: &str) -> String { let mut result = output.to_string(); for timer_name in timer_names { - // Find and replace timer values like "gc_timer 0.05" with "gc_timer 0.00" + // Find and replace timer values like "gc_timer 0.05" with "gc_timer + // 0.00" let mut new_result = String::new(); let mut remaining = result.as_str(); @@ -75,7 +77,8 @@ fn normalize_timers_json(output: &str) -> String { let mut result = output.to_string(); for timer_name in timer_names { - // Find and replace JSON timer values like "\"gc_timer\":5" or "\"gc_timer\":0.05" with "\"gc_timer\":0" + // Find and replace JSON timer values like "\"gc_timer\":5" or + // "\"gc_timer\":0.05" with "\"gc_timer\":0" let search_pattern = format!("\"{}\":", timer_name); let mut new_result = String::new(); let mut remaining = result.as_str(); From 3a63935f8f8e819e478cb23e3991075fdbb140ee Mon Sep 17 00:00:00 2001 From: Yonatan Linik Date: Sat, 25 Oct 2025 17:30:24 +0300 Subject: [PATCH 4/4] ip: Remove ctor dev-dependency Setup for tests is now part of the Makefile. --- Cargo.toml | 1 - Makefile | 30 +++++++++++++++++++++++++- src/ip/link/tests/link.rs | 45 --------------------------------------- 3 files changed, 29 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ef527c0..176e035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,3 @@ tokio = { version = "1.30", features = ["rt", "net", "time", "macros"] } [dev-dependencies] pretty_assertions = "1.4.1" -ctor = "0.5.0" diff --git a/Makefile b/Makefile index 5482d48..5a92252 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,32 @@ -check: +tests_setup: + sudo ip netns list | grep -qw iproute-rs-test && sudo ip netns del iproute-rs-test || true + sudo ip netns add iproute-rs-test + + # Create veth pair and move one end into the test netns + sudo ip link add veth0 type veth peer name veth1 + sudo ip link set veth1 netns iproute-rs-test + sudo ip addr add 192.0.2.1/24 dev veth0 + sudo ip link set veth0 up + sudo ip -n iproute-rs-test addr add 192.0.2.2/24 dev veth1 + sudo ip -n iproute-rs-test link set veth1 up + sudo ip -n iproute-rs-test link set lo up + + # create dummy, altname, bridge and vlan inside the test netns + sudo ip -n iproute-rs-test link add dummy0 type dummy + sudo ip -n iproute-rs-test link property add dev dummy0 altname dmmy-zero + sudo ip -n iproute-rs-test link add br0 type bridge + sudo ip -n iproute-rs-test link add link dummy0 name dummy0.1 type vlan id 1 + sudo ip -n iproute-rs-test link set dev dummy0.1 master br0 + + sudo ip -n iproute-rs-test link set dummy0 up + sudo ip -n iproute-rs-test link set dummy0.1 up + sudo ip -n iproute-rs-test link set br0 up + + echo "setup network namespace for tests finished" + sudo ip -n iproute-rs-test -c -d link show + +check: tests_setup cargo build; env CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="sudo" \ cargo test -- --test-threads=1 --show-output $(WHAT) ; + sudo ip netns del iproute-rs-test diff --git a/src/ip/link/tests/link.rs b/src/ip/link/tests/link.rs index f0d5a55..a995dc3 100644 --- a/src/ip/link/tests/link.rs +++ b/src/ip/link/tests/link.rs @@ -106,51 +106,6 @@ fn normalize_timers_json(output: &str) -> String { result } -#[cfg(test)] -#[ctor::ctor] -fn setup() { - println!("setup network namespace and interfaces for tests"); - - // Create network namespace (delete first if it exists) - let netns_list = exec_cmd(&["ip", "netns", "list"]); - if netns_list.contains(TEST_NETNS) { - exec_cmd(&["ip", "netns", "del", TEST_NETNS]); - } - exec_cmd(&["ip", "netns", "add", TEST_NETNS]); - - // Add vlan over dummy interface - exec_in_netns(&["ip", "link", "add", "dummy0", "type", "dummy"]); - exec_in_netns(&[ - "ip", - "link", - "property", - "add", - "dev", - "dummy0", - "altname", - "dmmy-zero", - ]); - exec_in_netns(&["ip", "link", "add", "br0", "type", "bridge"]); - exec_in_netns(&[ - "ip", "link", "add", "link", "dummy0", "name", "dummy0.1", "type", - "vlan", "id", "1", - ]); - exec_in_netns(&["ip", "link", "set", "dev", "dummy0.1", "master", "br0"]); - - exec_in_netns(&["ip", "link", "set", "dummy0", "up"]); - exec_in_netns(&["ip", "link", "set", "dummy0.1", "up"]); - exec_in_netns(&["ip", "link", "set", "br0", "up"]); -} - -#[cfg(test)] -#[ctor::dtor] -fn teardown() { - println!("teardown network namespace for tests"); - - // Delete network namespace - exec_cmd(&["ip", "netns", "del", TEST_NETNS]); -} - #[test] fn test_link_show() { let cli_path = get_ip_cli_path();