Skip to content

Commit cdf6443

Browse files
author
Your Name
committed
ip-link: Support showing link-netns and down-link
1 parent 202ca2d commit cdf6443

File tree

2 files changed

+203
-18
lines changed

2 files changed

+203
-18
lines changed

src/ip/link/show.rs

Lines changed: 194 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
// SPDX-License-Identifier: MIT
22

3+
use std::os::fd::AsRawFd;
34
use std::{collections::HashMap, ffi::CStr};
45

6+
use futures_util::stream::StreamExt;
57
use futures_util::stream::TryStreamExt;
8+
use rtnetlink::packet_route::link::LinkInfo;
69
use rtnetlink::{
710
packet_route::link::{
811
AfSpecInet6, AfSpecUnspec, LinkAttribute, LinkLayerType, LinkMessage,
@@ -21,9 +24,14 @@ use iproute_rs::{
2124
enum CliLinkTypeDetails {
2225
Loopback,
2326
Ether {
27+
#[serde(skip_serializing_if = "String::is_empty")]
2428
parentbus: String,
29+
#[serde(skip_serializing_if = "String::is_empty")]
2530
parentdev: String,
2631
},
32+
Ipgre,
33+
Tunnel,
34+
Sit,
2735
}
2836

2937
impl CliLinkTypeDetails {
@@ -61,16 +69,16 @@ impl CliLinkTypeDetails {
6169
LinkLayerType::Ddcmp => todo!(),
6270
LinkLayerType::Rawhdlc => todo!(),
6371
LinkLayerType::Rawip => todo!(),
64-
LinkLayerType::Tunnel => todo!(),
72+
LinkLayerType::Tunnel => CliLinkTypeDetails::Tunnel,
6573
LinkLayerType::Tunnel6 => todo!(),
6674
LinkLayerType::Frad => todo!(),
6775
LinkLayerType::Skip => todo!(),
6876
LinkLayerType::Localtlk => todo!(),
6977
LinkLayerType::Fddi => todo!(),
7078
LinkLayerType::Bif => todo!(),
71-
LinkLayerType::Sit => todo!(),
79+
LinkLayerType::Sit => CliLinkTypeDetails::Sit,
7280
LinkLayerType::Ipddp => todo!(),
73-
LinkLayerType::Ipgre => todo!(),
81+
LinkLayerType::Ipgre => CliLinkTypeDetails::Ipgre,
7482
LinkLayerType::Pimreg => todo!(),
7583
LinkLayerType::Hippi => todo!(),
7684
LinkLayerType::Ash => todo!(),
@@ -139,18 +147,43 @@ impl std::fmt::Display for CliLinkTypeDetails {
139147
CliLinkTypeDetails::Ether {
140148
parentbus,
141149
parentdev,
142-
} => write!(f, "parentbus {parentbus} parentdev {parentdev} ")?,
150+
} => {
151+
if !parentbus.is_empty() {
152+
write!(f, "parentbus {parentbus} ")?
153+
}
154+
if !parentdev.is_empty() {
155+
write!(f, "parentdev {parentdev} ")?
156+
}
157+
}
158+
CliLinkTypeDetails::Ipgre => (),
159+
CliLinkTypeDetails::Tunnel => (),
160+
CliLinkTypeDetails::Sit => (),
143161
}
144162

145163
Ok(())
146164
}
147165
}
148166

167+
#[derive(Serialize)]
168+
pub(crate) struct CliLinkInfoKind {
169+
info_kind: String,
170+
}
171+
172+
impl std::fmt::Display for CliLinkInfoKind {
173+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174+
write!(f, "\n ")?;
175+
write!(f, "{} ", self.info_kind)?;
176+
Ok(())
177+
}
178+
}
179+
149180
#[derive(Serialize)]
150181
pub(crate) struct CliLinkInfoDetails {
151182
promiscuity: u32,
152183
min_mtu: u32,
153184
max_mtu: u32,
185+
#[serde(skip_serializing_if = "Option::is_none")]
186+
linkinfo: Option<CliLinkInfoKind>,
154187
#[serde(skip_serializing_if = "String::is_empty")]
155188
inet6_addr_gen_mode: String,
156189
num_tx_queues: u32,
@@ -171,6 +204,7 @@ impl CliLinkInfoDetails {
171204
let mut promiscuity = 0;
172205
let mut min_mtu = 0;
173206
let mut max_mtu = 0;
207+
let mut linkinfo = None;
174208
let mut num_tx_queues = 0;
175209
let mut num_rx_queues = 0;
176210
let mut gso_max_size = 0;
@@ -189,6 +223,17 @@ impl CliLinkInfoDetails {
189223
LinkAttribute::NumRxQueues(n) => num_rx_queues = *n,
190224
LinkAttribute::GsoMaxSize(g) => gso_max_size = *g,
191225
LinkAttribute::GsoMaxSegs(g) => gso_max_segs = *g,
226+
LinkAttribute::LinkInfo(info) => {
227+
// println!("LinkInfo: {:?}", info);
228+
let mut info_kind = String::new();
229+
for nla in info {
230+
if let LinkInfo::Kind(t) = nla {
231+
info_kind = t.to_string();
232+
}
233+
}
234+
235+
linkinfo = Some(CliLinkInfoKind { info_kind });
236+
}
192237
_ => {
193238
// println!("Remains {:?}", nl_attr);
194239
}
@@ -199,6 +244,7 @@ impl CliLinkInfoDetails {
199244
promiscuity,
200245
min_mtu,
201246
max_mtu,
247+
linkinfo,
202248
inet6_addr_gen_mode,
203249
num_tx_queues,
204250
num_rx_queues,
@@ -213,10 +259,17 @@ impl std::fmt::Display for CliLinkInfoDetails {
213259
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214260
write!(
215261
f,
216-
" promiscuity {} minmtu {} maxmtu {} addrgenmode {} numtxqueues {} numrxqueues {} gso_max_size {} gso_max_segs {} {}",
217-
self.promiscuity,
218-
self.min_mtu,
219-
self.max_mtu,
262+
" promiscuity {} minmtu {} maxmtu {} ",
263+
self.promiscuity, self.min_mtu, self.max_mtu,
264+
)?;
265+
266+
if let Some(linkinfo) = &self.linkinfo {
267+
write!(f, "{linkinfo}")?;
268+
}
269+
270+
write!(
271+
f,
272+
"addrgenmode {} numtxqueues {} numrxqueues {} gso_max_size {} gso_max_segs {} {}",
220273
self.inet6_addr_gen_mode,
221274
self.num_tx_queues,
222275
self.num_rx_queues,
@@ -231,6 +284,10 @@ impl std::fmt::Display for CliLinkInfoDetails {
231284
#[derive(Serialize, Default)]
232285
pub(crate) struct CliLinkInfo {
233286
ifindex: u32,
287+
#[serde(skip_serializing_if = "Option::is_none")]
288+
link: Option<String>,
289+
#[serde(skip_serializing_if = "Option::is_none")]
290+
link_index: Option<u32>,
234291
ifname: String,
235292
flags: Vec<String>,
236293
mtu: u32,
@@ -249,6 +306,10 @@ pub(crate) struct CliLinkInfo {
249306
address: String,
250307
#[serde(skip_serializing_if = "String::is_empty")]
251308
broadcast: String,
309+
#[serde(skip)]
310+
link_netns: String,
311+
#[serde(skip_serializing_if = "Option::is_none")]
312+
link_netnsid: Option<i32>,
252313
#[serde(skip_serializing_if = "Option::is_none")]
253314
#[serde(flatten)]
254315
details: Option<CliLinkInfoDetails>,
@@ -257,7 +318,20 @@ pub(crate) struct CliLinkInfo {
257318
impl std::fmt::Display for CliLinkInfo {
258319
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259320
write!(f, "{}: ", self.ifindex)?;
260-
write_with_color!(f, CliColor::IfaceName, "{}: ", self.ifname)?;
321+
let link = if self.link_index.is_some() || self.link.is_some() {
322+
let display_name = if let Some(link_name) = &self.link {
323+
link_name
324+
} else if let Some(link_index) = self.link_index {
325+
&format!("if{link_index}")
326+
} else {
327+
"NONE"
328+
};
329+
format!("@{display_name}")
330+
} else {
331+
String::new()
332+
};
333+
334+
write_with_color!(f, CliColor::IfaceName, "{}{link}: ", self.ifname)?;
261335
write!(
262336
f,
263337
"<{}> mtu {} qdisc {}",
@@ -288,6 +362,12 @@ impl std::fmt::Display for CliLinkInfo {
288362
write_with_color!(f, CliColor::Mac, "{}", self.broadcast)?;
289363
}
290364

365+
if !self.link_netns.is_empty() {
366+
write!(f, " link-netns {}", self.link_netns)?;
367+
} else if let Some(netns_id) = self.link_netnsid {
368+
write!(f, " link-netnsid {netns_id}")?;
369+
}
370+
291371
if let Some(details) = &self.details {
292372
write!(f, "{details}",)?;
293373
}
@@ -323,25 +403,31 @@ pub(crate) async fn handle_show(
323403
let mut ifaces: Vec<CliLinkInfo> = Vec::new();
324404

325405
while let Some(nl_msg) = links.try_next().await? {
326-
ifaces.push(parse_nl_msg_to_iface(nl_msg, include_details)?);
406+
ifaces.push(parse_nl_msg_to_iface(nl_msg, include_details).await?);
327407
}
328408

329-
resolve_controller_name(&mut ifaces);
409+
resolve_controller_and_link_names(&mut ifaces);
410+
resolve_netns_names(&mut ifaces).await?;
330411

331412
Ok(ifaces)
332413
}
333414

334-
pub(crate) fn parse_nl_msg_to_iface(
415+
pub(crate) async fn parse_nl_msg_to_iface(
335416
nl_msg: LinkMessage,
336417
include_details: bool,
337418
) -> Result<CliLinkInfo, CliError> {
338419
let mut ret = CliLinkInfo {
339420
ifindex: nl_msg.header.index,
340421
flags: link_flags_to_string(nl_msg.header.flags),
341-
link_type: nl_msg.header.link_layer_type.to_string().to_lowercase(),
422+
link_type: link_type_to_string(nl_msg.header.link_layer_type),
342423
..Default::default()
343424
};
344425

426+
// // Make sure to show the link doesn't exist if it is required by this type
427+
// if has_down_link(&nl_msg.header.link_layer_type) {
428+
// ret.link = Some(None);
429+
// }
430+
345431
ret.details = include_details.then_some(CliLinkInfoDetails::new_with_type(
346432
nl_msg.header.link_layer_type,
347433
&nl_msg.attributes,
@@ -370,6 +456,8 @@ pub(crate) fn parse_nl_msg_to_iface(
370456
}
371457
LinkAttribute::Mode(v) => ret.linkmode = v.to_string(),
372458
LinkAttribute::Controller(d) => ret.controller_ifindex = Some(d),
459+
LinkAttribute::Link(i) => ret.link_index = Some(i),
460+
LinkAttribute::LinkNetNsId(i) => ret.link_netnsid = Some(i),
373461
_ => {
374462
// println!("Remains {:?}", nl_attr);
375463
}
@@ -379,6 +467,50 @@ pub(crate) fn parse_nl_msg_to_iface(
379467
Ok(ret)
380468
}
381469

470+
fn link_type_to_string(link_type: LinkLayerType) -> String {
471+
match link_type {
472+
LinkLayerType::Ipgre => "gre".to_string(),
473+
LinkLayerType::Tunnel => "ipip".to_string(),
474+
_ => link_type.to_string().to_lowercase(),
475+
}
476+
}
477+
/// Try to resolve a netns id to its name using rtnetlink.
478+
/// If not found, returns the id as a string.
479+
async fn get_netns_id_from_fd(
480+
handle: &mut rtnetlink::Handle,
481+
fd: u32,
482+
) -> Option<i32> {
483+
let mut nsid_msg = rtnetlink::packet_route::nsid::NsidMessage::default();
484+
nsid_msg
485+
.attributes
486+
.push(rtnetlink::packet_route::nsid::NsidAttribute::Fd(fd));
487+
let mut nsid_req = rtnetlink::packet_core::NetlinkMessage::new(
488+
rtnetlink::packet_core::NetlinkHeader::default(),
489+
rtnetlink::packet_core::NetlinkPayload::InnerMessage(
490+
rtnetlink::packet_route::RouteNetlinkMessage::GetNsId(nsid_msg),
491+
),
492+
);
493+
nsid_req.header.flags = rtnetlink::packet_core::NLM_F_REQUEST;
494+
495+
let mut netns = handle.request(nsid_req.clone()).unwrap();
496+
497+
if let Some(msg) = netns.next().await {
498+
let rtnetlink::packet_core::NetlinkPayload::InnerMessage(
499+
rtnetlink::packet_route::RouteNetlinkMessage::NewNsId(payload),
500+
) = msg.payload
501+
else {
502+
return None;
503+
};
504+
for attr in payload.attributes {
505+
if let rtnetlink::packet_route::nsid::NsidAttribute::Id(id) = attr {
506+
return Some(id);
507+
}
508+
}
509+
}
510+
511+
None
512+
}
513+
382514
fn get_addr_gen_mode(af_spec_unspec: &[AfSpecUnspec]) -> String {
383515
af_spec_unspec
384516
.iter()
@@ -397,9 +529,8 @@ fn get_addr_gen_mode(af_spec_unspec: &[AfSpecUnspec]) -> String {
397529
.next()
398530
})
399531
.next()
400-
.copied()
532+
.map(|i| i.to_string())
401533
.unwrap_or_default()
402-
.to_string()
403534
}
404535

405536
fn resolve_ip_link_group_name(id: u32) -> String {
@@ -410,7 +541,39 @@ fn resolve_ip_link_group_name(id: u32) -> String {
410541
}
411542
}
412543

413-
fn resolve_controller_name(links: &mut [CliLinkInfo]) {
544+
async fn resolve_netns_names(
545+
links: &mut [CliLinkInfo],
546+
) -> Result<(), CliError> {
547+
let (conn, mut handle, _) = rtnetlink::new_connection().unwrap();
548+
tokio::spawn(conn);
549+
550+
// Read netns names from /run/netns
551+
let netnses = std::fs::read_dir("/run/netns")?;
552+
let mut id_to_name: HashMap<i32, String> = HashMap::new();
553+
for netns in netnses {
554+
let netns = netns?;
555+
let name = netns.file_name().into_string().unwrap_or_default();
556+
let file = std::fs::File::open(netns.path())?;
557+
558+
if let Some(id) =
559+
get_netns_id_from_fd(&mut handle, file.as_raw_fd() as u32).await
560+
{
561+
id_to_name.insert(id, name);
562+
}
563+
}
564+
565+
for link in links.iter_mut() {
566+
if let Some(link_netns_id) = link.link_netnsid {
567+
if let Some(name) = id_to_name.get(&link_netns_id) {
568+
link.link_netns = name.to_string();
569+
}
570+
}
571+
}
572+
573+
Ok(())
574+
}
575+
576+
fn resolve_controller_and_link_names(links: &mut [CliLinkInfo]) {
414577
let index_2_name: HashMap<u32, String> = links
415578
.iter()
416579
.map(|l| (l.ifindex, l.ifname.to_string()))
@@ -422,5 +585,20 @@ fn resolve_controller_name(links: &mut [CliLinkInfo]) {
422585
{
423586
link.controller = Some(name.to_string());
424587
}
588+
if let Some(link_ifindex) = link.link_index {
589+
if link_ifindex == 0 {
590+
continue;
591+
}
592+
593+
// Only set link name if the link is from the current netns
594+
if let Some(name) = index_2_name.get(&link_ifindex)
595+
&& link.link_netnsid.is_none()
596+
{
597+
link.link = Some(name.to_string());
598+
// Clear link_index if we have a name
599+
// We want to serialize one or the other
600+
link.link_index = None;
601+
}
602+
}
425603
}
426604
}

src/mac.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
use std::fmt::Write;
44

55
pub fn mac_to_string(data: &[u8]) -> String {
6+
let as_ip = data.len() == 4;
7+
let sep = if as_ip { '.' } else { ':' };
68
let mut rt = String::new();
79
for (i, m) in data.iter().enumerate().take(data.len()) {
8-
write!(rt, "{m:02x}").ok();
10+
if as_ip {
11+
write!(rt, "{m}").ok();
12+
} else {
13+
write!(rt, "{m:02x}").ok();
14+
}
15+
916
if i != data.len() - 1 {
10-
rt.push(':');
17+
rt.push(sep);
1118
}
1219
}
1320
rt

0 commit comments

Comments
 (0)