Skip to content

Commit

Permalink
Merge pull request #861 from bgilbert/dhcp
Browse files Browse the repository at this point in the history
Support DHCP option lookup from NetworkManager
  • Loading branch information
bgilbert committed Feb 1, 2023
2 parents 71f400a + 105eb25 commit a6f3c26
Show file tree
Hide file tree
Showing 8 changed files with 644 additions and 55 deletions.
460 changes: 457 additions & 3 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ slog-term = ">= 2.6, < 3"
tempfile = ">= 3.2, < 4"
users = "0.11"
vmw_backdoor = "0.2"
zbus = ">= 2.3, < 4"

[dev-dependencies]
mockito = ">= 0.29, < 0.32"
3 changes: 3 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ nav_order: 8

Major changes:

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

Minor changes:

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

Packaging changes:

Expand Down
6 changes: 2 additions & 4 deletions src/providers/cloudstack/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ use openssh_keys::PublicKey;

use crate::providers::MetadataProvider;
use crate::retry;
use crate::util;

const SERVER_ADDRESS: &str = "SERVER_ADDRESS";
use crate::util::DhcpOption;

#[derive(Clone, Debug)]
pub struct CloudstackNetwork {
Expand Down Expand Up @@ -38,7 +36,7 @@ impl CloudstackNetwork {
#[cfg(test)]
return Ok(mockito::server_url());
}
let server = util::dns_lease_key_lookup(SERVER_ADDRESS)?;
let server = DhcpOption::DhcpServerId.get_value()?;
let ip = server
.parse::<IpAddr>()
.with_context(|| format!("failed to parse server ip address: {}", server))?;
Expand Down
10 changes: 5 additions & 5 deletions src/providers/microsoft/azure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,12 @@ impl Azure {

#[cfg(not(test))]
fn get_fabric_address_from_dhcp() -> Result<IpAddr> {
let v = crate::util::dns_lease_key_lookup("OPTION_245")?;
// value is an 8 digit hex value. convert it to u32 and
// then parse that into an ip. Ipv4Addr::from(u32)
// performs conversion from big-endian
let v = crate::util::DhcpOption::AzureFabricAddress.get_value()?;
// value is an 8 digit hex value, with colons if it came from
// NetworkManager. Convert it to u32 and then parse that into an
// IP. Ipv4Addr::from(u32) performs conversion from big-endian.
slog_scope::trace!("found fabric address in hex - {:?}", v);
let dec = u32::from_str_radix(&v, 16)
let dec = u32::from_str_radix(&v.replace(':', ""), 16)
.with_context(|| format!("failed to convert '{}' from hex", v))?;
Ok(IpAddr::V4(dec.into()))
}
Expand Down
10 changes: 5 additions & 5 deletions src/providers/microsoft/azurestack/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ impl AzureStack {

#[cfg(not(test))]
fn get_fabric_address_from_dhcp() -> Result<IpAddr> {
let v = crate::util::dns_lease_key_lookup("OPTION_245")?;
// value is an 8 digit hex value. convert it to u32 and
// then parse that into an ip. Ipv4Addr::from(u32)
// performs conversion from big-endian
let v = crate::util::DhcpOption::AzureFabricAddress.get_value()?;
// value is an 8 digit hex value, with colons if it came from
// NetworkManager. Convert it to u32 and then parse that into an
// IP. Ipv4Addr::from(u32) performs conversion from big-endian.
slog_scope::trace!("found fabric address in hex - {:?}", v);
let dec = u32::from_str_radix(&v, 16)
let dec = u32::from_str_radix(&v.replace(':', ""), 16)
.with_context(|| format!("failed to convert '{}' from hex", v))?;
Ok(IpAddr::V4(dec.into()))
}
Expand Down
167 changes: 167 additions & 0 deletions src/util/dhcp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! DHCP lease option lookup

use anyhow::{anyhow, Context, Result};
use slog_scope::{debug, trace};
use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
use std::time::Duration;
use zbus::{dbus_proxy, zvariant};

use super::key_lookup;
use crate::retry;

pub enum DhcpOption {
DhcpServerId,
// avoid dead code warnings with cfg(test)
#[allow(dead_code)]
AzureFabricAddress,
}

impl DhcpOption {
pub fn get_value(&self) -> Result<String> {
retry::Retry::new()
.initial_backoff(Duration::from_millis(50))
.max_backoff(Duration::from_millis(500))
.max_retries(60)
.retry(|_| {
match self.try_nm() {
Ok(res) => return Ok(res),
Err(e) => trace!("failed querying NetworkManager: {e:#}"),
}
match self.try_networkd() {
Ok(res) => return Ok(res),
Err(e) => trace!("failed querying networkd: {e:#}"),
}
Err(anyhow!("failed to acquire DHCP option"))
})
}

fn try_nm(&self) -> Result<String> {
let key = match *self {
Self::DhcpServerId => "dhcp_server_identifier",
Self::AzureFabricAddress => "private_245",
};

// We set up everything from scratch on every attempt. This isn't
// super-efficient but is simple and clear.
//
// We'd like to set both `property` and `object` attributes on the
// trait methods, but that fails to compile, so we create proxies by
// hand.

// query NM for active connections
let bus = zbus::blocking::Connection::system().context("connecting to D-Bus")?;
let nm = NetworkManagerProxyBlocking::new(&bus).context("creating NetworkManager proxy")?;
let conn_paths = nm
.active_connections()
.context("listing active connections")?;

// walk active connections
for conn_path in conn_paths {
if conn_path == "/" {
continue;
}
trace!("found NetworkManager connection: {conn_path}");
let conn = NMActiveConnectionProxyBlocking::builder(&bus)
.path(conn_path)
.context("setting connection path")?
.build()
.context("creating connection proxy")?;

// get DHCP options
let dhcp_path = conn.dhcp4_config().context("getting DHCP config")?;
if dhcp_path == "/" {
continue;
}
debug!("checking DHCP config: {dhcp_path}");
let dhcp = NMDhcp4ConfigProxyBlocking::builder(&bus)
.path(dhcp_path)
.context("setting DHCP config path")?
.build()
.context("creating DHCP config proxy")?;
let options = dhcp.options().context("getting DHCP options")?;

// check for option
if let Some(value) = options.get(key) {
return value.try_into().context("reading DHCP option as string");
}
}

// not found
Err(anyhow!("failed to acquire DHCP option {key}"))
}

fn try_networkd(&self) -> Result<String> {
let key = match *self {
Self::DhcpServerId => "SERVER_ADDRESS",
Self::AzureFabricAddress => "OPTION_245",
};

let interfaces = pnet_datalink::interfaces();
trace!("interfaces - {:?}", interfaces);

for interface in interfaces {
trace!("looking at interface {:?}", interface);
let lease_path = format!("/run/systemd/netif/leases/{}", interface.index);
let lease_path = Path::new(&lease_path);
if lease_path.exists() {
debug!("found lease file - {:?}", lease_path);
let lease = File::open(lease_path)
.with_context(|| format!("failed to open lease file ({:?})", lease_path))?;

if let Some(v) = key_lookup('=', key, lease)? {
return Ok(v);
}

debug!(
"failed to get value from existing lease file '{:?}'",
lease_path
);
}
}
Err(anyhow!("failed to acquire DHCP option {key}"))
}
}

#[dbus_proxy(
default_service = "org.freedesktop.NetworkManager",
default_path = "/org/freedesktop/NetworkManager",
interface = "org.freedesktop.NetworkManager"
)]
trait NetworkManager {
#[dbus_proxy(property)]
fn active_connections(&self) -> zbus::Result<Vec<zvariant::ObjectPath>>;
}

#[dbus_proxy(
default_service = "org.freedesktop.NetworkManager",
interface = "org.freedesktop.NetworkManager.Connection.Active"
)]
trait NMActiveConnection {
#[dbus_proxy(property)]
fn dhcp4_config(&self) -> zbus::Result<zvariant::ObjectPath>;
}

#[dbus_proxy(
default_service = "org.freedesktop.NetworkManager",
interface = "org.freedesktop.NetworkManager.DHCP4Config"
)]
trait NMDhcp4Config {
#[dbus_proxy(property)]
fn options(&self) -> Result<HashMap<String, zvariant::Value>>;
}
42 changes: 4 additions & 38 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,15 @@

//! utility functions

use crate::retry;
use anyhow::{anyhow, Context, Result};
use slog_scope::{debug, trace};
use std::fs::File;
use anyhow::Result;
use std::io::{BufRead, BufReader, Read};
use std::path::Path;
use std::time::Duration;

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

mod dhcp;
pub use self::dhcp::DhcpOption;

mod mount;
pub(crate) use mount::{mount_ro, unmount};

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

pub fn dns_lease_key_lookup(key: &str) -> Result<String> {
let interfaces = pnet_datalink::interfaces();
trace!("interfaces - {:?}", interfaces);

retry::Retry::new()
.initial_backoff(Duration::from_millis(50))
.max_backoff(Duration::from_millis(500))
.max_retries(60)
.retry(|_| {
for interface in interfaces.clone() {
trace!("looking at interface {:?}", interface);
let lease_path = format!("/run/systemd/netif/leases/{}", interface.index);
let lease_path = Path::new(&lease_path);
if lease_path.exists() {
debug!("found lease file - {:?}", lease_path);
let lease = File::open(lease_path)
.with_context(|| format!("failed to open lease file ({:?})", lease_path))?;

if let Some(v) = key_lookup('=', key, lease)? {
return Ok(v);
}

debug!(
"failed to get value from existing lease file '{:?}'",
lease_path
);
}
}
Err(anyhow!("failed to retrieve fabric address"))
})
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit a6f3c26

Please sign in to comment.