Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SplitHorizonResult holds the comparison between an authoritative DNS answer
|
||||
// and the answer from a local resolver.
|
||||
type SplitHorizonResult struct {
|
||||
AuthoritativeIPs []string // IPs from the authoritative nameserver
|
||||
AuthoritativeSource string // NS hostname that provided the authoritative answer
|
||||
LocalIPs []string // IPs from the local resolver (nil if query failed / NXDOMAIN)
|
||||
LocalSource string // Local resolver address that was queried (empty if all failed)
|
||||
HasConflict bool // True when both sets are non-nil and differ
|
||||
}
|
||||
|
||||
// CheckSplitHorizon queries local resolvers for hostname and compares the result
|
||||
// against authoritativeIPs.
|
||||
//
|
||||
// Local resolvers are tried in priority order; the first successful response is used.
|
||||
// If all local resolvers fail or return NXDOMAIN, HasConflict is false (no conflict).
|
||||
// w receives Stage 2.5 diagnostic lines (pass io.Discard to suppress).
|
||||
func CheckSplitHorizon(ctx context.Context, w io.Writer, localResolvers []string, hostname string, authoritativeIPs []string, authoritativeSource string, timeout time.Duration) SplitHorizonResult {
|
||||
result := SplitHorizonResult{
|
||||
AuthoritativeIPs: authoritativeIPs,
|
||||
AuthoritativeSource: authoritativeSource,
|
||||
}
|
||||
|
||||
if len(localResolvers) > 0 {
|
||||
fmt.Fprintf(w, "[dns] Stage 2.5: Split-horizon cross-check against local resolvers [%s]\n",
|
||||
strings.Join(localResolvers, ", "))
|
||||
}
|
||||
|
||||
for _, lr := range localResolvers {
|
||||
queryCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
ips, err := queryA(queryCtx, lr, hostname, timeout)
|
||||
cancel()
|
||||
if err != nil {
|
||||
continue // timeout, NXDOMAIN, etc. — not a conflict
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
continue
|
||||
}
|
||||
result.LocalIPs = ips
|
||||
result.LocalSource = lr
|
||||
result.HasConflict = !ipSetsEqual(authoritativeIPs, ips)
|
||||
if result.HasConflict {
|
||||
fmt.Fprintf(w, "[dns] %s \u2192 %s (differs from authoritative %s)\n",
|
||||
lr, strings.Join(ips, ", "), strings.Join(authoritativeIPs, ", "))
|
||||
fmt.Fprintf(w, "[dns] CONFLICT: authoritative and local resolvers disagree\n")
|
||||
} else {
|
||||
fmt.Fprintf(w, "[dns] %s \u2192 %s (matches authoritative)\n", lr, strings.Join(ips, ", "))
|
||||
fmt.Fprintf(w, "[dns] No conflict detected\n")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// All local resolvers failed — no conflict detectable.
|
||||
return result
|
||||
}
|
||||
|
||||
// ipSetsEqual reports whether a and b contain the same IPs regardless of order.
|
||||
func ipSetsEqual(a, b []string) bool {
|
||||
sa := sortedDedup(a)
|
||||
sb := sortedDedup(b)
|
||||
if len(sa) != len(sb) {
|
||||
return false
|
||||
}
|
||||
for i := range sa {
|
||||
if sa[i] != sb[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// sortedDedup returns a sorted, deduplicated copy of ips.
|
||||
func sortedDedup(ips []string) []string {
|
||||
seen := make(map[string]bool, len(ips))
|
||||
deduped := make([]string, 0, len(ips))
|
||||
for _, ip := range ips {
|
||||
if !seen[ip] {
|
||||
seen[ip] = true
|
||||
deduped = append(deduped, ip)
|
||||
}
|
||||
}
|
||||
sort.Strings(deduped)
|
||||
return deduped
|
||||
}
|
||||
Reference in New Issue
Block a user