Skip to content

Commit 6eee006

Browse files
authored
feat(dhcp): include the host's IPv6 DNS resolvers in the agent's DHCP config (#2686)
## Summary The dual-stack `DhcpConfig` field `carbide_nameservers_v6` landed in #2672, but nothing fills it — `from_forge_dhcp_config` and the agent's config builders are still IPv4-only. This has the agent populate it from the DPU's `ServiceAddresses` (which already holds both families): - A `split_nameservers_by_family` helper splits the nameservers once, used at both DHCP-config build sites (the gRPC control path and the on-disk config path) so they can't diverge. - `from_forge_dhcp_config` / `build_server_config` thread the IPv6 set through into the config. - The IPv4 DHCPv4 option-6 path is unchanged; the IPv6 set stays inert until a delivery channel (DHCPv6 / RA) consumes it. Tests cover `from_forge_dhcp_config` populating the field and the family split. Implements #2640. Part of the #2628 epic (dual-stack DNS resolver advertisement). > **Reworked onto current main.** The original version stacked on #2685 (Task 1), now closed as superseded by #2672 — which already added the proto/model fields this PR builds on. So this PR is now just the agent-population step. Draft pending review. Signed-off-by: Chet Nichols III <chetn@nvidia.com>
1 parent 813a69c commit 6eee006

3 files changed

Lines changed: 67 additions & 26 deletions

File tree

crates/agent/src/dhcp.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17-
use std::net::Ipv4Addr;
17+
use std::net::{Ipv4Addr, Ipv6Addr};
1818

1919
use ::rpc::forge as rpc;
2020
use carbide_rpc_utils::dhcp::HostConfig;
@@ -64,12 +64,14 @@ pub fn build_server_config(
6464
pxe_ip: Ipv4Addr,
6565
ntpservers: Vec<Ipv4Addr>,
6666
nameservers: Vec<Ipv4Addr>,
67+
nameservers_v6: Vec<Ipv6Addr>,
6768
loopback_ip: Ipv4Addr,
6869
) -> Result<String, eyre::Report> {
6970
let dhcp_config = carbide_rpc_utils::dhcp::DhcpConfig::from_forge_dhcp_config(
7071
pxe_ip,
7172
ntpservers,
7273
nameservers,
74+
nameservers_v6,
7375
loopback_ip,
7476
)?;
7577

crates/agent/src/ethernet_virtualization.rs

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use std::collections::HashMap;
1919
use std::ffi::CStr;
2020
use std::fs::File;
2121
use std::io::Read;
22-
use std::net::{IpAddr, Ipv4Addr};
22+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
2323
use std::path::{Path, PathBuf};
2424
use std::str::FromStr;
2525
use std::time::Duration;
@@ -138,6 +138,21 @@ pub struct ServiceAddresses {
138138
pub nameservers: Vec<IpAddr>,
139139
}
140140

141+
/// Split a dual-stack nameserver list into its IPv4 and IPv6 members, so the
142+
/// gRPC and file-write DHCP-config paths derive both families the same way.
143+
fn split_nameservers_by_family(nameservers: &[IpAddr]) -> (Vec<Ipv4Addr>, Vec<Ipv6Addr>) {
144+
nameservers
145+
.iter()
146+
.copied()
147+
.fold((Vec::new(), Vec::new()), |(mut v4, mut v6), addr| {
148+
match addr {
149+
IpAddr::V4(v4_addr) => v4.push(v4_addr),
150+
IpAddr::V6(v6_addr) => v6.push(v6_addr),
151+
}
152+
(v4, v6)
153+
})
154+
}
155+
141156
fn build_dhcp_ntp_servers(
142157
nc: &rpc::ManagedHostNetworkConfigResponse,
143158
service_addrs: &ServiceAddresses,
@@ -1066,14 +1081,7 @@ async fn update_dhcp_via_grpc(
10661081
};
10671082
let loopback_ip: Ipv4Addr = mh_nc.loopback_ip.parse()?;
10681083

1069-
let nameservers_v4 = service_addrs
1070-
.nameservers
1071-
.iter()
1072-
.filter_map(|x| match x {
1073-
IpAddr::V4(x) => Some(*x),
1074-
_ => None,
1075-
})
1076-
.collect::<Vec<Ipv4Addr>>();
1084+
let (nameservers_v4, nameservers_v6) = split_nameservers_by_family(&service_addrs.nameservers);
10771085

10781086
let ntpservers_v4 = build_dhcp_ntp_servers(network_config, service_addrs);
10791087

@@ -1095,6 +1103,7 @@ async fn update_dhcp_via_grpc(
10951103
pxe_ip_v4,
10961104
ntpservers_v4,
10971105
nameservers_v4,
1106+
nameservers_v6,
10981107
loopback_ip,
10991108
)?;
11001109
let mut host_config = carbide_rpc_utils::dhcp::HostConfig::try_from(
@@ -1471,18 +1480,10 @@ fn write_dhcp_v4_server_config(
14711480

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

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

14871488
let ntpservers_v4 = build_dhcp_ntp_servers(nc, service_addrs);
14881489

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

1520-
let next_contents =
1521-
dhcp::build_server_config(pxe_ip_v4, ntpservers_v4, nameservers_v4, loopback_ip)?;
1521+
let next_contents = dhcp::build_server_config(
1522+
pxe_ip_v4,
1523+
ntpservers_v4,
1524+
nameservers_v4,
1525+
nameservers_v6,
1526+
loopback_ip,
1527+
)?;
15221528
match write(
15231529
next_contents,
15241530
&dhcp_server_path.config,
@@ -1923,7 +1929,7 @@ impl InterfaceTranslationMode {
19231929
mod tests {
19241930
use std::fs;
19251931
use std::io::Write;
1926-
use std::net::{IpAddr, Ipv4Addr};
1932+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
19271933
use std::path::{Path, PathBuf};
19281934
use std::str::FromStr;
19291935

@@ -3305,6 +3311,31 @@ mod tests {
33053311
Ok(())
33063312
}
33073313

3314+
#[test]
3315+
fn split_nameservers_by_family_partitions_by_family() {
3316+
use carbide_test_support::value_scenarios;
3317+
3318+
value_scenarios!(
3319+
run = |input: Vec<IpAddr>| -> (Vec<Ipv4Addr>, Vec<Ipv6Addr>) {
3320+
split_nameservers_by_family(&input)
3321+
};
3322+
"splits nameservers by family" {
3323+
vec![
3324+
IpAddr::from([10, 0, 0, 1]),
3325+
"2001:db8::1".parse::<IpAddr>().unwrap(),
3326+
IpAddr::from([10, 0, 0, 2]),
3327+
] => (
3328+
vec![Ipv4Addr::new(10, 0, 0, 1), Ipv4Addr::new(10, 0, 0, 2)],
3329+
vec!["2001:db8::1".parse::<Ipv6Addr>().unwrap()],
3330+
),
3331+
vec![IpAddr::from([10, 0, 0, 1])] => (vec![Ipv4Addr::new(10, 0, 0, 1)], vec![]),
3332+
vec!["2001:db8::1".parse::<IpAddr>().unwrap()]
3333+
=> (vec![], vec!["2001:db8::1".parse::<Ipv6Addr>().unwrap()]),
3334+
vec![] => (vec![], vec![]),
3335+
}
3336+
);
3337+
}
3338+
33083339
fn validate_dhcp_config(received: DhcpConfig, expected: DhcpConfig) {
33093340
assert_eq!(received.lease_time_secs, expected.lease_time_secs);
33103341
assert_eq!(received.renewal_time_secs, expected.renewal_time_secs);

crates/rpc-utils/src/dhcp.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@ impl DhcpConfig {
9696
carbide_provisioning_server_ipv4: Ipv4Addr,
9797
carbide_ntpservers: Vec<Ipv4Addr>,
9898
carbide_nameservers: Vec<Ipv4Addr>,
99+
carbide_nameservers_v6: Vec<Ipv6Addr>,
99100
loopback_ip: Ipv4Addr,
100101
) -> Result<Self, DhcpDataError> {
101102
Ok(DhcpConfig {
102103
carbide_nameservers,
104+
carbide_nameservers_v6,
103105
carbide_ntpservers,
104106
carbide_provisioning_server_ipv4,
105107
carbide_dhcp_server: loopback_ip,
@@ -344,6 +346,7 @@ mod tests {
344346
dhcp_server: Ipv4Addr,
345347
ntpservers: Vec<Ipv4Addr>,
346348
nameservers: Vec<Ipv4Addr>,
349+
nameservers_v6: Vec<Ipv6Addr>,
347350
lease_time_secs: u32,
348351
}
349352

@@ -434,24 +437,27 @@ mod tests {
434437
}
435438

436439
fn summarize_dhcp_config(
437-
(provisioning_server, ntpservers, nameservers, dhcp_server): (
440+
(provisioning_server, ntpservers, nameservers, nameservers_v6, dhcp_server): (
438441
Ipv4Addr,
439442
Vec<Ipv4Addr>,
440443
Vec<Ipv4Addr>,
444+
Vec<Ipv6Addr>,
441445
Ipv4Addr,
442446
),
443447
) -> Result<DhcpConfigSummary, &'static str> {
444448
DhcpConfig::from_forge_dhcp_config(
445449
provisioning_server,
446450
ntpservers,
447451
nameservers,
452+
nameservers_v6,
448453
dhcp_server,
449454
)
450455
.map(|config| DhcpConfigSummary {
451456
provisioning_server: config.carbide_provisioning_server_ipv4,
452457
dhcp_server: config.carbide_dhcp_server,
453458
ntpservers: config.carbide_ntpservers,
454459
nameservers: config.carbide_nameservers,
460+
nameservers_v6: config.carbide_nameservers_v6,
455461
lease_time_secs: config.lease_time_secs,
456462
})
457463
.map_err(dhcp_error_kind)
@@ -484,12 +490,14 @@ mod tests {
484490
Ipv4Addr::new(192, 0, 2, 10),
485491
vec![Ipv4Addr::new(192, 0, 2, 20)],
486492
vec![Ipv4Addr::new(192, 0, 2, 53)],
493+
vec!["2001:db8::53".parse::<Ipv6Addr>().unwrap()],
487494
Ipv4Addr::new(127, 0, 0, 2),
488495
) => Yields(DhcpConfigSummary {
489496
provisioning_server: Ipv4Addr::new(192, 0, 2, 10),
490497
dhcp_server: Ipv4Addr::new(127, 0, 0, 2),
491498
ntpservers: vec![Ipv4Addr::new(192, 0, 2, 20)],
492499
nameservers: vec![Ipv4Addr::new(192, 0, 2, 53)],
500+
nameservers_v6: vec!["2001:db8::53".parse::<Ipv6Addr>().unwrap()],
493501
lease_time_secs: DEFAULT_LEASE_TIME_SECS,
494502
}),
495503
}

0 commit comments

Comments
 (0)