Skip to content

Commit d762d21

Browse files
committed
feat(dhcp): populate IPv6 DNS resolvers in the agent's DHCP config
The dual-stack `DhcpConfig` added in #2672 has a `carbide_nameservers_v6` field, but nothing fills it -- `from_forge_dhcp_config` and the agent's config builders are IPv4-only, so a host's IPv6 DNS resolvers never reach the config. The agent already learns both families from the DPU's `ServiceAddresses`; this splits them by family and fills `carbide_nameservers_v6` at both DHCP-config build sites (the gRPC control path and the on-disk config path), threading the IPv6 set through `from_forge_dhcp_config` and `build_server_config`. A `split_nameservers_by_family` helper does the split once, so the two build sites cannot diverge. The IPv4 DHCPv4 option-6 path is unchanged; the IPv6 set stays inert until a delivery channel consumes it (DHCPv6 or RA, tracked by the broader IPv6 effort). Tests cover `from_forge_dhcp_config` populating the v6 field and the family split itself. This supports #2640. Signed-off-by: Chet Nichols III <chetn@nvidia.com>
1 parent 47e42bc commit d762d21

3 files changed

Lines changed: 57 additions & 25 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: 45 additions & 23 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,20 @@ 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+
let mut v4 = Vec::new();
145+
let mut v6 = Vec::new();
146+
for addr in nameservers {
147+
match addr {
148+
IpAddr::V4(v4_addr) => v4.push(*v4_addr),
149+
IpAddr::V6(v6_addr) => v6.push(*v6_addr),
150+
}
151+
}
152+
(v4, v6)
153+
}
154+
141155
fn build_dhcp_ntp_servers(
142156
nc: &rpc::ManagedHostNetworkConfigResponse,
143157
service_addrs: &ServiceAddresses,
@@ -1066,14 +1080,7 @@ async fn update_dhcp_via_grpc(
10661080
};
10671081
let loopback_ip: Ipv4Addr = mh_nc.loopback_ip.parse()?;
10681082

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>>();
1083+
let (nameservers_v4, nameservers_v6) = split_nameservers_by_family(&service_addrs.nameservers);
10771084

10781085
let ntpservers_v4 = build_dhcp_ntp_servers(network_config, service_addrs);
10791086

@@ -1095,6 +1102,7 @@ async fn update_dhcp_via_grpc(
10951102
pxe_ip_v4,
10961103
ntpservers_v4,
10971104
nameservers_v4,
1105+
nameservers_v6,
10981106
loopback_ip,
10991107
)?;
11001108
let mut host_config = carbide_rpc_utils::dhcp::HostConfig::try_from(
@@ -1471,18 +1479,10 @@ fn write_dhcp_v4_server_config(
14711479

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

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>>();
1482+
// Split the dual-stack nameservers by family: the IPv4 set drives the
1483+
// DHCPv4 options written here, while the IPv6 set is held in the config for
1484+
// the eventual DHCPv6 / RA consumer (inert in this path for now).
1485+
let (nameservers_v4, nameservers_v6) = split_nameservers_by_family(&service_addrs.nameservers);
14861486

14871487
let ntpservers_v4 = build_dhcp_ntp_servers(nc, service_addrs);
14881488

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

1520-
let next_contents =
1521-
dhcp::build_server_config(pxe_ip_v4, ntpservers_v4, nameservers_v4, loopback_ip)?;
1520+
let next_contents = dhcp::build_server_config(
1521+
pxe_ip_v4,
1522+
ntpservers_v4,
1523+
nameservers_v4,
1524+
nameservers_v6,
1525+
loopback_ip,
1526+
)?;
15221527
match write(
15231528
next_contents,
15241529
&dhcp_server_path.config,
@@ -3305,6 +3310,23 @@ mod tests {
33053310
Ok(())
33063311
}
33073312

3313+
#[test]
3314+
fn split_nameservers_by_family_partitions_both_families() {
3315+
let (v4, v6) = split_nameservers_by_family(&[
3316+
IpAddr::from([10, 0, 0, 1]),
3317+
"2001:db8::1".parse().unwrap(),
3318+
IpAddr::from([10, 0, 0, 2]),
3319+
]);
3320+
assert_eq!(
3321+
v4,
3322+
vec![Ipv4Addr::new(10, 0, 0, 1), Ipv4Addr::new(10, 0, 0, 2)]
3323+
);
3324+
assert_eq!(
3325+
v6,
3326+
vec!["2001:db8::1".parse::<std::net::Ipv6Addr>().unwrap()]
3327+
);
3328+
}
3329+
33083330
fn validate_dhcp_config(received: DhcpConfig, expected: DhcpConfig) {
33093331
assert_eq!(received.lease_time_secs, expected.lease_time_secs);
33103332
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)