Skip to content

Commit ee52b2b

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

File tree

2 files changed

+197
-18
lines changed

2 files changed

+197
-18
lines changed

src/ip/link/show.rs

Lines changed: 188 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,16 @@ 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+
let mut info_kind = String::new();
228+
for nla in info {
229+
if let LinkInfo::Kind(t) = nla {
230+
info_kind = t.to_string();
231+
}
232+
}
233+
234+
linkinfo = Some(CliLinkInfoKind { info_kind });
235+
}
192236
_ => {
193237
// println!("Remains {:?}", nl_attr);
194238
}
@@ -199,6 +243,7 @@ impl CliLinkInfoDetails {
199243
promiscuity,
200244
min_mtu,
201245
max_mtu,
246+
linkinfo,
202247
inet6_addr_gen_mode,
203248
num_tx_queues,
204249
num_rx_queues,
@@ -213,10 +258,17 @@ impl std::fmt::Display for CliLinkInfoDetails {
213258
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214259
write!(
215260
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,
261+
" promiscuity {} minmtu {} maxmtu {} ",
262+
self.promiscuity, self.min_mtu, self.max_mtu,
263+
)?;
264+
265+
if let Some(linkinfo) = &self.linkinfo {
266+
write!(f, "{linkinfo}")?;
267+
}
268+
269+
write!(
270+
f,
271+
"addrgenmode {} numtxqueues {} numrxqueues {} gso_max_size {} gso_max_segs {} {}",
220272
self.inet6_addr_gen_mode,
221273
self.num_tx_queues,
222274
self.num_rx_queues,
@@ -231,6 +283,10 @@ impl std::fmt::Display for CliLinkInfoDetails {
231283
#[derive(Serialize, Default)]
232284
pub(crate) struct CliLinkInfo {
233285
ifindex: u32,
286+
#[serde(skip)]
287+
link: Option<Option<String>>,
288+
#[serde(skip_serializing_if = "Option::is_none")]
289+
link_index: Option<u32>,
234290
ifname: String,
235291
flags: Vec<String>,
236292
mtu: u32,
@@ -249,6 +305,10 @@ pub(crate) struct CliLinkInfo {
249305
address: String,
250306
#[serde(skip_serializing_if = "String::is_empty")]
251307
broadcast: String,
308+
#[serde(skip)]
309+
link_netns: String,
310+
#[serde(skip_serializing_if = "Option::is_none")]
311+
link_netnsid: Option<i32>,
252312
#[serde(skip_serializing_if = "Option::is_none")]
253313
#[serde(flatten)]
254314
details: Option<CliLinkInfoDetails>,
@@ -257,7 +317,18 @@ pub(crate) struct CliLinkInfo {
257317
impl std::fmt::Display for CliLinkInfo {
258318
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259319
write!(f, "{}: ", self.ifindex)?;
260-
write_with_color!(f, CliColor::IfaceName, "{}: ", self.ifname)?;
320+
let link = if let Some(link_name_opt) = &self.link {
321+
let display_name = if let Some(link_name) = link_name_opt {
322+
link_name
323+
} else {
324+
"NONE"
325+
};
326+
format!("@{display_name}")
327+
} else {
328+
String::new()
329+
};
330+
331+
write_with_color!(f, CliColor::IfaceName, "{}{link}: ", self.ifname)?;
261332
write!(
262333
f,
263334
"<{}> mtu {} qdisc {}",
@@ -288,6 +359,10 @@ impl std::fmt::Display for CliLinkInfo {
288359
write_with_color!(f, CliColor::Mac, "{}", self.broadcast)?;
289360
}
290361

362+
if !self.link_netns.is_empty() {
363+
write!(f, " link-netns {}", self.link_netns)?;
364+
}
365+
291366
if let Some(details) = &self.details {
292367
write!(f, "{details}",)?;
293368
}
@@ -323,25 +398,31 @@ pub(crate) async fn handle_show(
323398
let mut ifaces: Vec<CliLinkInfo> = Vec::new();
324399

325400
while let Some(nl_msg) = links.try_next().await? {
326-
ifaces.push(parse_nl_msg_to_iface(nl_msg, include_details)?);
401+
ifaces.push(parse_nl_msg_to_iface(nl_msg, include_details).await?);
327402
}
328403

329-
resolve_controller_name(&mut ifaces);
404+
resolve_controller_and_link_names(&mut ifaces);
405+
resolve_netns_names(&mut ifaces).await?;
330406

331407
Ok(ifaces)
332408
}
333409

334-
pub(crate) fn parse_nl_msg_to_iface(
410+
pub(crate) async fn parse_nl_msg_to_iface(
335411
nl_msg: LinkMessage,
336412
include_details: bool,
337413
) -> Result<CliLinkInfo, CliError> {
338414
let mut ret = CliLinkInfo {
339415
ifindex: nl_msg.header.index,
340416
flags: link_flags_to_string(nl_msg.header.flags),
341-
link_type: nl_msg.header.link_layer_type.to_string().to_lowercase(),
417+
link_type: link_type_to_string(nl_msg.header.link_layer_type),
342418
..Default::default()
343419
};
344420

421+
// // Make sure to show the link doesn't exist if it is required by this type
422+
// if has_down_link(&nl_msg.header.link_layer_type) {
423+
// ret.link = Some(None);
424+
// }
425+
345426
ret.details = include_details.then_some(CliLinkInfoDetails::new_with_type(
346427
nl_msg.header.link_layer_type,
347428
&nl_msg.attributes,
@@ -370,6 +451,8 @@ pub(crate) fn parse_nl_msg_to_iface(
370451
}
371452
LinkAttribute::Mode(v) => ret.linkmode = v.to_string(),
372453
LinkAttribute::Controller(d) => ret.controller_ifindex = Some(d),
454+
LinkAttribute::Link(i) => ret.link_index = Some(i),
455+
LinkAttribute::LinkNetNsId(i) => ret.link_netnsid = Some(i),
373456
_ => {
374457
// println!("Remains {:?}", nl_attr);
375458
}
@@ -379,6 +462,50 @@ pub(crate) fn parse_nl_msg_to_iface(
379462
Ok(ret)
380463
}
381464

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

405531
fn resolve_ip_link_group_name(id: u32) -> String {
@@ -410,7 +536,41 @@ fn resolve_ip_link_group_name(id: u32) -> String {
410536
}
411537
}
412538

413-
fn resolve_controller_name(links: &mut [CliLinkInfo]) {
539+
async fn resolve_netns_names(
540+
links: &mut [CliLinkInfo],
541+
) -> Result<(), CliError> {
542+
let (conn, mut handle, _) = rtnetlink::new_connection().unwrap();
543+
tokio::spawn(conn);
544+
545+
// Read netns names from /run/netns
546+
let netnses = std::fs::read_dir("/run/netns")?;
547+
let mut id_to_name: HashMap<i32, String> = HashMap::new();
548+
for netns in netnses {
549+
let netns = netns?;
550+
let name = netns.file_name().into_string().unwrap_or_default();
551+
let file = std::fs::File::open(netns.path())?;
552+
553+
if let Some(id) =
554+
get_netns_id_from_fd(&mut handle, file.as_raw_fd() as u32).await
555+
{
556+
id_to_name.insert(id, name);
557+
}
558+
}
559+
560+
for link in links.iter_mut() {
561+
if let Some(link_netns_id) = link.link_netnsid {
562+
if let Some(name) = id_to_name.get(&link_netns_id) {
563+
link.link_netns = name.to_string();
564+
} else {
565+
link.link_netns = format!("ns{link_netns_id}");
566+
}
567+
}
568+
}
569+
570+
Ok(())
571+
}
572+
573+
fn resolve_controller_and_link_names(links: &mut [CliLinkInfo]) {
414574
let index_2_name: HashMap<u32, String> = links
415575
.iter()
416576
.map(|l| (l.ifindex, l.ifname.to_string()))
@@ -422,5 +582,17 @@ fn resolve_controller_name(links: &mut [CliLinkInfo]) {
422582
{
423583
link.controller = Some(name.to_string());
424584
}
585+
if let Some(link_ifindex) = link.link_index {
586+
if link_ifindex == 0 {
587+
link.link = Some(None);
588+
continue;
589+
}
590+
591+
if let Some(name) = index_2_name.get(&link_ifindex) {
592+
link.link = Some(Some(name.to_string()));
593+
} else {
594+
link.link = Some(Some(format!("if{link_ifindex}")));
595+
}
596+
}
425597
}
426598
}

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)