Skip to content

Add DNS-over-QUIC (DoQ) support#127

Merged
poyrazK merged 13 commits intomainfrom
release/doq-support
May 2, 2026
Merged

Add DNS-over-QUIC (DoQ) support#127
poyrazK merged 13 commits intomainfrom
release/doq-support

Conversation

@poyrazK
Copy link
Copy Markdown
Owner

@poyrazK poyrazK commented Apr 30, 2026

Summary

  • Implement DNS-over-QUIC (RFC 9250) transport layer with quic-go library
  • Add DoQ E2E tests covering basic queries, multiple streams, invalid queries, and connection lifecycle
  • Add ADR 0010 documenting the implementation decision
  • Update README and features.md with DoQ support

Test plan

  • go test -short ./... - All tests pass
  • go test -run DoQ ./internal/dns/server/... - DoQ specific tests pass
  • Manual verification with DoQ client (future)

Coverage

  • handleDoQListener: 90%
  • handleDoQConnection: 100%
  • handleDoQStream: 78.6%
  • setupDoQListener: 75%

Summary by CodeRabbit

  • New Features

    • Added DNS-over-QUIC (DoQ, RFC 9250) support as an additional transport, enabling lower-latency queries and 0-RTT.
  • Documentation

    • Updated docs and decision record to describe DoQ support, RFC 9250 compliance, configuration via DOQ_ADDR, and operational notes.
  • Tests

    • Added end-to-end DoQ tests covering basic queries, multiple streams, invalid input, and connection-close behavior.

poyrazK added 4 commits April 30, 2026 16:02
Implements RFC 9250 DNS-over-QUIC transport:
- handleDoQListener: Accept incoming QUIC connections
- handleDoQConnection: Handle QUIC sessions and streams
- handleDoQStream: Process DNS messages on bidirectional streams
- setupDoQListener: Create QUIC listener with TLS config

Adds doqListener and DoQAddr fields to Server struct.
Uses quic-go library with 0-RTT support and 30s timeouts.
Tests for DNS-over-QUIC implementation:
- TestDoQ_E2E_BasicQuery: Valid DNS query returns A record
- TestDoQ_E2E_MultipleStreams: Multiple queries on same connection
- TestDoQ_E2E_InvalidQuery: Malformed message handling
- TestDoQ_E2E_ConnectionClose: Connection lifecycle
Documents RFC 9250 implementation decision, configuration, and consequences.
- README.md: Add DoQ feature bullet
- features.md: Add DoQ under Advanced DNS Standards
- go.mod: Add quic-go v0.59.0 dependency
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@poyrazK has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 46 minutes and 28 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 456c21f0-6f7b-4ebb-b878-c607e1d49e55

📥 Commits

Reviewing files that changed from the base of the PR and between 8d326ae and 9c1ee01.

📒 Files selected for processing (2)
  • internal/dns/server/doq.go
  • internal/dns/server/server.go
📝 Walkthrough

Walkthrough

Adds DNS-over-QUIC (DoQ, RFC 9250) support: documentation and ADR, quic-go dependency, Server fields and lifecycle wiring for DoQ, a QUIC listener/connection/stream implementation forwarding raw DNS bytes into existing packet handler, and end-to-end tests covering streams and error cases.

Changes

DoQ feature (docs, server wiring, QUIC handling, tests)

Layer / File(s) Summary
Design / Decision
docs/decisions/0010-dns-over-quic-doq.md
New ADR (0010) documents RFC 9250 compliance, ALPN doq, 0-RTT notes, DOQ_ADDR config, and quic-go dependency reference.
Docs / Feature list
README.md, features.md
Adds DoQ to Advanced DNS Standards and feature docs, mentioning low-latency and 0-RTT.
Module deps
go.mod
Adds indirect dependency github.com/quic-go/quic-go v0.59.0.
Server API / Wiring
internal/dns/server/server.go
Adds Server.DoQAddr string and Server.doqListener *quic.Listener; Run() now conditionally starts DoQ listener when DoQAddr set and TLSConfig present and closes listener during shutdown.
Core QUIC implementation
internal/dns/server/doq.go
Implements setupDoQListener, handleDoQListener (accept loop), handleDoQConnection (per-conn stream accept loop), and handleDoQStream (per-stream read/write of raw DNS bytes, deadline handling, forward to s.handlePacket("doq"), write response).
Tests
internal/dns/server/doq_e2e_test.go
E2E tests: basic query, multiple streams on same connection, invalid query handling, connection close behavior using ephemeral TLS with ALPN doq.

Sequence Diagram(s)

sequenceDiagram
    actor Client as QUIC Client
    participant DoQListener as DoQ Listener
    participant Connection as QUIC Connection
    participant StreamHandler as Stream Handler
    participant DNSHandler as DNS Handler

    Client->>DoQListener: Establish QUIC connection (ALPN: doq)
    DoQListener->>Connection: Accept connection → spawn conn goroutine
    Client->>Connection: Open bidirectional stream
    Connection->>StreamHandler: Spawn stream goroutine
    Client->>StreamHandler: Write raw DNS query bytes
    StreamHandler->>DNSHandler: handlePacket(query, "doq")
    DNSHandler-->>StreamHandler: Return DNS response bytes
    StreamHandler->>Client: Write response and close stream
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hopped on QUIC with ears held high,
Streams danced quick beneath the sky,
Raw DNS whispers raced and flew,
0‑RTT speed—how nimbly they flew! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add DNS-over-QUIC (DoQ) support' directly and concisely summarizes the main change in the pull request—implementing DNS-over-QUIC transport alongside existing DNS standards.
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
  • Commit unit tests in branch release/doq-support

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
Review rate limit: 0/1 reviews remaining, refill in 46 minutes and 28 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
internal/dns/server/doq_e2e_test.go (1)

57-57: ⚡ Quick win

Replace the fixed sleeps with readiness polling.

These hard-coded delays will race on slower CI runners and make the suite flaky. Prefer a helper that retries quic.DialAddr until the listener is ready and rely on read deadlines / eventual assertions instead of sleeping after writes and closes.

Also applies to: 90-91, 131-131, 204-204, 246-246, 260-261

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/dns/server/doq_e2e_test.go` at line 57, Replace fixed time.Sleep
calls with a readiness-poll helper and rely on read deadlines/eventual
assertions: implement a helper (e.g., waitForQuicDial or waitForListenerReady)
that repeatedly calls quic.DialAddr with a short timeout until it succeeds or a
global test deadline elapses, use that helper wherever time.Sleep is used
(locations around the current time.Sleep at line 57 and the other instances at
90-91, 131, 204, 246, 260-261), and remove post-write/close sleeps by using
SetReadDeadline/SetDeadline on the connection and asserting expected
reads/closures with retries instead of sleeping. Ensure the helper uses
context/time.After for overall timeout and small per-attempt timeouts to avoid
long hangs and reference quic.DialAddr and the test connection read/deadline
methods when locating where to replace sleeps.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/decisions/0010-dns-over-quic-doq.md`:
- Around line 118-119: Update the incorrect RFC reference in the bullet list:
replace the second entry labeled "[RFC 7830: DNS-over-QUIC Initial Keys]" with
the correct QUIC/TLS key derivation spec by changing the label and URL to
reference RFC 9001 (e.g., "[RFC 9001: QUIC:
TLS](https://datatracker.ietf.org/doc/html/rfc9001)"), leaving the first RFC
9250 entry unchanged.

In `@internal/dns/server/doq.go`:
- Around line 59-72: The DoQ stream read uses a single stream.Read call that can
return a partial DNS message; replace the manual buffer + Read logic in the DoQ
handler (the code that reads from `stream` in internal/dns/server/doq.go) with a
read-until-EOF call such as io.ReadAll(stream) to assemble the complete message,
then check for zero-length and use the returned byte slice as the DNS payload
(i.e., remove the fixed 65535 buffer, the single Read call and the manual copy
into dnsMsg). Ensure any read errors are handled and returned consistently with
existing error handling in the handler.
- Around line 83-95: The TLS config passed into quic.ListenAddr must advertise
the "doq" ALPN; currently s.TLSConfig is used as-is and lacks NextProtos
enrichment. Fix by cloning s.TLSConfig (to avoid mutating the original), ensure
NextProtos contains "doq" (append if missing) before calling quic.ListenAddr,
and leave quic.Config.Allow0RTT at its default (do not rely on KeepAlivePeriod
for 0-RTT); also update any docs/comments that claim 0-RTT is enabled by
default. Target symbols: s.TLSConfig, TLSConfig.NextProtos, quic.ListenAddr,
quic.Config.Allow0RTT, KeepAlivePeriod.

---

Nitpick comments:
In `@internal/dns/server/doq_e2e_test.go`:
- Line 57: Replace fixed time.Sleep calls with a readiness-poll helper and rely
on read deadlines/eventual assertions: implement a helper (e.g., waitForQuicDial
or waitForListenerReady) that repeatedly calls quic.DialAddr with a short
timeout until it succeeds or a global test deadline elapses, use that helper
wherever time.Sleep is used (locations around the current time.Sleep at line 57
and the other instances at 90-91, 131, 204, 246, 260-261), and remove
post-write/close sleeps by using SetReadDeadline/SetDeadline on the connection
and asserting expected reads/closures with retries instead of sleeping. Ensure
the helper uses context/time.After for overall timeout and small per-attempt
timeouts to avoid long hangs and reference quic.DialAddr and the test connection
read/deadline methods when locating where to replace sleeps.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e699b38e-da9c-4ff9-b5b2-7749d7da3d19

📥 Commits

Reviewing files that changed from the base of the PR and between 4eaa38a and 38868e8.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • README.md
  • docs/decisions/0010-dns-over-quic-doq.md
  • features.md
  • go.mod
  • internal/dns/server/doq.go
  • internal/dns/server/doq_e2e_test.go
  • internal/dns/server/server.go

Comment on lines +118 to +119
- [RFC 9250: DNS-over-QUIC](https://datatracker.ietf.org/doc/html/rfc9250)
- [RFC 7830: DNS-over-QUIC Initial Keys](https://datatracker.ietf.org/doc/html/rfc7830)
Copy link
Copy Markdown

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

Fix the QUIC reference here.

RFC 7830 is the EDNS(0) Padding Option, not a DoQ / QUIC initial-keys reference. If you meant the QUIC-TLS key derivation spec, this should point to RFC 9001 instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/decisions/0010-dns-over-quic-doq.md` around lines 118 - 119, Update the
incorrect RFC reference in the bullet list: replace the second entry labeled
"[RFC 7830: DNS-over-QUIC Initial Keys]" with the correct QUIC/TLS key
derivation spec by changing the label and URL to reference RFC 9001 (e.g., "[RFC
9001: QUIC: TLS](https://datatracker.ietf.org/doc/html/rfc9001)"), leaving the
first RFC 9250 entry unchanged.

Comment on lines +59 to +72
// DNS-over-QUIC uses variable-length messages (no 2-byte length prefix like TCP)
buf := make([]byte, 65535)
n, err := stream.Read(buf)
if err != nil {
if err != io.EOF && err != io.ErrUnexpectedEOF {
return
}
}
if n == 0 {
return
}

dnsMsg := make([]byte, n)
copy(dnsMsg, buf[:n])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n internal/dns/server/doq.go | head -100

Repository: poyrazK/cloudDNS

Length of output: 3182


🏁 Script executed:

find . -type f -name "*doq*test*.go" -o -name "*test*doq*.go" | head -20

Repository: poyrazK/cloudDNS

Length of output: 98


🏁 Script executed:

cat -n ./internal/dns/server/doq_e2e_test.go

Repository: poyrazK/cloudDNS

Length of output: 9287


🏁 Script executed:

rg -n "DNSPacket|FromBuffer" internal/dns/packet/ --type go | head -30

Repository: poyrazK/cloudDNS

Length of output: 2338


🏁 Script executed:

rg -n "func.*FromBuffer" internal/dns/packet/ --type go -A 10 | head -50

Repository: poyrazK/cloudDNS

Length of output: 3361


🏁 Script executed:

sed -n '941,970p' internal/dns/packet/packet.go

Repository: poyrazK/cloudDNS

Length of output: 1012


🏁 Script executed:

cat -n internal/dns/packet/packet.go | sed -n '1,50p'

Repository: poyrazK/cloudDNS

Length of output: 2110


🏁 Script executed:

rg -n "type BytePacketBuffer" internal/dns/packet/ --type go -A 20

Repository: poyrazK/cloudDNS

Length of output: 1331


🏁 Script executed:

rg -n "Load|read|Read" internal/dns/packet/buffer.go --type go | head -20

Repository: poyrazK/cloudDNS

Length of output: 999


🏁 Script executed:

cat -n internal/dns/packet/buffer.go | sed -n '68,75p'

Repository: poyrazK/cloudDNS

Length of output: 354


🏁 Script executed:

rg -n "length|Length|framing|Framing" internal/dns/server/doq.go --type go

Repository: poyrazK/cloudDNS

Length of output: 228


🏁 Script executed:

find . -name "*.go" -path "*/dns/server/*" | grep -E "tcp|dot" | head -10

Repository: poyrazK/cloudDNS

Length of output: 42


🏁 Script executed:

rg -n "handleTCP|handleDOT" internal/dns/server/ --type go -A 15 | head -50

Repository: poyrazK/cloudDNS

Length of output: 2922


🏁 Script executed:

rg -n "func.*handleTCPConnection" internal/dns/server/ --type go -A 25 | head -60

Repository: poyrazK/cloudDNS

Length of output: 1819


Use io.ReadAll() to read complete DoQ message before parsing.

QUIC streams are byte streams without message boundaries. A single Read() may return only a partial DNS message if the client writes are split across packets. The current handler will intermittently parse truncated queries, though E2E tests don't expose this because they write complete queries in single packets. Read until EOF/FIN, then parse the assembled message.

Suggested direction
-	buf := make([]byte, 65535)
-	n, err := stream.Read(buf)
+	dnsMsg, err := io.ReadAll(stream)
 	if err != nil {
-		if err != io.EOF && err != io.ErrUnexpectedEOF {
+		if err != io.EOF && err != io.ErrUnexpectedEOF {
 			return
 		}
 	}
-	if n == 0 {
+	if len(dnsMsg) == 0 {
 		return
 	}
-
-	dnsMsg := make([]byte, n)
-	copy(dnsMsg, buf[:n])

Compare with TCP handler (lines 598-609 in server.go) which uses 2-byte length framing with io.ReadFull() to ensure complete message receipt before parsing.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// DNS-over-QUIC uses variable-length messages (no 2-byte length prefix like TCP)
buf := make([]byte, 65535)
n, err := stream.Read(buf)
if err != nil {
if err != io.EOF && err != io.ErrUnexpectedEOF {
return
}
}
if n == 0 {
return
}
dnsMsg := make([]byte, n)
copy(dnsMsg, buf[:n])
// DNS-over-QUIC uses variable-length messages (no 2-byte length prefix like TCP)
dnsMsg, err := io.ReadAll(stream)
if err != nil {
if err != io.EOF && err != io.ErrUnexpectedEOF {
return
}
}
if len(dnsMsg) == 0 {
return
}
🧰 Tools
🪛 GitHub Check: Lint

[failure] 63-63:
comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error (errorlint)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/dns/server/doq.go` around lines 59 - 72, The DoQ stream read uses a
single stream.Read call that can return a partial DNS message; replace the
manual buffer + Read logic in the DoQ handler (the code that reads from `stream`
in internal/dns/server/doq.go) with a read-until-EOF call such as
io.ReadAll(stream) to assemble the complete message, then check for zero-length
and use the returned byte slice as the DNS payload (i.e., remove the fixed 65535
buffer, the single Read call and the manual copy into dnsMsg). Ensure any read
errors are handled and returned consistently with existing error handling in the
handler.

Comment thread internal/dns/server/doq.go
poyrazK added 4 commits April 30, 2026 18:00
- Add missing zone to TestDoQ_E2E_MultipleStreams
- Add defer cancel to TestDoQ_E2E_ConnectionClose
- Log unexpected read errors in handleDoQStream

Note: 0-RTT warning not added as disabling early data in quic-go
requires complex configuration; documented in ADR instead.
- Use errors.Is for wrapped error comparison
- Check error returns from Close/CloseWithError
- Add underscore to ignored handlePacket return
Pass lifecycleCtx to handleDoQConnection and AcceptStream
to satisfy contextcheck linter requirement.
Propagate lifecycleCtx from Run() through handleDoQListener,
handleDoQConnection, to handleDoQStream to satisfy contextcheck linter.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 (1)
internal/dns/server/doq_e2e_test.go (1)

57-57: ⚡ Quick win

Replace fixed sleeps with readiness polling to reduce flakiness.

Using fixed delays for startup/processing makes these tests timing-sensitive and slower in CI. Prefer a short retry loop with timeout (dial until success / operation ready) instead of sleeping.

Also applies to: 91-91, 134-134, 207-207, 250-250, 265-265

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/dns/server/doq_e2e_test.go` at line 57, Replace the hard-coded
time.Sleep calls in internal/dns/server/doq_e2e_test.go with a short
readiness-polling loop that retries until success or a timeout to avoid
flakiness; implement a small helper (e.g., waitForReady or retryUntilTimeout)
used from the test functions that repeatedly performs the real readiness check
(dialing the DoQ endpoint, performing a minimal DNS query, or checking the
server listener) with a small backoff and a total timeout, and replace the
occurrences of time.Sleep(200 * time.Millisecond) (and the other listed sleeps)
with calls to that helper so tests proceed as soon as the service is ready
instead of waiting a fixed interval.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/dns/server/doq_e2e_test.go`:
- Around line 228-232: The test currently only logs unexpected stream errors so
invalid-query cases can silently pass; change the error handling after calling
stream.Read(respBuf) to fail the test when err is non-nil and not isEOFLike(err)
by replacing the t.Logf call with a test failure (e.g., t.Fatalf or t.Errorf
followed by return) so unexpected errors in the stream.Read(respBuf) path cause
the test to fail; modify the branch that references isEOFLike and t.Logf to use
t.Fatalf (or t.Errorf + t.FailNow) instead.
- Around line 147-149: The test currently ignores errors from conn.OpenStream()
and subsequent stream I/O/decoding (e.g., stream1, stream2 opens and
stream.Read() results), which can hide failures and cause nil derefs; update the
multi-stream test to fail fast by checking and handling all returned errors from
conn.OpenStream(), Close(), and Read() calls (e.g., after calling
conn.OpenStream() for stream1/stream2, assert err==nil and t.Fatalf on error),
validate the returned stream is non-nil before using it, and check Read()/decode
errors and lengths before parsing so any I/O or decode error immediately fails
the test rather than being ignored.

---

Nitpick comments:
In `@internal/dns/server/doq_e2e_test.go`:
- Line 57: Replace the hard-coded time.Sleep calls in
internal/dns/server/doq_e2e_test.go with a short readiness-polling loop that
retries until success or a timeout to avoid flakiness; implement a small helper
(e.g., waitForReady or retryUntilTimeout) used from the test functions that
repeatedly performs the real readiness check (dialing the DoQ endpoint,
performing a minimal DNS query, or checking the server listener) with a small
backoff and a total timeout, and replace the occurrences of time.Sleep(200 *
time.Millisecond) (and the other listed sleeps) with calls to that helper so
tests proceed as soon as the service is ready instead of waiting a fixed
interval.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9cdf7a97-2dac-49c4-b03e-c5ee2ce2092b

📥 Commits

Reviewing files that changed from the base of the PR and between 38868e8 and f6dc342.

📒 Files selected for processing (3)
  • internal/dns/server/doq.go
  • internal/dns/server/doq_e2e_test.go
  • internal/dns/server/server.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/dns/server/doq.go
  • internal/dns/server/server.go

Comment on lines +147 to +149
stream1, _ := conn.OpenStream()
defer stream1.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ignored returned errors in this test file.
rg -n -C2 ':\s*=\s*[^,\n]+,\s*_' internal/dns/server/doq_e2e_test.go
rg -n -C2 '_\s*=\s*[^;\n]+\(' internal/dns/server/doq_e2e_test.go

Repository: poyrazK/cloudDNS

Length of output: 2143


🏁 Script executed:

cat -n internal/dns/server/doq_e2e_test.go | sed -n '140,195p'

Repository: poyrazK/cloudDNS

Length of output: 2227


Fail fast on stream I/O and decode errors in the multi-stream test.

Lines 147, 154, 158, 161, 169, 173, 178, and 186 ignore errors, which can mask protocol regressions and may panic if stream operations fail. In particular, ignoring errors from OpenStream() can result in nil-stream operations, and ignoring Read() errors while using the byte count can lead to parsing incomplete data.

Suggested fix
-	stream1, _ := conn.OpenStream()
+	stream1, err := conn.OpenStream()
+	if err != nil {
+		t.Fatalf("Failed to open first stream: %v", err)
+	}
 	defer stream1.Close()
@@
-	_, _ = stream1.Write(qb1.Buf[:qb1.Position()])
+	if _, err := stream1.Write(qb1.Buf[:qb1.Position()]); err != nil {
+		t.Fatalf("Failed to write first query: %v", err)
+	}
@@
-	n1, _ := stream1.Read(respBuf1)
+	n1, err := stream1.Read(respBuf1)
+	if err != nil && !(n1 > 0 && isEOFLike(err)) {
+		t.Fatalf("Failed to read first response: %v", err)
+	}
@@
-	stream2, _ := conn.OpenStream()
+	stream2, err := conn.OpenStream()
+	if err != nil {
+		t.Fatalf("Failed to open second stream: %v", err)
+	}
 	defer stream2.Close()
@@
-	_, _ = stream2.Write(qb2.Buf[:qb2.Position()])
+	if _, err := stream2.Write(qb2.Buf[:qb2.Position()]); err != nil {
+		t.Fatalf("Failed to write second query: %v", err)
+	}
@@
-	n2, _ := stream2.Read(respBuf2)
+	n2, err := stream2.Read(respBuf2)
+	if err != nil && !(n2 > 0 && isEOFLike(err)) {
+		t.Fatalf("Failed to read second response: %v", err)
+	}
@@
-	_ = resPacket.FromBuffer(pb)
+	if err := resPacket.FromBuffer(pb); err != nil {
+		t.Fatalf("Failed to parse first response: %v", err)
+	}
@@
-	_ = resPacket2.FromBuffer(pb2)
+	if err := resPacket2.FromBuffer(pb2); err != nil {
+		t.Fatalf("Failed to parse second response: %v", err)
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/dns/server/doq_e2e_test.go` around lines 147 - 149, The test
currently ignores errors from conn.OpenStream() and subsequent stream
I/O/decoding (e.g., stream1, stream2 opens and stream.Read() results), which can
hide failures and cause nil derefs; update the multi-stream test to fail fast by
checking and handling all returned errors from conn.OpenStream(), Close(), and
Read() calls (e.g., after calling conn.OpenStream() for stream1/stream2, assert
err==nil and t.Fatalf on error), validate the returned stream is non-nil before
using it, and check Read()/decode errors and lengths before parsing so any I/O
or decode error immediately fails the test rather than being ignored.

Comment on lines +228 to +232
_, err = stream.Read(respBuf)
// EOF or error is acceptable - server may close stream on parse failure
if err != nil && !isEOFLike(err) {
t.Logf("Stream error (may be expected): %v", err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Invalid-query test currently passes on unexpected stream errors.

Line 230 only logs unexpected errors, so the test can succeed when behavior is wrong. This should fail the test.

Suggested fix
-	if err != nil && !isEOFLike(err) {
-		t.Logf("Stream error (may be expected): %v", err)
-	}
+	if err != nil && !isEOFLike(err) {
+		t.Fatalf("Unexpected stream error for invalid query: %v", err)
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_, err = stream.Read(respBuf)
// EOF or error is acceptable - server may close stream on parse failure
if err != nil && !isEOFLike(err) {
t.Logf("Stream error (may be expected): %v", err)
}
_, err = stream.Read(respBuf)
// EOF or error is acceptable - server may close stream on parse failure
if err != nil && !isEOFLike(err) {
t.Fatalf("Unexpected stream error for invalid query: %v", err)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/dns/server/doq_e2e_test.go` around lines 228 - 232, The test
currently only logs unexpected stream errors so invalid-query cases can silently
pass; change the error handling after calling stream.Read(respBuf) to fail the
test when err is non-nil and not isEOFLike(err) by replacing the t.Logf call
with a test failure (e.g., t.Fatalf or t.Errorf followed by return) so
unexpected errors in the stream.Read(respBuf) path cause the test to fail;
modify the branch that references isEOFLike and t.Logf to use t.Fatalf (or
t.Errorf + t.FailNow) instead.

poyrazK added 4 commits May 1, 2026 21:55
The quic.Listener.Accept call doesn't support context cancellation
properly, so use context.Background. Context is still propagated to
handleDoQConnection and handleDoQStream for proper lifecycle.
Use lifecycleCtx from server struct to satisfy contextcheck linter.
Listener.Accept still uses context.Background as quic-go
doesn't properly support context cancellation there.
Use ctx instead of context.Background for listener.Accept
to satisfy contextcheck linter. This is the final fix.
Log a warning when DoQ listener starts to alert operators
that 0-RTT has replay attack implications.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/dns/server/server.go`:
- Around line 513-523: The code silently skips starting DoQ when s.DoQAddr is
set but s.TLSConfig is nil or when setupDoQListener returns an error; update the
startup logic in the block that checks s.DoQAddr to treat these as failures (or
at minimum log an error) instead of ignoring them: validate that s.TLSConfig is
non-nil and if nil call s.Logger.Error with context ("DoQ configured but
TLSConfig is nil") and return/propagate the failure or mark startup as failed;
likewise, if setupDoQListener returns a non-nil errDoQ, log it at error level
with the addr and the error and stop/startup failure rather than silently
continuing, while still setting s.doqListener and spawning the goroutine only on
success.
- Around line 69-70: The shared TLSConfig (TLSConfig) may lack the "doq" ALPN
and causes QUIC handshakes to fail; in setupDoQListener (called from doq.go:91)
clone the provided TLSConfig (use tlsConfig.Clone() or shallow copy if nil),
ensure NextProtos includes "doq" (append if missing) on the cloned config, and
pass that cloned config to quic.ListenAddr so the original TLSConfig used for
DoT/DoH is not mutated and the QUIC listener always presents the "doq" ALPN.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69b25f15-8edf-4a16-87b4-1853409d7542

📥 Commits

Reviewing files that changed from the base of the PR and between f6dc342 and 8d326ae.

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

Comment thread internal/dns/server/server.go
Comment thread internal/dns/server/server.go Outdated
- server.go: Log error when TLSConfig is nil or setupDoQListener fails
- doq.go: Clone TLSConfig and append "doq" ALPN to avoid handshake failures

Both issues were identified in post-merge review of PR #127.
Copy link
Copy Markdown
Owner Author

@poyrazK poyrazK left a comment

Choose a reason for hiding this comment

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

It's okay to merge

@poyrazK poyrazK merged commit 0052adb into main May 2, 2026
7 checks passed
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