Skip to content

Commit 802d873

Browse files
committed
feat(dns): serve reverse PTR for overlay instance addresses
Overlay (DPU-managed) instances got forward A/AAAA records in #2408, but their addresses still resolved to nothing in reverse -- find_ptr_record only answered from machine interfaces, and overlay addresses live in instance_addresses. This adds the reverse half: an overlay instance's address now answers a PTR with its own FQDN, so a forward record and its PTR round-trip. find_ptr_record gains a second UNION arm over instance_addresses that mirrors the dns_records_instance forward view -- same join to the segment's forward zone, same host_inband exclusion (those addresses are the host's own, already answered by the machine arm). It reads the hostname stored at allocation, so there is no second IP-to-name derivation; the lookup is by address, like the machine arm. - Add an instance_addresses arm to find_ptr_record, resolving an overlay address to <hostname>.<zone>. - Reuse #2408's stored hostname + the segment's forward zone; exclude host_inband (the machine arm already answers those). Tests added! This supports #2776 Signed-off-by: Chet Nichols III <chetn@nvidia.com>
1 parent 4869a26 commit 802d873

1 file changed

Lines changed: 104 additions & 8 deletions

File tree

crates/api-db/src/dns/resource_record.rs

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,19 @@ impl<'r> FromRow<'r, PgRow> for DbPtrRecord {
130130
}
131131
}
132132

133-
/// Find the PTR answers for an address: the FQDN(s) the forward shortname view
134-
/// publishes for whichever primary or BMC interface holds it. The `WHERE` matches
135-
/// `dns_records_shortname_combined`'s (primary or BMC), so a forward A/AAAA record
136-
/// and its PTR round-trip; the joins are otherwise narrower (no `dns_record_types`,
137-
/// since PTR's type is fixed) and the TTL uses `COALESCE(meta.ttl, 300)` to match
138-
/// the TTL the forward record is actually served with. The lookup is by `address`,
139-
/// so it rides the `machine_interface_addresses_address_idx` index rather than scanning.
133+
/// Find the PTR answers for an address: the FQDN(s) it resolves back to. Two
134+
/// sources, each mirroring its forward counterpart so a forward A/AAAA record and
135+
/// its PTR round-trip:
136+
/// - a machine interface that holds the address -- the `dns_records_shortname_combined`
137+
/// primary/BMC arm, with `COALESCE(meta.ttl, 300)` to match the forward TTL;
138+
/// - an overlay instance allocated the address -- the `dns_records_instance` arm
139+
/// (`instance_addresses` joined to its segment's forward zone), with `host_inband`
140+
/// excluded since those are the host's own address, already answered by the
141+
/// machine source.
142+
///
143+
/// Both look up by `address`, so the query rides the address indexes rather than
144+
/// scanning. The two arms are disjoint (an overlay address is never a machine
145+
/// interface address), so the `UNION` only ever merges an accidental exact match.
140146
pub async fn find_ptr_record(
141147
txn: impl DbReader<'_>,
142148
address: IpAddr,
@@ -151,7 +157,18 @@ pub async fn find_ptr_record(
151157
JOIN domains d ON d.id = mi.domain_id
152158
LEFT JOIN dns_record_metadata meta ON meta.id = mi.id
153159
WHERE mia.address = $1::inet
154-
AND (mi.primary_interface = TRUE OR mi.interface_type = 'Bmc')"#;
160+
AND (mi.primary_interface = TRUE OR mi.interface_type = 'Bmc')
161+
UNION
162+
SELECT
163+
concat(ia.hostname, '.', d.name, '.') AS ptr_content,
164+
300 AS ttl,
165+
d.id AS domain_id
166+
FROM instance_addresses ia
167+
JOIN network_segments ns ON ns.id = ia.segment_id
168+
JOIN domains d ON d.id = ns.subdomain_id
169+
WHERE ia.address = $1::inet
170+
AND ia.hostname IS NOT NULL
171+
AND ns.network_segment_type <> 'host_inband'"#;
155172

156173
sqlx::query_as::<_, DbPtrRecord>(query)
157174
.bind(address.to_string())
@@ -365,4 +382,83 @@ mod tests {
365382
"host_inband addresses are not published by the instance arm"
366383
);
367384
}
385+
386+
#[crate::sqlx_test]
387+
async fn overlay_instance_addresses_resolve_reverse_ptr(pool: sqlx::PgPool) {
388+
struct Case {
389+
address: &'static str,
390+
prefix: &'static str,
391+
ptr: &'static str,
392+
}
393+
// One row per address family: the PTR answers with the instance's forward
394+
// FQDN -- the reverse of #2408's A/AAAA record, so the two round-trip.
395+
let cases = [
396+
Case {
397+
address: "10.1.2.3",
398+
prefix: "10.1.2.0/24",
399+
ptr: "10-1-2-3.tenant.example.com.",
400+
},
401+
Case {
402+
address: "2001:db8:abcd::2",
403+
prefix: "2001:db8:abcd::/64",
404+
ptr: "2001-0db8-abcd-0000-0000-0000-0000-0002.tenant.example.com.",
405+
},
406+
];
407+
408+
let mut txn = pool.begin().await.unwrap();
409+
let (instance_id, segment_id, vpc_id) =
410+
seed_instance_segment(txn.as_mut(), "tenant.example.com", "tenant").await;
411+
for case in &cases {
412+
add_address(
413+
txn.as_mut(),
414+
instance_id,
415+
segment_id,
416+
vpc_id,
417+
case.address,
418+
case.prefix,
419+
)
420+
.await;
421+
}
422+
423+
for case in &cases {
424+
let ptrs = super::find_ptr_record(
425+
txn.as_mut(),
426+
case.address.parse::<std::net::IpAddr>().unwrap(),
427+
)
428+
.await
429+
.unwrap();
430+
assert_eq!(ptrs.len(), 1, "one PTR for {}", case.address);
431+
assert_eq!(ptrs[0].ptr_content, case.ptr);
432+
}
433+
}
434+
435+
#[crate::sqlx_test]
436+
async fn host_inband_instance_addresses_have_no_instance_ptr(pool: sqlx::PgPool) {
437+
// A host_inband address is the host's own; its PTR comes from the machine
438+
// source, not the instance arm. With no machine interface here there is no
439+
// answer -- proving the instance arm excludes host_inband.
440+
let mut txn = pool.begin().await.unwrap();
441+
let (instance_id, segment_id, vpc_id) =
442+
seed_instance_segment(txn.as_mut(), "host.example.com", "host_inband").await;
443+
add_address(
444+
txn.as_mut(),
445+
instance_id,
446+
segment_id,
447+
vpc_id,
448+
"10.9.9.9",
449+
"10.9.9.0/24",
450+
)
451+
.await;
452+
453+
let ptrs = super::find_ptr_record(
454+
txn.as_mut(),
455+
"10.9.9.9".parse::<std::net::IpAddr>().unwrap(),
456+
)
457+
.await
458+
.unwrap();
459+
assert!(
460+
ptrs.is_empty(),
461+
"host_inband addresses are not answered by the instance arm"
462+
);
463+
}
368464
}

0 commit comments

Comments
 (0)