Skip to content

Commit a6f3c26

Browse files
authored
Merge pull request #861 from bgilbert/dhcp
Support DHCP option lookup from NetworkManager
2 parents 71f400a + 105eb25 commit a6f3c26

File tree

8 files changed

+644
-55
lines changed

8 files changed

+644
-55
lines changed

Cargo.lock

Lines changed: 457 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ slog-term = ">= 2.6, < 3"
5252
tempfile = ">= 3.2, < 4"
5353
users = "0.11"
5454
vmw_backdoor = "0.2"
55+
zbus = ">= 2.3, < 4"
5556

5657
[dev-dependencies]
5758
mockito = ">= 0.29, < 0.32"

docs/release-notes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ nav_order: 8
88

99
Major changes:
1010

11+
- Support reading DHCP options from NetworkManager, fixing 30s delay on
12+
Azure, Azure Stack, and CloudStack
1113

1214
Minor changes:
1315

1416
- Add `AWS_AVAILABILITY_ZONE_ID` attribute to AWS
1517
- Add release notes to documentation
1618
- Fix default dependency ordering on all `checkin` services
1719
- Fix failure setting SSH keys on IBM Cloud if none are provided
20+
- Don't ignore network interfaces that appear during DHCP option lookup retry
1821

1922
Packaging changes:
2023

src/providers/cloudstack/network.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ use openssh_keys::PublicKey;
88

99
use crate::providers::MetadataProvider;
1010
use crate::retry;
11-
use crate::util;
12-
13-
const SERVER_ADDRESS: &str = "SERVER_ADDRESS";
11+
use crate::util::DhcpOption;
1412

1513
#[derive(Clone, Debug)]
1614
pub struct CloudstackNetwork {
@@ -38,7 +36,7 @@ impl CloudstackNetwork {
3836
#[cfg(test)]
3937
return Ok(mockito::server_url());
4038
}
41-
let server = util::dns_lease_key_lookup(SERVER_ADDRESS)?;
39+
let server = DhcpOption::DhcpServerId.get_value()?;
4240
let ip = server
4341
.parse::<IpAddr>()
4442
.with_context(|| format!("failed to parse server ip address: {}", server))?;

src/providers/microsoft/azure/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,12 @@ impl Azure {
179179

180180
#[cfg(not(test))]
181181
fn get_fabric_address_from_dhcp() -> Result<IpAddr> {
182-
let v = crate::util::dns_lease_key_lookup("OPTION_245")?;
183-
// value is an 8 digit hex value. convert it to u32 and
184-
// then parse that into an ip. Ipv4Addr::from(u32)
185-
// performs conversion from big-endian
182+
let v = crate::util::DhcpOption::AzureFabricAddress.get_value()?;
183+
// value is an 8 digit hex value, with colons if it came from
184+
// NetworkManager. Convert it to u32 and then parse that into an
185+
// IP. Ipv4Addr::from(u32) performs conversion from big-endian.
186186
slog_scope::trace!("found fabric address in hex - {:?}", v);
187-
let dec = u32::from_str_radix(&v, 16)
187+
let dec = u32::from_str_radix(&v.replace(':', ""), 16)
188188
.with_context(|| format!("failed to convert '{}' from hex", v))?;
189189
Ok(IpAddr::V4(dec.into()))
190190
}

src/providers/microsoft/azurestack/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,12 @@ impl AzureStack {
194194

195195
#[cfg(not(test))]
196196
fn get_fabric_address_from_dhcp() -> Result<IpAddr> {
197-
let v = crate::util::dns_lease_key_lookup("OPTION_245")?;
198-
// value is an 8 digit hex value. convert it to u32 and
199-
// then parse that into an ip. Ipv4Addr::from(u32)
200-
// performs conversion from big-endian
197+
let v = crate::util::DhcpOption::AzureFabricAddress.get_value()?;
198+
// value is an 8 digit hex value, with colons if it came from
199+
// NetworkManager. Convert it to u32 and then parse that into an
200+
// IP. Ipv4Addr::from(u32) performs conversion from big-endian.
201201
slog_scope::trace!("found fabric address in hex - {:?}", v);
202-
let dec = u32::from_str_radix(&v, 16)
202+
let dec = u32::from_str_radix(&v.replace(':', ""), 16)
203203
.with_context(|| format!("failed to convert '{}' from hex", v))?;
204204
Ok(IpAddr::V4(dec.into()))
205205
}

src/util/dhcp.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright 2017 CoreOS, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! DHCP lease option lookup
16+
17+
use anyhow::{anyhow, Context, Result};
18+
use slog_scope::{debug, trace};
19+
use std::collections::HashMap;
20+
use std::fs::File;
21+
use std::path::Path;
22+
use std::time::Duration;
23+
use zbus::{dbus_proxy, zvariant};
24+
25+
use super::key_lookup;
26+
use crate::retry;
27+
28+
pub enum DhcpOption {
29+
DhcpServerId,
30+
// avoid dead code warnings with cfg(test)
31+
#[allow(dead_code)]
32+
AzureFabricAddress,
33+
}
34+
35+
impl DhcpOption {
36+
pub fn get_value(&self) -> Result<String> {
37+
retry::Retry::new()
38+
.initial_backoff(Duration::from_millis(50))
39+
.max_backoff(Duration::from_millis(500))
40+
.max_retries(60)
41+
.retry(|_| {
42+
match self.try_nm() {
43+
Ok(res) => return Ok(res),
44+
Err(e) => trace!("failed querying NetworkManager: {e:#}"),
45+
}
46+
match self.try_networkd() {
47+
Ok(res) => return Ok(res),
48+
Err(e) => trace!("failed querying networkd: {e:#}"),
49+
}
50+
Err(anyhow!("failed to acquire DHCP option"))
51+
})
52+
}
53+
54+
fn try_nm(&self) -> Result<String> {
55+
let key = match *self {
56+
Self::DhcpServerId => "dhcp_server_identifier",
57+
Self::AzureFabricAddress => "private_245",
58+
};
59+
60+
// We set up everything from scratch on every attempt. This isn't
61+
// super-efficient but is simple and clear.
62+
//
63+
// We'd like to set both `property` and `object` attributes on the
64+
// trait methods, but that fails to compile, so we create proxies by
65+
// hand.
66+
67+
// query NM for active connections
68+
let bus = zbus::blocking::Connection::system().context("connecting to D-Bus")?;
69+
let nm = NetworkManagerProxyBlocking::new(&bus).context("creating NetworkManager proxy")?;
70+
let conn_paths = nm
71+
.active_connections()
72+
.context("listing active connections")?;
73+
74+
// walk active connections
75+
for conn_path in conn_paths {
76+
if conn_path == "/" {
77+
continue;
78+
}
79+
trace!("found NetworkManager connection: {conn_path}");
80+
let conn = NMActiveConnectionProxyBlocking::builder(&bus)
81+
.path(conn_path)
82+
.context("setting connection path")?
83+
.build()
84+
.context("creating connection proxy")?;
85+
86+
// get DHCP options
87+
let dhcp_path = conn.dhcp4_config().context("getting DHCP config")?;
88+
if dhcp_path == "/" {
89+
continue;
90+
}
91+
debug!("checking DHCP config: {dhcp_path}");
92+
let dhcp = NMDhcp4ConfigProxyBlocking::builder(&bus)
93+
.path(dhcp_path)
94+
.context("setting DHCP config path")?
95+
.build()
96+
.context("creating DHCP config proxy")?;
97+
let options = dhcp.options().context("getting DHCP options")?;
98+
99+
// check for option
100+
if let Some(value) = options.get(key) {
101+
return value.try_into().context("reading DHCP option as string");
102+
}
103+
}
104+
105+
// not found
106+
Err(anyhow!("failed to acquire DHCP option {key}"))
107+
}
108+
109+
fn try_networkd(&self) -> Result<String> {
110+
let key = match *self {
111+
Self::DhcpServerId => "SERVER_ADDRESS",
112+
Self::AzureFabricAddress => "OPTION_245",
113+
};
114+
115+
let interfaces = pnet_datalink::interfaces();
116+
trace!("interfaces - {:?}", interfaces);
117+
118+
for interface in interfaces {
119+
trace!("looking at interface {:?}", interface);
120+
let lease_path = format!("/run/systemd/netif/leases/{}", interface.index);
121+
let lease_path = Path::new(&lease_path);
122+
if lease_path.exists() {
123+
debug!("found lease file - {:?}", lease_path);
124+
let lease = File::open(lease_path)
125+
.with_context(|| format!("failed to open lease file ({:?})", lease_path))?;
126+
127+
if let Some(v) = key_lookup('=', key, lease)? {
128+
return Ok(v);
129+
}
130+
131+
debug!(
132+
"failed to get value from existing lease file '{:?}'",
133+
lease_path
134+
);
135+
}
136+
}
137+
Err(anyhow!("failed to acquire DHCP option {key}"))
138+
}
139+
}
140+
141+
#[dbus_proxy(
142+
default_service = "org.freedesktop.NetworkManager",
143+
default_path = "/org/freedesktop/NetworkManager",
144+
interface = "org.freedesktop.NetworkManager"
145+
)]
146+
trait NetworkManager {
147+
#[dbus_proxy(property)]
148+
fn active_connections(&self) -> zbus::Result<Vec<zvariant::ObjectPath>>;
149+
}
150+
151+
#[dbus_proxy(
152+
default_service = "org.freedesktop.NetworkManager",
153+
interface = "org.freedesktop.NetworkManager.Connection.Active"
154+
)]
155+
trait NMActiveConnection {
156+
#[dbus_proxy(property)]
157+
fn dhcp4_config(&self) -> zbus::Result<zvariant::ObjectPath>;
158+
}
159+
160+
#[dbus_proxy(
161+
default_service = "org.freedesktop.NetworkManager",
162+
interface = "org.freedesktop.NetworkManager.DHCP4Config"
163+
)]
164+
trait NMDhcp4Config {
165+
#[dbus_proxy(property)]
166+
fn options(&self) -> Result<HashMap<String, zvariant::Value>>;
167+
}

src/util/mod.rs

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,15 @@
1414

1515
//! utility functions
1616
17-
use crate::retry;
18-
use anyhow::{anyhow, Context, Result};
19-
use slog_scope::{debug, trace};
20-
use std::fs::File;
17+
use anyhow::Result;
2118
use std::io::{BufRead, BufReader, Read};
22-
use std::path::Path;
23-
use std::time::Duration;
2419

2520
mod cmdline;
2621
pub use self::cmdline::{get_platform, has_network_kargs};
2722

23+
mod dhcp;
24+
pub use self::dhcp::DhcpOption;
25+
2826
mod mount;
2927
pub(crate) use mount::{mount_ro, unmount};
3028

@@ -54,38 +52,6 @@ pub fn key_lookup<R: Read>(delim: char, key: &str, reader: R) -> Result<Option<S
5452
Ok(None)
5553
}
5654

57-
pub fn dns_lease_key_lookup(key: &str) -> Result<String> {
58-
let interfaces = pnet_datalink::interfaces();
59-
trace!("interfaces - {:?}", interfaces);
60-
61-
retry::Retry::new()
62-
.initial_backoff(Duration::from_millis(50))
63-
.max_backoff(Duration::from_millis(500))
64-
.max_retries(60)
65-
.retry(|_| {
66-
for interface in interfaces.clone() {
67-
trace!("looking at interface {:?}", interface);
68-
let lease_path = format!("/run/systemd/netif/leases/{}", interface.index);
69-
let lease_path = Path::new(&lease_path);
70-
if lease_path.exists() {
71-
debug!("found lease file - {:?}", lease_path);
72-
let lease = File::open(lease_path)
73-
.with_context(|| format!("failed to open lease file ({:?})", lease_path))?;
74-
75-
if let Some(v) = key_lookup('=', key, lease)? {
76-
return Ok(v);
77-
}
78-
79-
debug!(
80-
"failed to get value from existing lease file '{:?}'",
81-
lease_path
82-
);
83-
}
84-
}
85-
Err(anyhow!("failed to retrieve fabric address"))
86-
})
87-
}
88-
8955
#[cfg(test)]
9056
mod tests {
9157
use super::*;

0 commit comments

Comments
 (0)