1
1
// SPDX-License-Identifier: MIT
2
2
3
+ use std:: os:: fd:: AsRawFd ;
3
4
use std:: { collections:: HashMap , ffi:: CStr } ;
4
5
6
+ use futures_util:: stream:: StreamExt ;
5
7
use futures_util:: stream:: TryStreamExt ;
8
+ use rtnetlink:: packet_route:: link:: LinkInfo ;
6
9
use rtnetlink:: {
7
10
packet_route:: link:: {
8
11
AfSpecInet6 , AfSpecUnspec , LinkAttribute , LinkLayerType , LinkMessage ,
@@ -21,9 +24,14 @@ use iproute_rs::{
21
24
enum CliLinkTypeDetails {
22
25
Loopback ,
23
26
Ether {
27
+ #[ serde( skip_serializing_if = "String::is_empty" ) ]
24
28
parentbus : String ,
29
+ #[ serde( skip_serializing_if = "String::is_empty" ) ]
25
30
parentdev : String ,
26
31
} ,
32
+ Ipgre ,
33
+ Tunnel ,
34
+ Sit ,
27
35
}
28
36
29
37
impl CliLinkTypeDetails {
@@ -61,16 +69,16 @@ impl CliLinkTypeDetails {
61
69
LinkLayerType :: Ddcmp => todo ! ( ) ,
62
70
LinkLayerType :: Rawhdlc => todo ! ( ) ,
63
71
LinkLayerType :: Rawip => todo ! ( ) ,
64
- LinkLayerType :: Tunnel => todo ! ( ) ,
72
+ LinkLayerType :: Tunnel => CliLinkTypeDetails :: Tunnel ,
65
73
LinkLayerType :: Tunnel6 => todo ! ( ) ,
66
74
LinkLayerType :: Frad => todo ! ( ) ,
67
75
LinkLayerType :: Skip => todo ! ( ) ,
68
76
LinkLayerType :: Localtlk => todo ! ( ) ,
69
77
LinkLayerType :: Fddi => todo ! ( ) ,
70
78
LinkLayerType :: Bif => todo ! ( ) ,
71
- LinkLayerType :: Sit => todo ! ( ) ,
79
+ LinkLayerType :: Sit => CliLinkTypeDetails :: Sit ,
72
80
LinkLayerType :: Ipddp => todo ! ( ) ,
73
- LinkLayerType :: Ipgre => todo ! ( ) ,
81
+ LinkLayerType :: Ipgre => CliLinkTypeDetails :: Ipgre ,
74
82
LinkLayerType :: Pimreg => todo ! ( ) ,
75
83
LinkLayerType :: Hippi => todo ! ( ) ,
76
84
LinkLayerType :: Ash => todo ! ( ) ,
@@ -139,18 +147,43 @@ impl std::fmt::Display for CliLinkTypeDetails {
139
147
CliLinkTypeDetails :: Ether {
140
148
parentbus,
141
149
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 => ( ) ,
143
161
}
144
162
145
163
Ok ( ( ) )
146
164
}
147
165
}
148
166
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
+
149
180
#[ derive( Serialize ) ]
150
181
pub ( crate ) struct CliLinkInfoDetails {
151
182
promiscuity : u32 ,
152
183
min_mtu : u32 ,
153
184
max_mtu : u32 ,
185
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
186
+ linkinfo : Option < CliLinkInfoKind > ,
154
187
#[ serde( skip_serializing_if = "String::is_empty" ) ]
155
188
inet6_addr_gen_mode : String ,
156
189
num_tx_queues : u32 ,
@@ -171,6 +204,7 @@ impl CliLinkInfoDetails {
171
204
let mut promiscuity = 0 ;
172
205
let mut min_mtu = 0 ;
173
206
let mut max_mtu = 0 ;
207
+ let mut linkinfo = None ;
174
208
let mut num_tx_queues = 0 ;
175
209
let mut num_rx_queues = 0 ;
176
210
let mut gso_max_size = 0 ;
@@ -189,6 +223,17 @@ impl CliLinkInfoDetails {
189
223
LinkAttribute :: NumRxQueues ( n) => num_rx_queues = * n,
190
224
LinkAttribute :: GsoMaxSize ( g) => gso_max_size = * g,
191
225
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
+ }
192
237
_ => {
193
238
// println!("Remains {:?}", nl_attr);
194
239
}
@@ -199,6 +244,7 @@ impl CliLinkInfoDetails {
199
244
promiscuity,
200
245
min_mtu,
201
246
max_mtu,
247
+ linkinfo,
202
248
inet6_addr_gen_mode,
203
249
num_tx_queues,
204
250
num_rx_queues,
@@ -213,10 +259,17 @@ impl std::fmt::Display for CliLinkInfoDetails {
213
259
fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
214
260
write ! (
215
261
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 {} {}" ,
220
273
self . inet6_addr_gen_mode,
221
274
self . num_tx_queues,
222
275
self . num_rx_queues,
@@ -231,6 +284,10 @@ impl std::fmt::Display for CliLinkInfoDetails {
231
284
#[ derive( Serialize , Default ) ]
232
285
pub ( crate ) struct CliLinkInfo {
233
286
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 > ,
234
291
ifname : String ,
235
292
flags : Vec < String > ,
236
293
mtu : u32 ,
@@ -249,6 +306,10 @@ pub(crate) struct CliLinkInfo {
249
306
address : String ,
250
307
#[ serde( skip_serializing_if = "String::is_empty" ) ]
251
308
broadcast : String ,
309
+ #[ serde( skip) ]
310
+ link_netns : String ,
311
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
312
+ link_netnsid : Option < i32 > ,
252
313
#[ serde( skip_serializing_if = "Option::is_none" ) ]
253
314
#[ serde( flatten) ]
254
315
details : Option < CliLinkInfoDetails > ,
@@ -257,7 +318,20 @@ pub(crate) struct CliLinkInfo {
257
318
impl std:: fmt:: Display for CliLinkInfo {
258
319
fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
259
320
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) ?;
261
335
write ! (
262
336
f,
263
337
"<{}> mtu {} qdisc {}" ,
@@ -288,6 +362,12 @@ impl std::fmt::Display for CliLinkInfo {
288
362
write_with_color ! ( f, CliColor :: Mac , "{}" , self . broadcast) ?;
289
363
}
290
364
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
+
291
371
if let Some ( details) = & self . details {
292
372
write ! ( f, "{details}" , ) ?;
293
373
}
@@ -323,25 +403,31 @@ pub(crate) async fn handle_show(
323
403
let mut ifaces: Vec < CliLinkInfo > = Vec :: new ( ) ;
324
404
325
405
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 ?) ;
327
407
}
328
408
329
- resolve_controller_name ( & mut ifaces) ;
409
+ resolve_controller_and_link_names ( & mut ifaces) ;
410
+ resolve_netns_names ( & mut ifaces) . await ?;
330
411
331
412
Ok ( ifaces)
332
413
}
333
414
334
- pub ( crate ) fn parse_nl_msg_to_iface (
415
+ pub ( crate ) async fn parse_nl_msg_to_iface (
335
416
nl_msg : LinkMessage ,
336
417
include_details : bool ,
337
418
) -> Result < CliLinkInfo , CliError > {
338
419
let mut ret = CliLinkInfo {
339
420
ifindex : nl_msg. header . index ,
340
421
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 ) ,
342
423
..Default :: default ( )
343
424
} ;
344
425
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
+
345
431
ret. details = include_details. then_some ( CliLinkInfoDetails :: new_with_type (
346
432
nl_msg. header . link_layer_type ,
347
433
& nl_msg. attributes ,
@@ -370,6 +456,8 @@ pub(crate) fn parse_nl_msg_to_iface(
370
456
}
371
457
LinkAttribute :: Mode ( v) => ret. linkmode = v. to_string ( ) ,
372
458
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) ,
373
461
_ => {
374
462
// println!("Remains {:?}", nl_attr);
375
463
}
@@ -379,6 +467,50 @@ pub(crate) fn parse_nl_msg_to_iface(
379
467
Ok ( ret)
380
468
}
381
469
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
+
382
514
fn get_addr_gen_mode ( af_spec_unspec : & [ AfSpecUnspec ] ) -> String {
383
515
af_spec_unspec
384
516
. iter ( )
@@ -397,9 +529,8 @@ fn get_addr_gen_mode(af_spec_unspec: &[AfSpecUnspec]) -> String {
397
529
. next ( )
398
530
} )
399
531
. next ( )
400
- . copied ( )
532
+ . map ( |i| i . to_string ( ) )
401
533
. unwrap_or_default ( )
402
- . to_string ( )
403
534
}
404
535
405
536
fn resolve_ip_link_group_name ( id : u32 ) -> String {
@@ -410,7 +541,39 @@ fn resolve_ip_link_group_name(id: u32) -> String {
410
541
}
411
542
}
412
543
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 ] ) {
414
577
let index_2_name: HashMap < u32 , String > = links
415
578
. iter ( )
416
579
. map ( |l| ( l. ifindex , l. ifname . to_string ( ) ) )
@@ -422,5 +585,20 @@ fn resolve_controller_name(links: &mut [CliLinkInfo]) {
422
585
{
423
586
link. controller = Some ( name. to_string ( ) ) ;
424
587
}
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
+ }
425
603
}
426
604
}
0 commit comments