Skip to content

Commit 21f5d8c

Browse files
committed
address: Support dynamic IPv6 address and its flags
* Fix valid_lft and preferred_lft of dynamic(kernel_ra) IP address * Complete rewrite on ip address flag processing Unit test case expanded for this. Signed-off-by: Gris Ge <cnfourt@gmail.com>
1 parent e6e3f24 commit 21f5d8c

4 files changed

Lines changed: 158 additions & 18 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ path = "src/ip/main.rs"
2020
[dependencies]
2121
clap = { version = "4.5.40", features = ["cargo"] }
2222
futures-util = "0.3.31"
23+
indexmap = { version = "2.14.0", features = ["serde"] }
24+
log = { version = "0.4.29", features = ["std"] }
2325
rtnetlink = { git = "https://github.com/rust-netlink/rtnetlink" }
2426
serde = { version = "1.0", default-features = false, features = ["derive"] }
2527
serde_json = "1.0.140"

src/ip/address/cli.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
// SPDX-License-Identifier: MIT
22

3-
use iproute_rs::CliError;
4-
53
use super::show::handle_show;
6-
use crate::link::CliLinkInfo;
4+
use crate::{CliError, link::CliLinkInfo};
75

86
pub(crate) struct AddressCommand;
97

src/ip/address/show.rs

Lines changed: 120 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::collections::HashMap;
44

55
use futures_util::TryStreamExt;
6+
use indexmap::IndexMap;
67
use iproute_rs::{CanDisplay, CanOutput, CliColor, write_with_color};
78
use 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+
3598
impl 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+
83155
impl 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+
98203
fn 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

161266
pub(crate) async fn handle_show(

src/ip/address/tests/address.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,42 @@ where
8282
exec_cmd(&["ip", "link", "set", dummy_name, "up"]);
8383

8484
exec_cmd(&["ip", "addr", "add", "192.168.1.1/24", "dev", dummy_name]);
85+
exec_cmd(&["ip", "addr", "add", "192.168.1.2/24", "dev", dummy_name]);
8586
exec_cmd(&["ip", "addr", "add", "ff::ab:cd/64", "dev", dummy_name]);
87+
exec_cmd(&[
88+
"ip",
89+
"addr",
90+
"add",
91+
"2001:db8:beef::1/64",
92+
"dev",
93+
dummy_name,
94+
"valid_lft",
95+
"21384",
96+
"preferred_lft",
97+
"21384",
98+
"scope",
99+
"global",
100+
"mngtmpaddr",
101+
"proto",
102+
"kernel_ra",
103+
]);
104+
exec_cmd(&[
105+
"ip",
106+
"addr",
107+
"add",
108+
"2001:db8:beef::2/64",
109+
"dev",
110+
dummy_name,
111+
"valid_lft",
112+
"21381",
113+
"preferred_lft",
114+
"21381",
115+
"scope",
116+
"global",
117+
"home",
118+
"proto",
119+
"kernel_ra",
120+
]);
86121

87122
// Wait 2 seconds for interface to be up and addresses to be assigned
88123
std::thread::sleep(std::time::Duration::from_secs(2));

0 commit comments

Comments
 (0)