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,16 @@ 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
+ 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
+ }
192
236
_ => {
193
237
// println!("Remains {:?}", nl_attr);
194
238
}
@@ -199,6 +243,7 @@ impl CliLinkInfoDetails {
199
243
promiscuity,
200
244
min_mtu,
201
245
max_mtu,
246
+ linkinfo,
202
247
inet6_addr_gen_mode,
203
248
num_tx_queues,
204
249
num_rx_queues,
@@ -213,10 +258,17 @@ impl std::fmt::Display for CliLinkInfoDetails {
213
258
fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
214
259
write ! (
215
260
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 {} {}" ,
220
272
self . inet6_addr_gen_mode,
221
273
self . num_tx_queues,
222
274
self . num_rx_queues,
@@ -231,6 +283,10 @@ impl std::fmt::Display for CliLinkInfoDetails {
231
283
#[ derive( Serialize , Default ) ]
232
284
pub ( crate ) struct CliLinkInfo {
233
285
ifindex : u32 ,
286
+ #[ serde( skip) ]
287
+ link : Option < Option < String > > ,
288
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
289
+ link_index : Option < u32 > ,
234
290
ifname : String ,
235
291
flags : Vec < String > ,
236
292
mtu : u32 ,
@@ -249,6 +305,10 @@ pub(crate) struct CliLinkInfo {
249
305
address : String ,
250
306
#[ serde( skip_serializing_if = "String::is_empty" ) ]
251
307
broadcast : String ,
308
+ #[ serde( skip) ]
309
+ link_netns : String ,
310
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
311
+ link_netnsid : Option < i32 > ,
252
312
#[ serde( skip_serializing_if = "Option::is_none" ) ]
253
313
#[ serde( flatten) ]
254
314
details : Option < CliLinkInfoDetails > ,
@@ -257,7 +317,18 @@ pub(crate) struct CliLinkInfo {
257
317
impl std:: fmt:: Display for CliLinkInfo {
258
318
fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
259
319
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) ?;
261
332
write ! (
262
333
f,
263
334
"<{}> mtu {} qdisc {}" ,
@@ -288,6 +359,10 @@ impl std::fmt::Display for CliLinkInfo {
288
359
write_with_color ! ( f, CliColor :: Mac , "{}" , self . broadcast) ?;
289
360
}
290
361
362
+ if !self . link_netns . is_empty ( ) {
363
+ write ! ( f, " link-netns {}" , self . link_netns) ?;
364
+ }
365
+
291
366
if let Some ( details) = & self . details {
292
367
write ! ( f, "{details}" , ) ?;
293
368
}
@@ -323,25 +398,31 @@ pub(crate) async fn handle_show(
323
398
let mut ifaces: Vec < CliLinkInfo > = Vec :: new ( ) ;
324
399
325
400
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 ?) ;
327
402
}
328
403
329
- resolve_controller_name ( & mut ifaces) ;
404
+ resolve_controller_and_link_names ( & mut ifaces) ;
405
+ resolve_netns_names ( & mut ifaces) . await ?;
330
406
331
407
Ok ( ifaces)
332
408
}
333
409
334
- pub ( crate ) fn parse_nl_msg_to_iface (
410
+ pub ( crate ) async fn parse_nl_msg_to_iface (
335
411
nl_msg : LinkMessage ,
336
412
include_details : bool ,
337
413
) -> Result < CliLinkInfo , CliError > {
338
414
let mut ret = CliLinkInfo {
339
415
ifindex : nl_msg. header . index ,
340
416
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 ) ,
342
418
..Default :: default ( )
343
419
} ;
344
420
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
+
345
426
ret. details = include_details. then_some ( CliLinkInfoDetails :: new_with_type (
346
427
nl_msg. header . link_layer_type ,
347
428
& nl_msg. attributes ,
@@ -370,6 +451,8 @@ pub(crate) fn parse_nl_msg_to_iface(
370
451
}
371
452
LinkAttribute :: Mode ( v) => ret. linkmode = v. to_string ( ) ,
372
453
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) ,
373
456
_ => {
374
457
// println!("Remains {:?}", nl_attr);
375
458
}
@@ -379,6 +462,50 @@ pub(crate) fn parse_nl_msg_to_iface(
379
462
Ok ( ret)
380
463
}
381
464
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
+
382
509
fn get_addr_gen_mode ( af_spec_unspec : & [ AfSpecUnspec ] ) -> String {
383
510
af_spec_unspec
384
511
. iter ( )
@@ -397,9 +524,8 @@ fn get_addr_gen_mode(af_spec_unspec: &[AfSpecUnspec]) -> String {
397
524
. next ( )
398
525
} )
399
526
. next ( )
400
- . copied ( )
527
+ . map ( |i| i . to_string ( ) )
401
528
. unwrap_or_default ( )
402
- . to_string ( )
403
529
}
404
530
405
531
fn resolve_ip_link_group_name ( id : u32 ) -> String {
@@ -410,7 +536,41 @@ fn resolve_ip_link_group_name(id: u32) -> String {
410
536
}
411
537
}
412
538
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 ] ) {
414
574
let index_2_name: HashMap < u32 , String > = links
415
575
. iter ( )
416
576
. map ( |l| ( l. ifindex , l. ifname . to_string ( ) ) )
@@ -422,5 +582,17 @@ fn resolve_controller_name(links: &mut [CliLinkInfo]) {
422
582
{
423
583
link. controller = Some ( name. to_string ( ) ) ;
424
584
}
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
+ }
425
597
}
426
598
}
0 commit comments