Skip to content

feat(dns): parse reverse-DNS (PTR) query names into IP addresses#2691

Open
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-2637
Open

feat(dns): parse reverse-DNS (PTR) query names into IP addresses#2691
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-2637

Conversation

@chet

@chet chet commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

The conversion foundation for reverse DNS (PTR). A PTR query name is an address in in-addr.arpa / ip6.arpa form; resolving it means going name → address, which is a clean inverse — so this does it in Rust instead of a large PL/pgSQL function:

  • arpa_qname_to_ip(&str) -> Option<IpAddr> reverses the four octets (IPv4) or 32 nibbles (IPv6); Ipv6Addr assembles the address, so the IPv6 case is a few lines. Returns None for non-arpa / malformed names.
  • Table tests (value_scenarios!) cover IPv4, IPv6, and the reject cases.

The PTR handler will parse the incoming qname with this and look the interface up by address (an indexed WHERE address = $ip), rather than computing an arpa string for every row in a view. The address index + find_ptr_record land with #2639; the handler with #2641.

Reworked from the original SQL approach (a nico_ip_to_arpa_qname PL/pgSQL function + dns_records_ptr view). Doing the inverse in Rust avoids a large SQL function, keeps the logic testable in one place, and makes the lookup a plain indexed address equality. The existing nico_inet_to_dns_hostname (forward instance DNS) is tracked for the same treatment separately.

Implements #2637. Part of the #2630 epic (reverse DNS / PTR records).

Draft pending review.

@copy-pr-bot

copy-pr-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 69048ea0-abc8-4964-8ee5-77a0410bc5d5

📥 Commits

Reviewing files that changed from the base of the PR and between b7198e7 and 34935ba.

📒 Files selected for processing (1)
  • crates/api-db/src/dns/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/api-db/src/dns/mod.rs

Summary by CodeRabbit

Release Notes

  • New Features

    • Added strict reverse-DNS (PTR) qname parsing helpers for IPv4 and IPv6, converting deterministically to IP addresses with exact validation.
  • Bug Fixes

    • Improved domain normalization to use ASCII lowercase for more consistent casing behavior.
  • Tests

    • Added unit tests covering correct IPv4/IPv6 PTR qnames (including case-insensitive handling) and verifying malformed/non-matching inputs are rejected.

Walkthrough

A new exported Rust function arpa_qname_to_ip is added to crates/api-db/src/dns/mod.rs. It parses reverse-DNS PTR qnames for .in-addr.arpa (IPv4) and .ip6.arpa (IPv6) into IpAddr, returning None for malformed or non-matching inputs. The existing normalize_domain function is refined to use ASCII-specific lowercasing. A unit test module exercises the function against valid and invalid cases.

Changes

Rust ARPA qname to IP parsing

Layer / File(s) Summary
arpa_qname_to_ip implementation and unit tests
crates/api-db/src/dns/mod.rs
Adds std::net imports and the exported arpa_qname_to_ip function that trims trailing dots, lowercases using ASCII-specific rules, validates label counts (exactly 4 decimal octets for IPv4, exactly 32 single hex nibbles for IPv6), and reconstructs the corresponding IpAddr by reversing and assembling labels in least-significant-first order. Updates normalize_domain to use to_ascii_lowercase() for ASCII-aware lowercasing. Unit tests assert correct parsing for IPv4, IPv6, loopback, documentation-prefix addresses, case-insensitive qname normalization, and None for malformed or non-PTR qnames.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the primary change: introducing a function to parse reverse-DNS PTR query names into IP addresses.
Description check ✅ Passed The description comprehensively explains the purpose, implementation details, testing approach, and broader context of the change, directly relating to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@chet

chet commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/api-db/src/dns/mod.rs`:
- Around line 90-126: The test cases in the check_cases_async function call
currently cover standard IPv4 and IPv6 addresses but are missing a test case for
IPv6-mapped IPv4 addresses (in the format ::ffff:x.x.x.x), which exercise a
dedicated embedded-IPv4 conversion branch in the SQL function. Add a new Case
object to the test array with a scenario that uses an IPv6-mapped IPv4 address
as input (for example ::ffff:192.0.2.1), set an appropriate expect value with
the expected PTR record output for that mapped address, and place it alongside
the other test cases passed to check_cases_async to ensure this branch is
covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3e40a21a-8107-4574-b58b-fd3918484804

📥 Commits

Reviewing files that changed from the base of the PR and between 6d36a43 and 0244842.

📒 Files selected for processing (2)
  • crates/api-db/migrations/20260618070345_reverse_dns_ptr_records.sql
  • crates/api-db/src/dns/mod.rs

Comment thread crates/api-db/src/dns/mod.rs Outdated
@chet chet marked this pull request as ready for review June 19, 2026 01:22
@chet chet requested a review from a team as a code owner June 19, 2026 01:22
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
boot-artifacts-aarch64 3 0 0 3 0 0
boot-artifacts-x86_64 3 0 0 3 0 0
forge-admin-cli-x86_64 264 6 23 99 6 130
machine-validation-runner 704 34 183 258 35 194
machine_validation 704 34 183 258 35 194
nvmetal-carbide 704 34 183 258 35 194
TOTAL 2382 108 572 879 111 712

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

@chet chet changed the title feat(dns): derive PTR records from machine interface addresses feat(dns): parse reverse-DNS (PTR) query names into IP addresses Jun 20, 2026
@chet

chet commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/api-db/src/dns/mod.rs (1)

86-112: 💤 Low value

Consider adding a case-insensitivity test case.

The function normalizes input via to_ascii_lowercase() (line 35), but no test case exercises this path. Adding a mixed-case variant would strengthen the contract.

📝 Suggested addition
             "rejects non-arpa and malformed" {
                 "host.example.com." => None,
                 "1.2.3.in-addr.arpa." => None,
                 "300.0.0.0.in-addr.arpa." => None,
                 "1.0.168.192.in-addr.arpa.extra." => None,
             }
+            "handles case normalization" {
+                "1.0.168.192.IN-ADDR.ARPA." => Some(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))),
+            }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/api-db/src/dns/mod.rs` around lines 86 - 112, The
parses_arpa_qname_to_ip test function does not verify that the arpa_qname_to_ip
function correctly handles mixed-case input, even though the implementation
normalizes input via to_ascii_lowercase(). Add test cases with mixed-case
variants (such as uppercase letters in the arpa domain names like "IN-ADDR.ARPA"
or "IP6.ARPA") to at least one of the existing test groups to ensure the
case-insensitivity normalization path is exercised and working correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/api-db/src/dns/mod.rs`:
- Around line 86-112: The parses_arpa_qname_to_ip test function does not verify
that the arpa_qname_to_ip function correctly handles mixed-case input, even
though the implementation normalizes input via to_ascii_lowercase(). Add test
cases with mixed-case variants (such as uppercase letters in the arpa domain
names like "IN-ADDR.ARPA" or "IP6.ARPA") to at least one of the existing test
groups to ensure the case-insensitivity normalization path is exercised and
working correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c53b2f0f-008a-4a76-8de5-59c8ca2befd4

📥 Commits

Reviewing files that changed from the base of the PR and between 38244e1 and b7198e7.

📒 Files selected for processing (1)
  • crates/api-db/src/dns/mod.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/api-db/src/dns/mod.rs (1)

26-26: ⚡ Quick win

Prefer structured tracing fields over interpolated log text.

Use tracing attributes so logs remain queryable in logfmt pipelines.

Suggested patch
-    tracing::debug!("Normalized domain name: {} to: {}", name, normalize_domain);
+    tracing::debug!(original=%name, normalized=%normalize_domain, "normalized domain name");

As per coding guidelines, "All services should emit logs in 'logfmt' syntax" and "prefer placing common fields as attributes passed to tracing functions instead of using string interpolation."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/api-db/src/dns/mod.rs` at line 26, The tracing::debug! call uses
string interpolation which makes logs non-queryable in logfmt pipelines. Replace
the interpolated string format in the debug call with structured tracing fields.
Instead of passing the name and normalize_domain values as format string
arguments, pass them as named attributes to the tracing macro (for example,
using field_name = value syntax), keeping only a descriptive message string in
the macro call itself. This ensures the log output uses logfmt syntax and
remains queryable.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/api-db/src/dns/mod.rs`:
- Line 25: The normalize_domain variable uses to_lowercase() instead of
to_ascii_lowercase(), which creates an inconsistency with how PTR parsing
normalizes domains and can produce inconsistent DNS lookup keys for non-ASCII
input. Replace the to_lowercase() method call with to_ascii_lowercase() in the
normalize_domain assignment to ensure consistent ASCII case-folding for DNS
domain-key normalization.

---

Nitpick comments:
In `@crates/api-db/src/dns/mod.rs`:
- Line 26: The tracing::debug! call uses string interpolation which makes logs
non-queryable in logfmt pipelines. Replace the interpolated string format in the
debug call with structured tracing fields. Instead of passing the name and
normalize_domain values as format string arguments, pass them as named
attributes to the tracing macro (for example, using field_name = value syntax),
keeping only a descriptive message string in the macro call itself. This ensures
the log output uses logfmt syntax and remains queryable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: dd9fe382-037e-49c2-8cae-1dfe368b50a8

📥 Commits

Reviewing files that changed from the base of the PR and between 79e54c0 and b7198e7.

📒 Files selected for processing (1)
  • crates/api-db/src/dns/mod.rs

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

pub fn normalize_domain(name: &str) -> String {
let normalize_domain = name.trim_end_matches('.').to_lowercase();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use ASCII case-folding in domain normalization for DNS keys.

normalize_domain uses to_lowercase(), while PTR parsing uses to_ascii_lowercase(). For DNS/domain-key normalization, this mismatch can produce inconsistent lookup keys for non-ASCII input paths.

Suggested patch
-    let normalize_domain = name.trim_end_matches('.').to_lowercase();
+    let normalize_domain = name.trim_end_matches('.').to_ascii_lowercase();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/api-db/src/dns/mod.rs` at line 25, The normalize_domain variable uses
to_lowercase() instead of to_ascii_lowercase(), which creates an inconsistency
with how PTR parsing normalizes domains and can produce inconsistent DNS lookup
keys for non-ASCII input. Replace the to_lowercase() method call with
to_ascii_lowercase() in the normalize_domain assignment to ensure consistent
ASCII case-folding for DNS domain-key normalization.

Reverse DNS resolves a PTR query name -- an address in `in-addr.arpa` / `ip6.arpa` form -- back to a hostname. The lookup direction, arpa name to address, is a clean inverse, so this does it in Rust rather than a large PL/pgSQL function: `arpa_qname_to_ip` reverses the four octets (IPv4) or the 32 nibbles (IPv6) and hands back an `IpAddr`. The PTR handler will use that to look the interface up by address directly -- an indexed equality, not a per-row arpa string computed across a view.

`Ipv6Addr` does the address assembly, so the IPv6 case is a few lines instead of the manual hextet expansion the equivalent SQL needs. This is the conversion foundation for reverse DNS; the indexed address lookup (`find_ptr_record`) and the handler arm build on it.

Tests cover the IPv4 and IPv6 forms plus rejection of non-arpa and malformed names.

This supports NVIDIA#2637.

Signed-off-by: Chet Nichols III <chetn@nvidia.com>
@chet

chet commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Thanks for the eyes. Updated -- added a case-normalization group (mixed-case IN-ADDR.ARPA + IP6.ARPA with uppercase hex nibbles) so the to_ascii_lowercase path is covered. PTAL. Resolve if good, thanks!

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the current state of the file under review
cat crates/api-db/src/dns/mod.rs

Length of output: 9828


🏁 Script executed:

#!/bin/bash
# List all CodeRabbit review comments on this PR to determine what to resolve
gh pr view 2691 --repo NVIDIA/infra-controller --comments | head -200

Length of output: 8116


🏁 Script executed:

#!/bin/bash
# Get open review comments to find IDs and determine what to resolve
gh api repos/NVIDIA/infra-controller/pulls/2691/comments --jq '.[] | {id: .id, body: .body[0:120], path: .path, line: .line}'

Length of output: 1092


🏁 Script executed:

#!/bin/bash
# Get full body of the open normalize_domain comment and the resolved mapped-IPv4 comment
gh api repos/NVIDIA/infra-controller/pulls/comments/3446815099 --jq '.body'
gh api repos/NVIDIA/infra-controller/pulls/comments/3433851517 --jq '.resolved // "not resolved field"'
# Check if comment 3433851517 thread was resolved
gh api repos/NVIDIA/infra-controller/pulls/comments/3433873291 --jq '.body[0:400]'

Length of output: 2233



@chet — the "normalizes case" test group is well-constructed and cleanly validates the to_ascii_lowercase path in arpa_qname_to_ip. That function looks production-ready.

One item remains before I can resolve: comment on Line 25 (normalize_domain) is still open and the fix has not yet landed in the current file. normalize_domain continues to use to_lowercase() while arpa_qname_to_ip uses to_ascii_lowercase() — the inconsistency is still present:

- let normalize_domain = name.trim_end_matches('.').to_lowercase();
+ let normalize_domain = name.trim_end_matches('.').to_ascii_lowercase();

DNS labels are restricted to ASCII by spec (RFC 1123), so to_ascii_lowercase() is the correct primitive for both functions. Once that one-liner is applied, I am happy to resolve all remaining comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants