Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ carbide-version = { path = "../version" }
tonic-prost-build = { workspace = true }

[dev-dependencies]
carbide-test-support = { path = "../test-support" }
ctor = { workspace = true }
prost = { workspace = true }
rustls-pemfile = { workspace = true }
Expand Down
4 changes: 3 additions & 1 deletion crates/agent/src/dhcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use std::net::Ipv4Addr;
use std::net::{Ipv4Addr, Ipv6Addr};

use ::rpc::forge as rpc;
use carbide_rpc_utils::dhcp::HostConfig;
Expand Down Expand Up @@ -64,12 +64,14 @@ pub fn build_server_config(
pxe_ip: Ipv4Addr,
ntpservers: Vec<Ipv4Addr>,
nameservers: Vec<Ipv4Addr>,
nameservers_v6: Vec<Ipv6Addr>,
loopback_ip: Ipv4Addr,
) -> Result<String, eyre::Report> {
let dhcp_config = carbide_rpc_utils::dhcp::DhcpConfig::from_forge_dhcp_config(
pxe_ip,
ntpservers,
nameservers,
nameservers_v6,
loopback_ip,
)?;

Expand Down
79 changes: 55 additions & 24 deletions crates/agent/src/ethernet_virtualization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::collections::HashMap;
use std::ffi::CStr;
use std::fs::File;
use std::io::Read;
use std::net::{IpAddr, Ipv4Addr};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
Expand Down Expand Up @@ -138,6 +138,21 @@ pub struct ServiceAddresses {
pub nameservers: Vec<IpAddr>,
}

/// Split a dual-stack nameserver list into its IPv4 and IPv6 members, so the
/// gRPC and file-write DHCP-config paths derive both families the same way.
fn split_nameservers_by_family(nameservers: &[IpAddr]) -> (Vec<Ipv4Addr>, Vec<Ipv6Addr>) {
nameservers
.iter()
.copied()
.fold((Vec::new(), Vec::new()), |(mut v4, mut v6), addr| {
match addr {
IpAddr::V4(v4_addr) => v4.push(v4_addr),
IpAddr::V6(v6_addr) => v6.push(v6_addr),
}
(v4, v6)
})
}

fn build_dhcp_ntp_servers(
nc: &rpc::ManagedHostNetworkConfigResponse,
service_addrs: &ServiceAddresses,
Expand Down Expand Up @@ -1066,14 +1081,7 @@ async fn update_dhcp_via_grpc(
};
let loopback_ip: Ipv4Addr = mh_nc.loopback_ip.parse()?;

let nameservers_v4 = service_addrs
.nameservers
.iter()
.filter_map(|x| match x {
IpAddr::V4(x) => Some(*x),
_ => None,
})
.collect::<Vec<Ipv4Addr>>();
let (nameservers_v4, nameservers_v6) = split_nameservers_by_family(&service_addrs.nameservers);

let ntpservers_v4 = build_dhcp_ntp_servers(network_config, service_addrs);

Expand All @@ -1095,6 +1103,7 @@ async fn update_dhcp_via_grpc(
pxe_ip_v4,
ntpservers_v4,
nameservers_v4,
nameservers_v6,
loopback_ip,
)?;
let mut host_config = carbide_rpc_utils::dhcp::HostConfig::try_from(
Expand Down Expand Up @@ -1471,18 +1480,10 @@ fn write_dhcp_v4_server_config(

let loopback_ip = mh_nc.loopback_ip.parse()?;

// Filter to IPv4, since this is specifically for the DHCPv4 server
// config, and the input ServiceAddresses holds both families.
// Again, we'll eventually have a specific builder for a DHCPv6
// that does similar things with ServiceAddresses, but for IPv6.
let nameservers_v4 = service_addrs
.nameservers
.iter()
.filter_map(|x| match x {
IpAddr::V4(x) => Some(*x),
_ => None,
})
.collect::<Vec<Ipv4Addr>>();
// Split the dual-stack nameservers by family: the IPv4 set drives the
// DHCPv4 options written here, while the IPv6 set is held in the config for
// the eventual DHCPv6 / RA consumer (inert in this path for now).
let (nameservers_v4, nameservers_v6) = split_nameservers_by_family(&service_addrs.nameservers);

let ntpservers_v4 = build_dhcp_ntp_servers(nc, service_addrs);

Expand Down Expand Up @@ -1517,8 +1518,13 @@ fn write_dhcp_v4_server_config(
Err(err) => tracing::error!("Write DHCP server {}: {err:#}", dhcp_server_path.server),
}

let next_contents =
dhcp::build_server_config(pxe_ip_v4, ntpservers_v4, nameservers_v4, loopback_ip)?;
let next_contents = dhcp::build_server_config(
pxe_ip_v4,
ntpservers_v4,
nameservers_v4,
nameservers_v6,
loopback_ip,
)?;
match write(
next_contents,
&dhcp_server_path.config,
Expand Down Expand Up @@ -1923,7 +1929,7 @@ impl InterfaceTranslationMode {
mod tests {
use std::fs;
use std::io::Write;
use std::net::{IpAddr, Ipv4Addr};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::path::{Path, PathBuf};
use std::str::FromStr;

Expand Down Expand Up @@ -3305,6 +3311,31 @@ mod tests {
Ok(())
}

#[test]
fn split_nameservers_by_family_partitions_by_family() {
use carbide_test_support::value_scenarios;

value_scenarios!(
run = |input: Vec<IpAddr>| -> (Vec<Ipv4Addr>, Vec<Ipv6Addr>) {
split_nameservers_by_family(&input)
};
"splits nameservers by family" {
vec![
IpAddr::from([10, 0, 0, 1]),
"2001:db8::1".parse::<IpAddr>().unwrap(),
IpAddr::from([10, 0, 0, 2]),
] => (
vec![Ipv4Addr::new(10, 0, 0, 1), Ipv4Addr::new(10, 0, 0, 2)],
vec!["2001:db8::1".parse::<Ipv6Addr>().unwrap()],
),
vec![IpAddr::from([10, 0, 0, 1])] => (vec![Ipv4Addr::new(10, 0, 0, 1)], vec![]),
vec!["2001:db8::1".parse::<IpAddr>().unwrap()]
=> (vec![], vec!["2001:db8::1".parse::<Ipv6Addr>().unwrap()]),
vec![] => (vec![], vec![]),
}
);
}

fn validate_dhcp_config(received: DhcpConfig, expected: DhcpConfig) {
assert_eq!(received.lease_time_secs, expected.lease_time_secs);
assert_eq!(received.renewal_time_secs, expected.renewal_time_secs);
Expand Down
10 changes: 9 additions & 1 deletion crates/rpc-utils/src/dhcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,12 @@ impl DhcpConfig {
carbide_provisioning_server_ipv4: Ipv4Addr,
carbide_ntpservers: Vec<Ipv4Addr>,
carbide_nameservers: Vec<Ipv4Addr>,
carbide_nameservers_v6: Vec<Ipv6Addr>,
loopback_ip: Ipv4Addr,
) -> Result<Self, DhcpDataError> {
Ok(DhcpConfig {
carbide_nameservers,
carbide_nameservers_v6,
carbide_ntpservers,
carbide_provisioning_server_ipv4,
carbide_dhcp_server: loopback_ip,
Expand Down Expand Up @@ -344,6 +346,7 @@ mod tests {
dhcp_server: Ipv4Addr,
ntpservers: Vec<Ipv4Addr>,
nameservers: Vec<Ipv4Addr>,
nameservers_v6: Vec<Ipv6Addr>,
lease_time_secs: u32,
}

Expand Down Expand Up @@ -434,24 +437,27 @@ mod tests {
}

fn summarize_dhcp_config(
(provisioning_server, ntpservers, nameservers, dhcp_server): (
(provisioning_server, ntpservers, nameservers, nameservers_v6, dhcp_server): (
Ipv4Addr,
Vec<Ipv4Addr>,
Vec<Ipv4Addr>,
Vec<Ipv6Addr>,
Ipv4Addr,
),
) -> Result<DhcpConfigSummary, &'static str> {
DhcpConfig::from_forge_dhcp_config(
provisioning_server,
ntpservers,
nameservers,
nameservers_v6,
dhcp_server,
)
.map(|config| DhcpConfigSummary {
provisioning_server: config.carbide_provisioning_server_ipv4,
dhcp_server: config.carbide_dhcp_server,
ntpservers: config.carbide_ntpservers,
nameservers: config.carbide_nameservers,
nameservers_v6: config.carbide_nameservers_v6,
lease_time_secs: config.lease_time_secs,
})
.map_err(dhcp_error_kind)
Expand Down Expand Up @@ -484,12 +490,14 @@ mod tests {
Ipv4Addr::new(192, 0, 2, 10),
vec![Ipv4Addr::new(192, 0, 2, 20)],
vec![Ipv4Addr::new(192, 0, 2, 53)],
vec!["2001:db8::53".parse::<Ipv6Addr>().unwrap()],
Ipv4Addr::new(127, 0, 0, 2),
) => Yields(DhcpConfigSummary {
provisioning_server: Ipv4Addr::new(192, 0, 2, 10),
dhcp_server: Ipv4Addr::new(127, 0, 0, 2),
ntpservers: vec![Ipv4Addr::new(192, 0, 2, 20)],
nameservers: vec![Ipv4Addr::new(192, 0, 2, 53)],
nameservers_v6: vec!["2001:db8::53".parse::<Ipv6Addr>().unwrap()],
lease_time_secs: DEFAULT_LEASE_TIME_SECS,
}),
}
Expand Down
Loading