Skip to content

Conversation

@BPplays
Copy link
Contributor

@BPplays BPplays commented Sep 19, 2025

this PR makes it check both IPv6 and IPv4 at the same time and falls back to both if neither is detected.

Summary by CodeRabbit

  • New Features

    • Dynamic DNS resolver selection: runtime detection of available IP versions (IPv4/IPv6) to adapt the resolver list and fall back to a full set when needed.
    • Added common public IPv4 DNS endpoints and concurrent connectivity checks to prefer reachable resolvers.
  • Tests

    • Added connectivity tests validating resolver reachability across protocols and ports.

✏️ Tip: You can customize this high-level summary in your review settings.

@auto-assign auto-assign bot requested a review from dwisiswant0 September 19, 2025 16:51
Copy link
Member

@Mzack9999 Mzack9999 left a comment

Choose a reason for hiding this comment

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

@BPplays Thanks for your PR! Just to confirm, the aim of this PR is to drop unresponsive resolvers by checking them at runtime within the init() function?

@BPplays
Copy link
Contributor Author

BPplays commented Nov 19, 2025

@Mzack9999 no, this doesn't do any DNS queries.
the reason i made this was because the original implementation only checks for IPv6 and i thought it might be useful to check IPv4 too for IPv6-only environments, but i can also add a check for unresponsive resolvers if you think it would be a good idea.

@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

Walkthrough

Init probes runtime IPv4/IPv6 reachability and builds DefaultResolvers from the new IPv4Resolvers and existing IPv6Resolvers using concurrent dial checks (checkDialConnectivity, availableIpVersions), falling back to both resolver lists if detection finds no reachable addresses.

Changes

Cohort / File(s) Summary
Resolver vars & init
cdncheck.go
DefaultResolvers changed from a hard-coded slice to a nil slice populated at init. Added public IPv4Resolvers. Init now conditionally appends IPv6/IPv4 resolvers or both as a fallback.
Connectivity checks
cdncheck.go
Added checkDialConnectivity(IPs []string, proto string) bool and availableIpVersions() (hasV6 bool, hasV4 bool); removed checkIPv6Connectivity(). Concurrency and dial-based probing implemented.
Tests
cdncheck_test.go
Added TestConnCheckValid() to exercise checkDialConnectivity() (expects true for DefaultResolvers over UDP and false for given IPv6 placeholders over TCP).

Sequence Diagram(s)

sequenceDiagram
    participant Init as init()
    participant Probe as availableIpVersions()
    participant Check as checkDialConnectivity()
    participant Net as net.Dial

    rect `#F5F8FF`
    note over Init: startup — detect reachable IP versions
    Init->>Probe: availableIpVersions()
    end

    par IPv6 probe
        Probe->>Check: checkDialConnectivity(IPv6Resolvers, proto)
        Check->>Net: concurrently dial IPv6 endpoints
        Net-->>Check: success / failure
        Check-->>Probe: hasV6
    and IPv4 probe
        Probe->>Check: checkDialConnectivity(IPv4Resolvers, proto)
        Check->>Net: concurrently dial IPv4 endpoints
        Net-->>Check: success / failure
        Check-->>Probe: hasV4
    end

    rect `#F5FFF5`
    Probe-->>Init: (hasV6, hasV4)
    end

    alt hasV6 && hasV4
        Init->>Init: append IPv6Resolvers then IPv4Resolvers
    else hasV6 only
        Init->>Init: append IPv6Resolvers
    else hasV4 only
        Init->>Init: append IPv4Resolvers
    else Neither
        Init->>Init: append both IPv6Resolvers and IPv4Resolvers (fallback)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I twitched my whiskers, sniffed the net,
I dialed both roads to see which get set.
If v6 says nothing, v4 I'll invite—
two lists, one hop, I choose what's right.
Hopping back with resolvers in sight.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main change: improving connectivity checks for DefaultResolvers through runtime IPv4/IPv6 detection and dynamic initialization.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

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

🧹 Nitpick comments (2)
cdncheck_test.go (1)

45-68: Consider decoupling TestConnCheckValid from real network availability

TestConnCheckValid asserts actual dialability of DefaultResolvers (and some invalid addresses), so it can fail in offline or heavily firewalled CI environments even when the library logic is correct. If you start seeing flaky builds, consider:

  • Injecting a dial function into checkDialConnectivity so tests can stub it, or
  • Guarding this test behind an env flag and skipping it when external connectivity isn’t guaranteed.

Functionally the assertions match the current implementation; this is just about long‑term test robustness.

cdncheck.go (1)

36-65: Code is safe on Go 1.24.0; refactoring recommended as best practice

The checkDialConnectivity function at lines 36-65 does not have a critical bug on the current Go version (1.24.0). Go 1.22 introduced per-iteration scope for range variables, making the closure capture safe. The code works correctly as-is.

However, the proposed refactoring—passing IP as a parameter—is still recommended as a best practice for code clarity and portability. It makes the captured value explicit and avoids surprises if the code is later compiled with older Go versions.

 	for _, IP := range IPs {
 		wg.Add(1)
-		go func(){
+		go func(ip string) {
 			defer wg.Done()

-			conn, err := net.DialTimeout(proto, IP, 3*time.Second)
+			conn, err := net.DialTimeout(proto, ip, 3*time.Second)
 			if conn != nil {
 				defer conn.Close()
 			}

 			results <- err == nil
-		}()
+		}(IP)
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46e3c48 and b3e6ac3.

📒 Files selected for processing (2)
  • cdncheck.go (2 hunks)
  • cdncheck_test.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
cdncheck_test.go (1)
cdncheck.go (1)
  • DefaultResolvers (18-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Builds (macOS-latest, 1.24.x)
  • GitHub Check: Test Builds (windows-latest, 1.24.x)
  • GitHub Check: Functional Test (windows-latest)
🔇 Additional comments (3)
cdncheck.go (2)

18-34: Dynamic resolver wiring and init fallback look correct

DefaultResolvers being populated in init() from IPv6Resolvers/IPv4Resolvers based on detected availability, with a final fallback to appending both lists when neither protocol is detected, matches the PR description and preserves a sane default even when connectivity checks fail. The new IPv4Resolvers list is consistent with the existing IPv6 list and keeps defaults explicit and discoverable.

Also applies to: 92-107


67-89: IPv4/IPv6 detection and resolver selection behavior aligns with intent

availableIpVersions probing IPv6 and IPv4 concurrently and init() using those flags to include the corresponding resolver lists (with a fallback to both when neither probe succeeds) gives the desired behavior for IPv6-only, IPv4-only, dual-stack, and “detection failed” environments. The concurrency and simple boolean flags keep startup overhead bounded while avoiding unnecessary DNS queries.

cdncheck_test.go (1)

10-18: Test helper for rewriting resolver ports is clear and fine

The allHostportsWithPortForTest helper cleanly rewrites resolver hostports to a custom port for connectivity testing and keeps TestConnCheckValid readable. Panicking on split errors is acceptable in this test-only utility.

Copy link

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

🧹 Nitpick comments (2)
cdncheck_test.go (2)

10-21: Prefer test failures over panics in helper

allHostportsWithPortForTest panics on SplitHostPort errors. It’s fine for internal constants but harder to debug if something changes. Consider passing *testing.T in and using require.NoError(t, err) instead of panic, so failures are reported as regular test failures.


48-71: Network-dependent test may be flaky across environments

TestConnCheckValid assumes successful UDP net.DialTimeout to public resolvers (including port 10000) and failure to the [::] addresses. In CI environments without IPv6 or with restricted outbound UDP, this can intermittently fail even though the code is correct.

You may want to gate or soften this test (e.g., skip if testing.Short(), or behind an env flag) so basic unit test runs don’t depend on external network conditions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 557f338 and a96c0a1.

📒 Files selected for processing (2)
  • cdncheck.go (2 hunks)
  • cdncheck_test.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
cdncheck_test.go (1)
cdncheck.go (1)
  • DefaultResolvers (18-18)
🔇 Additional comments (1)
cdncheck.go (1)

18-34: Resolver initialization and IP-version probing behavior looks sound

The new availableIpVersions + init() flow (populate DefaultResolvers from IPv6Resolvers/IPv4Resolvers, fallback to both on detection failure) matches the PR description and should behave correctly across IPv4-only, IPv6-only, dual-stack, and “no outbound” environments, assuming the underlying net.DialTimeout calls can complete.

No changes needed here beyond the checkDialConnectivity fix above.

Also applies to: 67-88, 92-107

Copy link

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

🧹 Nitpick comments (1)
cdncheck.go (1)

36-65: Goroutine capture issue resolved; consider parameter naming.

The critical goroutine loop variable capture issue from the previous review has been correctly fixed by passing IP as a parameter.

Minor style note: the parameter name IP (uppercase) is unconventional in Go. Consider using lowercase ip for consistency with Go idioms, as uppercase names typically indicate exported identifiers.

Optional: idiomatic parameter naming
-func checkDialConnectivity(IPs []string, proto string) bool {
+func checkDialConnectivity(ips []string, proto string) bool {
 	var wg sync.WaitGroup
-	results := make(chan bool, len(IPs))
+	results := make(chan bool, len(ips))

-	for _, IP := range IPs {
+	for _, ip := range ips {
 		wg.Add(1)
-		go func(IP string){
+		go func(ip string){
 			defer wg.Done()
 
-			conn, err := net.DialTimeout(proto, IP, 3*time.Second)
+			conn, err := net.DialTimeout(proto, ip, 3*time.Second)
 			if conn != nil {
 				defer conn.Close()
 			}
 
 			results <- err == nil
-		}(IP)
+		}(ip)
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a96c0a1 and 0479207.

📒 Files selected for processing (2)
  • cdncheck.go (2 hunks)
  • cdncheck_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cdncheck_test.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Builds (windows-latest, 1.24.x)
🔇 Additional comments (3)
cdncheck.go (3)

18-18: LGTM: Dynamic initialization approach.

Switching from a hardcoded list to runtime population based on connectivity is a solid improvement.


28-34: LGTM: Reliable IPv4 resolvers.

The IPv4 resolver list includes well-known, trustworthy public DNS servers (Cloudflare and Google).


92-108: LGTM: Sensible fallback strategy.

The init logic correctly appends resolvers based on detected connectivity and provides a reasonable fallback (both IPv6 and IPv4) when detection yields no results, ensuring DefaultResolvers is never empty.

Copy link

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

🧹 Nitpick comments (2)
cdncheck.go (2)

67-89: Consider testing multiple resolvers per protocol for more robust detection.

The function tests only a single IP address per protocol (Google DNS at lines 73 and 81). If that specific server is unreachable (e.g., due to firewall rules, routing issues, or regional blocking), the detection will fail even though other resolvers might be reachable. This could lead to suboptimal resolver selection.

While the fallback logic in init() (lines 104-107) mitigates this by including both resolver sets when detection fails, testing against multiple IPs would provide more accurate detection of actual connectivity.

🔎 Suggested enhancement to test multiple IPs
 func availableIpVersions() (hasV6 bool, hasV4 bool) {
 	var wg sync.WaitGroup
 
 	wg.Add(1)
 	go func(){
 		defer wg.Done()
-		if checkDialConnectivity([]string{"[2001:4860:4860::8888]:53"}, "udp") {
+		if checkDialConnectivity([]string{
+			"[2001:4860:4860::8888]:53",
+			"[2606:4700:4700::1111]:53",
+		}, "udp") {
 			hasV6 = true
 		}
 	}()
 
 	wg.Add(1)
 	go func(){
 		defer wg.Done()
-		if checkDialConnectivity([]string{"8.8.8.8:53"}, "udp") {
+		if checkDialConnectivity([]string{
+			"8.8.8.8:53",
+			"1.1.1.1:53",
+		}, "udp") {
 			hasV4 = true
 		}
 	}()
 
 	wg.Wait()
 
 	return hasV6, hasV4
 }

92-108: LGTM! Init logic correctly implements dynamic resolver selection.

The initialization correctly:

  • Detects available IP versions concurrently
  • Populates DefaultResolvers based on detected connectivity
  • Falls back to including both resolver sets if detection fails

This ensures DefaultResolvers is never empty while preferring resolvers that are more likely to be reachable based on the initial connectivity checks.

Optional nitpick: Line 104 uses len(DefaultResolvers) <= 0 but slice length cannot be negative, so == 0 would be more idiomatic. This is purely a style consideration and doesn't affect functionality.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48a1123 and 01eeef7.

📒 Files selected for processing (1)
  • cdncheck.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Builds (macOS-latest, 1.24.x)
  • GitHub Check: Test Builds (windows-latest, 1.24.x)
🔇 Additional comments (2)
cdncheck.go (2)

28-34: Good addition of trusted IPv4 resolvers.

The IPv4Resolvers list mirrors the structure of IPv6Resolvers and uses well-known, reliable public DNS servers (Cloudflare and Google).


36-65: LGTM! Loop variable capture fixed correctly.

The goroutine now properly captures the loop variable by passing it as a parameter (line 47), addressing the critical issue from the previous review. The implementation correctly tests concurrent connectivity, with proper resource cleanup and a buffered channel to prevent goroutine leaks.

@Mzack9999
Copy link
Member

The existing functionality checks if ipv6 stack is enabled and in case add ipv6 resolvers. For backward compatibility we are fine with the assumption that ipv4 is always available and only ipv6 needs a pre-flight check, and in case we are able to perform any connection to an Ipv6 address we add the trusted resolvers. Closing the PR as not planned

@Mzack9999 Mzack9999 closed this Jan 9, 2026
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