Skip to content

[client] Forward non-address DNS record types through route forwarders#6455

Open
lixmal wants to merge 4 commits into
mainfrom
dnsfwd-extra-qtypes
Open

[client] Forward non-address DNS record types through route forwarders#6455
lixmal wants to merge 4 commits into
mainfrom
dnsfwd-extra-qtypes

Conversation

@lixmal

@lixmal lixmal commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Describe your changes

DNS queries for a domain handled by a NetBird DNS route are now fully owned by that route's forwarder. Previously only A/AAAA were served; any other record type fell through to the client's system resolver, which is not authoritative for the routed (often internal, split-horizon) zone and answers NXDOMAIN. Because NXDOMAIN is name-scoped, that poisoned the whole name and broke the A/AAAA records the route does serve.

  • Forward all query types from the DNS route interceptor to the peer forwarder instead of passing non-A/AAAA queries down the handler chain
  • Resolve MX, TXT, NS, SRV, CNAME and PTR through the host resolver and return real records
  • Answer NODATA (NOERROR, empty) for record types the host resolver cannot serve, never NXDOMAIN or NOTIMP, so a routed name is not poisoned
  • Distinguish NXDOMAIN from NODATA on a missing record by probing whether the name has any address, treating an inconclusive probe as existing
  • Attach an Extended DNS Error ("Not Supported") on the NODATA returned for unsupported types when the client uses EDNS0

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)
  • This change does not modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — OR I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See CONTRIBUTING.md.

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Behavioral fix to existing DNS route forwarding; no user-facing configuration or API changes.

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features

    • Extended DNS forwarding to support MX, TXT, NS, SRV, CNAME, and PTR queries, including PTR reverse-DNS handling.
    • Added EDNS0 Extended DNS Error (EDE) responses for unsupported query types (when EDNS0 is present).
  • Bug Fixes

    • Improved correctness by returning NODATA (no records) instead of NXDOMAIN for “not found” and unsupported non-address lookups.
    • Self-referential CNAME and malformed PTR queries now return NODATA.
  • Tests

    • Expanded unit and forwarder tests covering PTR parsing, record routing, TXT chunking, and EDNS0 EDE behavior.

@coderabbitai

coderabbitai Bot commented Jun 17, 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b7dc927-95a7-4db3-9a9d-f77f3c77d3b6

📥 Commits

Reviewing files that changed from the base of the PR and between 3a6852c and dc7adf7.

📒 Files selected for processing (1)
  • client/internal/dns/resutil/resolve.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/internal/dns/resutil/resolve.go

📝 Walkthrough

Walkthrough

Adds DNS non-address record type support (MX, TXT, NS, SRV, CNAME, PTR) to the client DNS forwarding path. A new RecordResolver interface and LookupRecords function normalize names, dispatch by Qtype, and construct DNS records. The DNSForwarder routes queries by type with EDE signaling for unsupported types, and the dnsinterceptor stops short-circuiting non-A/AAAA queries to prevent NXDOMAIN poisoning.

Changes

DNS Non-Address Record Forwarding

Layer / File(s) Summary
RecordResolver interface and LookupRecords implementation
client/internal/dns/resutil/resolve.go
Adds RecordResolver interface and LookupRecords function that normalize input names to FQDN, dispatch by qtype to resolver helpers, and construct dns.RR records for MX/TXT/NS/SRV/CNAME/PTR. Fixes getRcodeForNotFound to return NODATA instead of NXDOMAIN for non-address qtypes. Includes slices package import.
PTR parsing, error mapping, and TXT chunking
client/internal/dns/resutil/resolve.go
Implements lookupPTR with ptrQueryAddr to parse in-addr.arpa and ip6.arpa reverse-DNS query names into address strings using netip.ParseAddr and slices.Reverse. Implements rcodeForRecordError mapping IsNotFound to NODATA and other errors to SERVFAIL. Adds chunkTXT to split long TXT strings into ≤255-byte character-string chunks.
resutil unit tests
client/internal/dns/resutil/resolve_test.go
Adds TestPtrQueryAddr covering valid IPv4/IPv6 reverse names and invalid inputs. Includes mockRecordResolver test double and TestLookupRecords validating record resolution for all types, TXT chunking, NODATA vs NXDOMAIN behavior, and rcode mapping for server failures.
Extend resolver interface
client/internal/dnsfwd/forwarder.go
Adds MX, TXT, NS, SRV, CNAME, and Addr lookup methods to the internal resolver interface.
DNSForwarder Qtype routing and handlers
client/internal/dnsfwd/forwarder.go
Replaces prior NotImplemented path with Qtype switch routing: A/AAAA to handleAddressQuery, MX/TXT/NS/SRV/CNAME/PTR to handleRecordQuery (via resutil.LookupRecords), and all other types to NODATA with optional RFC 8914 EDE. Adds attachEDE helper to append EDNS0_EDE options when EDNS0 is present.
Forwarder test suite for record queries and EDE
client/internal/dnsfwd/forwarder_test.go
Extends MockResolver with six new lookup methods. Updates TestDNSForwarder_ResponseCodes to use EDNS0, change unsupported-type query to CAA, assert empty answer sections, and verify EDE presence via hasEDE helper. Adds TestDNSForwarder_RecordQueries covering MX (NODATA vs NXDOMAIN), TXT, CNAME, and PTR forwarding with helper functions newRecordTestForwarder and runRecordQuery.
dnsinterceptor: remove non-A/AAAA short-circuit
client/internal/routemanager/dnsinterceptor/handler.go
Removes logic that forwarded non-A/AAAA queries to the next handler and deletes continueToNextHandler. All query types for an intercepted domain are now forwarded to the peer DNS forwarder.

Sequence Diagram(s)

sequenceDiagram
    participant Client as DNS Client
    participant Interceptor as dnsinterceptor
    participant Forwarder as DNSForwarder
    participant ResUtil as resutil.LookupRecords
    participant HostResolver as net.Resolver

    Client->>Interceptor: DNS query (any Qtype)
    Interceptor->>Forwarder: Forward query (all Qtypes, no short-circuit)
    alt A / AAAA
        Forwarder->>HostResolver: LookupIPAddr
        HostResolver-->>Forwarder: IPs
        Forwarder-->>Client: A/AAAA answer
    else MX / TXT / NS / SRV / CNAME / PTR
        Forwarder->>ResUtil: LookupRecords(ctx, resolver, name, qtype, ttl)
        ResUtil->>HostResolver: LookupMX / LookupTXT / etc.
        HostResolver-->>ResUtil: records or error
        ResUtil-->>Forwarder: ([]dns.RR, rcode)
        Forwarder-->>Client: Record answer (rcode + RRs)
    else Unsupported (e.g. CAA)
        Forwarder-->>Client: NODATA + EDE ExtendedErrorCodeNotSupported
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • netbirdio/netbird#5046: The main PR's changes to CNAME lookup/rcode handling in resutil/resolve.go and dnsfwd/forwarder.go overlap with the retrieved PR's CNAME-chasing logic in the local resolver (both adjust how CNAME answers/absence are produced).
  • netbirdio/netbird#5686: Adds IPv6 ip6.arpa PTR record generation and collection, aligning with the new PTR reverse-name parsing (ptrQueryAddr/LookupAddr) added to the DNS forwarder.

Suggested reviewers

  • pappz

🐇 Hoppity hop, no more NXDOMAIN fright,
MX and TXT records now come out right!
PTR reverse-names parsed with care,
EDE says "not supported" — fair and square.
The interceptor lets all queries through,
No more poisoning — DNS rings true! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: forwarding non-address DNS record types through route forwarders, which is the core objective of this PR.
Description check ✅ Passed The description comprehensively covers the problem, solution, and implementation details. All major sections from the template are completed, including a clear problem statement, bug fix confirmation, test creation confirmation, and documentation rationale.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dnsfwd-extra-qtypes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

Release artifacts

Built for PR head dc7adf7 in workflow run #15963.

Artifact Link
All release artifacts Download
Linux packages Download
Windows packages Download
macOS packages Download
UI artifacts Download
UI macOS artifacts Download

GHCR images (amd64)

This comment is updated by the Release workflow. Artifact links expire according to the workflow retention policy.

@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: 2

🧹 Nitpick comments (2)
client/internal/dns/resutil/resolve.go (1)

209-309: ⚡ Quick win

Extract the per-type branches to satisfy the static-analysis gate.

LookupRecords is now doing resolution, error mapping, and RR construction for every qtype in one switch, which matches the Sonar failure on Line 209 and the long-case warnings in both switches. Pulling each branch into small helpers like lookupMXRecords, lookupTXTRecords, and parseIPv4PTR/parseIPv6PTR should clear the gate without changing behavior.

Also applies to: 314-352

🤖 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 `@client/internal/dns/resutil/resolve.go` around lines 209 - 309, The
LookupRecords function is too large with all DNS record type handling (MX, TXT,
NS, SRV, CNAME, PTR) in a single switch statement, causing static analysis
failures. Extract each case branch into separate helper functions such as
lookupMXRecords, lookupTXTRecords, lookupNSRecords, lookupSRVRecords,
lookupCNAMERecords, and helper functions for PTR record parsing. Then replace
each case in the switch statement with a call to the corresponding helper
function, ensuring the same behavior and error handling are preserved while
reducing the main function's complexity.

Source: Linters/SAST tools

client/internal/dnsfwd/forwarder_test.go (1)

659-772: ⚡ Quick win

Add NS and SRV cases to the new record-query suite.

TestDNSForwarder_RecordQueries validates MX/TXT/CNAME/PTR, but NS/SRV forwarding (added in the same switch path) is not asserted here. Adding one success and one not-found/NODATA case for each would close the regression surface.

🤖 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 `@client/internal/dnsfwd/forwarder_test.go` around lines 659 - 772, The
TestDNSForwarder_RecordQueries function is missing test cases for NS and SRV
record types, even though they are handled in the same code path as the existing
tested record types (MX, TXT, CNAME, PTR). Add four new test cases within
TestDNSForwarder_RecordQueries: one success case and one not-found/NODATA case
each for NS and SRV records. Follow the same pattern as the existing test cases
by using t.Run subtests, mocking the appropriate resolver methods (LookupNS and
LookupSRV), calling runRecordQuery, and validating the response codes and record
values using require and assert statements.
🤖 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 `@client/internal/dns/resutil/resolve.go`:
- Around line 365-368: The current logic in the nameHasAddress check at line 365
only proves a name exists by checking for A/AAAA records. If no address records
are found, it returns NXDOMAIN at line 368, which is incorrect for names that
exist as owners of other record types like SRV, TXT, NS, or MX records. To fix
this, broaden the existence check beyond just nameHasAddress to also verify if
the name exists as a valid owner of supported non-address record types before
returning NXDOMAIN. Alternatively, change the default response for missing
address records to NODATA instead of NXDOMAIN. Apply the same fix to the similar
code block referenced at lines 374-389 to ensure consistent behavior across all
existence checks.

In `@client/internal/dnsfwd/forwarder_test.go`:
- Around line 135-163: The MockResolver methods directly type-assert the return
values from args.Get() without nil checks, which causes panics when mocked
values are nil. For each method (LookupMX, LookupTXT, LookupNS, LookupSRV, and
LookupAddr), guard the type assertions by checking if args.Get() returns nil
before performing the type cast, and return appropriate zero values (nil for
pointer types, nil or empty slices for slice types) when nil is encountered
instead of allowing the panic to occur.

---

Nitpick comments:
In `@client/internal/dns/resutil/resolve.go`:
- Around line 209-309: The LookupRecords function is too large with all DNS
record type handling (MX, TXT, NS, SRV, CNAME, PTR) in a single switch
statement, causing static analysis failures. Extract each case branch into
separate helper functions such as lookupMXRecords, lookupTXTRecords,
lookupNSRecords, lookupSRVRecords, lookupCNAMERecords, and helper functions for
PTR record parsing. Then replace each case in the switch statement with a call
to the corresponding helper function, ensuring the same behavior and error
handling are preserved while reducing the main function's complexity.

In `@client/internal/dnsfwd/forwarder_test.go`:
- Around line 659-772: The TestDNSForwarder_RecordQueries function is missing
test cases for NS and SRV record types, even though they are handled in the same
code path as the existing tested record types (MX, TXT, CNAME, PTR). Add four
new test cases within TestDNSForwarder_RecordQueries: one success case and one
not-found/NODATA case each for NS and SRV records. Follow the same pattern as
the existing test cases by using t.Run subtests, mocking the appropriate
resolver methods (LookupNS and LookupSRV), calling runRecordQuery, and
validating the response codes and record values using require and assert
statements.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 796f2565-acac-435c-b89b-93221b2dee8e

📥 Commits

Reviewing files that changed from the base of the PR and between 5095e17 and 4a4a46d.

📒 Files selected for processing (5)
  • client/internal/dns/resutil/resolve.go
  • client/internal/dns/resutil/resolve_test.go
  • client/internal/dnsfwd/forwarder.go
  • client/internal/dnsfwd/forwarder_test.go
  • client/internal/routemanager/dnsinterceptor/handler.go

Comment thread client/internal/dns/resutil/resolve.go Outdated
Comment thread client/internal/dnsfwd/forwarder_test.go
@sonarqubecloud

Copy link
Copy Markdown

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.

1 participant