Skip to content

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

Merged
lixmal merged 5 commits into
mainfrom
dnsfwd-extra-qtypes
Jun 28, 2026
Merged

[client] Forward non-address DNS record types through route forwarders#6455
lixmal merged 5 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

    • Added DNS forwarding support for MX, TXT, NS, SRV, CNAME, and PTR (including reverse-DNS PTR handling).
    • When EDNS0 is enabled, unsupported query types now return an EDNS0 EDE “not supported” response.
  • Bug Fixes

    • Improved DNS response correctness for non-address lookups to return NODATA (empty answers) instead of NXDOMAIN.
    • For intercepted domains, queries are forwarded to the peer resolver to avoid system-resolver NXDOMAIN behavior.
  • Tests

    • Expanded coverage for PTR name parsing, TXT chunking, record routing, 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: a06f5441-f09d-4855-9f72-685741194af4

📥 Commits

Reviewing files that changed from the base of the PR and between dc7adf7 and 04a2cfc.

📒 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
🚧 Files skipped from review as they are similar to previous changes (5)
  • client/internal/dns/resutil/resolve_test.go
  • client/internal/routemanager/dnsinterceptor/handler.go
  • client/internal/dnsfwd/forwarder.go
  • client/internal/dns/resutil/resolve.go
  • client/internal/dnsfwd/forwarder_test.go

📝 Walkthrough

Walkthrough

Adds MX, TXT, NS, SRV, CNAME, and PTR DNS record handling in the client forwarding path. The forwarder now dispatches by query type, resutil builds records and rcodes, tests cover PTR parsing and response behavior, and intercepted domains are forwarded for all query types.

Changes

DNS Record Forwarding

Layer / File(s) Summary
Record lookup dispatch
client/internal/dns/resutil/resolve.go
Adds the exported RecordResolver interface and LookupRecords dispatcher that normalizes names to FQDN, routes by qtype to MX/TXT/NS/SRV/CNAME helpers, and returns NODATA for unsupported qtypes. Updates getRcodeForNotFound for non-address lookups.
PTR parsing and TXT packing
client/internal/dns/resutil/resolve.go
Adds PTR reverse-name parsing for in-addr.arpa and ip6.arpa, converts parsed addresses into PTR records, maps lookup errors to rcodes, and splits TXT strings into DNS-sized chunks.
resutil tests
client/internal/dns/resutil/resolve_test.go
Adds table-driven PTR query parsing tests and LookupRecords coverage for MX, TXT, NS, SRV, CNAME, and PTR behavior, including TXT chunking and rcode mapping.
Forwarder qtype routing
client/internal/dnsfwd/forwarder.go
Extends the internal resolver interface, routes A and AAAA through handleAddressQuery, routes MX/TXT/NS/SRV/CNAME/PTR through handleRecordQuery, and returns NODATA with EDE for unsupported qtypes.
Forwarder record-query tests
client/internal/dnsfwd/forwarder_test.go
Expands the mock resolver, updates response-code assertions for EDNS0 EDE and empty answers on non-address queries, and adds record-query tests for MX, TXT, NS, SRV, CNAME, and PTR forwarding.
dnsinterceptor query forwarding
client/internal/routemanager/dnsinterceptor/handler.go
Updates ServeDNS so intercepted domains forward all query types to the peer DNS forwarder and deletes the handler-chain fallthrough helper.

Sequence Diagram(s)

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

    Client->>Interceptor: DNS query
    Interceptor->>Forwarder: Forward all query types
    alt A / AAAA
        Forwarder->>Resolver: LookupIPAddr
        Resolver-->>Forwarder: IPs
        Forwarder-->>Client: address answer
    else MX / TXT / NS / SRV / CNAME / PTR
        Forwarder->>ResUtil: LookupRecords(...)
        ResUtil->>Resolver: LookupMX / LookupTXT / LookupNS / LookupSRV / LookupCNAME / LookupAddr
        Resolver-->>ResUtil: records or error
        ResUtil-->>Forwarder: RRs + rcode
        Forwarder-->>Client: record answer
    else unsupported
        Forwarder-->>Client: NODATA + EDE
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • netbirdio/netbird#4887: Also changes client/internal/dnsfwd/forwarder.go’s address-query handling in the same forwarder path.

Suggested reviewers

  • pappz
  • theodorsm

Poem

🐇 Hop hop, the PTRs align,
TXT chunks pack in tidy line.
MX and SRV now find their way,
NODATA hums, “all good” today.
Intercepted queries leap through the wire,
With EDE sparks and DNS-fire.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% 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 is concise and accurately summarizes the main change: forwarding non-address DNS record types through route forwarders.
Description check ✅ Passed The description follows the template, covers the change, checklist, and docs decision, and is mostly complete despite the blank issue ticket field.
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.
✨ 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.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

Release artifacts

Built for PR head 04a2cfc in workflow run #16263.

Artifact Link
All release artifacts Download
Linux packages Download
Windows packages Download
macOS packages Download
UI artifacts Not available (failure)
UI macOS artifacts Download

GHCR images (amd64)

No GHCR images were pushed.

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
lixmal added 3 commits June 17, 2026 15:11
# Conflicts:
#	client/internal/dns/resutil/resolve_test.go
#	client/internal/dnsfwd/forwarder.go
#	client/internal/dnsfwd/forwarder_test.go

@riccardomanfrin riccardomanfrin 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.

Looks ok to me, it's improving the DNS support. Routing peer not updated would just reply with NotImplemented

@lixmal
lixmal merged commit 4400372 into main Jun 28, 2026
45 of 46 checks passed
@lixmal
lixmal deleted the dnsfwd-extra-qtypes branch June 28, 2026 16:50
@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.

2 participants