33use std:: collections:: HashMap ;
44
55use futures_util:: TryStreamExt ;
6+ use indexmap:: IndexMap ;
67use iproute_rs:: { CanDisplay , CanOutput , CliColor , write_with_color} ;
78use rtnetlink:: packet_route:: {
89 AddressFamily ,
@@ -22,8 +23,8 @@ pub(crate) struct CliAddressInfo {
2223 #[ serde( skip_serializing_if = "Option::is_none" ) ]
2324 broadcast : Option < String > ,
2425 scope : String ,
25- #[ serde( skip_serializing_if = "Option::is_none " ) ]
26- tentative : Option < bool > ,
26+ #[ serde( flatten , skip_serializing_if = "IndexMap::is_empty " ) ]
27+ flags : IndexMap < String , bool > ,
2728 #[ serde( skip_serializing_if = "String::is_empty" ) ]
2829 protocol : String ,
2930 #[ serde( skip_serializing_if = "String::is_empty" ) ]
@@ -32,6 +33,68 @@ pub(crate) struct CliAddressInfo {
3233 preferred_life_time : u32 ,
3334}
3435
36+ #[ derive( Clone , Copy ) ]
37+ struct AddressFlagData {
38+ name : & ' static str ,
39+ mask : AddressFlags ,
40+ }
41+
42+ // equal to iproute2 `struct ifa_flag_data_t` in `ipaddress.c`
43+ const ADDRESS_FLAG_DATA : & [ AddressFlagData ] = & [
44+ AddressFlagData {
45+ name : "secondary" ,
46+ mask : AddressFlags :: Secondary ,
47+ } ,
48+ AddressFlagData {
49+ name : "temporary" ,
50+ mask : AddressFlags :: Secondary ,
51+ } ,
52+ AddressFlagData {
53+ name : "nodad" ,
54+ mask : AddressFlags :: Nodad ,
55+ } ,
56+ AddressFlagData {
57+ name : "optimistic" ,
58+ mask : AddressFlags :: Optimistic ,
59+ } ,
60+ AddressFlagData {
61+ name : "dadfailed" ,
62+ mask : AddressFlags :: Dadfailed ,
63+ } ,
64+ AddressFlagData {
65+ name : "home" ,
66+ mask : AddressFlags :: Homeaddress ,
67+ } ,
68+ AddressFlagData {
69+ name : "deprecated" ,
70+ mask : AddressFlags :: Deprecated ,
71+ } ,
72+ AddressFlagData {
73+ name : "tentative" ,
74+ mask : AddressFlags :: Tentative ,
75+ } ,
76+ AddressFlagData {
77+ name : "permanent" ,
78+ mask : AddressFlags :: Permanent ,
79+ } ,
80+ AddressFlagData {
81+ name : "mngtmpaddr" ,
82+ mask : AddressFlags :: Managetempaddr ,
83+ } ,
84+ AddressFlagData {
85+ name : "noprefixroute" ,
86+ mask : AddressFlags :: Noprefixroute ,
87+ } ,
88+ AddressFlagData {
89+ name : "autojoin" ,
90+ mask : AddressFlags :: Mcautojoin ,
91+ } ,
92+ AddressFlagData {
93+ name : "stable-privacy" ,
94+ mask : AddressFlags :: StablePrivacy ,
95+ } ,
96+ ] ;
97+
3598impl std:: fmt:: Display for CliAddressInfo {
3699 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
37100 write ! ( f, "{} " , self . family) ?;
@@ -52,9 +115,7 @@ impl std::fmt::Display for CliAddressInfo {
52115 ) ?;
53116 }
54117 write ! ( f, " scope {} " , self . scope) ?;
55- if Some ( true ) == self . tentative {
56- write ! ( f, "tentative " ) ?;
57- }
118+ self . write_flags ( f) ?;
58119
59120 if !self . protocol . is_empty ( ) {
60121 write ! ( f, "proto {} " , self . protocol) ?;
@@ -68,18 +129,29 @@ impl std::fmt::Display for CliAddressInfo {
68129 if self . valid_life_time == u32 :: MAX {
69130 "forever" . to_string( )
70131 } else {
71- self . valid_life_time. to_string ( )
132+ format! ( "{}sec" , self . valid_life_time)
72133 } ,
73134 if self . preferred_life_time == u32 :: MAX {
74135 "forever" . to_string( )
75136 } else {
76- self . preferred_life_time. to_string ( )
137+ format! ( "{}sec" , self . preferred_life_time)
77138 }
78139 ) ?;
79140 Ok ( ( ) )
80141 }
81142}
82143
144+ impl CliAddressInfo {
145+ fn write_flags ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
146+ for flag_name in self . flags . iter ( ) . filter_map ( |( flag_name, value) | {
147+ if * value { Some ( flag_name) } else { None }
148+ } ) {
149+ write ! ( f, "{} " , flag_name) ?;
150+ }
151+ Ok ( ( ) )
152+ }
153+ }
154+
83155impl CanDisplay for CliAddressInfo {
84156 fn gen_string ( & self ) -> String {
85157 self . to_string ( )
@@ -95,6 +167,39 @@ fn addr_scope_to_cli_string(addr_scope: &AddressScope) -> String {
95167 }
96168}
97169
170+ fn get_address_flags (
171+ family : AddressFamily ,
172+ flags : AddressFlags ,
173+ ) -> IndexMap < String , bool > {
174+ let mut ret = IndexMap :: new ( ) ;
175+ let mut flags = flags;
176+
177+ for flag_data in ADDRESS_FLAG_DATA {
178+ if flag_data. mask == AddressFlags :: Permanent {
179+ if !flags. contains ( flag_data. mask ) {
180+ ret. insert ( "dynamic" . to_string ( ) , true ) ;
181+ }
182+ } else if flags. contains ( flag_data. mask ) {
183+ if flag_data. mask == AddressFlags :: Secondary
184+ && family == AddressFamily :: Inet6
185+ {
186+ ret. insert ( "temporary" . to_string ( ) , true ) ;
187+ } else {
188+ ret. insert ( flag_data. name . to_string ( ) , true ) ;
189+ }
190+ }
191+ flags. remove ( flag_data. mask ) ;
192+ }
193+ // iproute2 shows unknown flags in hex format, to support so
194+ // the IndexMap<String, bool> need to be changed to IndexMap<String, String>
195+ // which is overskill for this unknown flags. Let's just log a debug info
196+ // and wait bug report.
197+ if !flags. is_empty ( ) {
198+ log:: debug!( "Unknown address flags: {:02x}" , flags. bits( ) ) ;
199+ }
200+ ret
201+ }
202+
98203fn parse_nl_msg_to_address (
99204 nl_msg : AddressMessage ,
100205) -> Result < CliAddressInfo , CliError > {
@@ -104,7 +209,8 @@ fn parse_nl_msg_to_address(
104209 let prefixlen = nl_msg. header . prefix_len ;
105210 let mut broadcast = None ;
106211 let scope = addr_scope_to_cli_string ( & nl_msg. header . scope ) ;
107- let mut tentative = None ;
212+ let mut flags =
213+ AddressFlags :: from_bits_retain ( nl_msg. header . flags . bits ( ) . into ( ) ) ;
108214 let mut label = String :: new ( ) ;
109215 let mut valid_life_time = u32:: MAX ;
110216 let mut preferred_life_time = u32:: MAX ;
@@ -129,10 +235,7 @@ fn parse_nl_msg_to_address(
129235 preferred_life_time = c. ifa_preferred ;
130236 }
131237 AddressAttribute :: Flags ( f) => {
132- // If there is no tentative flag the field should be None
133- tentative = ( nl_msg. header . family == AddressFamily :: Inet6
134- && f. contains ( AddressFlags :: Tentative ) )
135- . then_some ( true ) ;
238+ flags = f;
136239 }
137240 AddressAttribute :: Protocol ( p) => {
138241 protocol = p. to_string ( ) ;
@@ -143,19 +246,21 @@ fn parse_nl_msg_to_address(
143246 }
144247 }
145248
146- Ok ( CliAddressInfo {
249+ let cli_addr_info = CliAddressInfo {
147250 index,
148251 family,
149252 local,
150253 prefixlen,
151254 broadcast,
152255 scope,
153- tentative ,
256+ flags : get_address_flags ( nl_msg . header . family , flags ) ,
154257 label,
155258 valid_life_time,
156259 preferred_life_time,
157260 protocol,
158- } )
261+ } ;
262+
263+ Ok ( cli_addr_info)
159264}
160265
161266pub ( crate ) async fn handle_show (
0 commit comments