Skip to content

Commit 398bbec

Browse files
committed
Fix DNS truncation, IPv6, and SERVFAIL handling in scanner
- Add EDNS0 (1232-byte buffer) to all DNS queries to avoid 512-byte UDP truncation - Add TCP fallback when responses are truncated (TC bit set) - Use net.JoinHostPort() everywhere for IPv6 address safety - Let QueryNS/QueryDoHNS inspect Authority section on non-SUCCESS Rcode (fixes subdomain NS delegation detection through SERVFAIL responses)
1 parent 8d97f7e commit 398bbec

5 files changed

Lines changed: 47 additions & 13 deletions

File tree

internal/scanner/dns.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ package scanner
22

33
import (
44
"context"
5+
"net"
56
"time"
67

78
"github.com/miekg/dns"
89
)
910

10-
func query(resolver, domain string, qtype uint16, timeout time.Duration) (*dns.Msg, bool) {
11+
// queryRaw sends a DNS query and handles EDNS0 + TCP fallback on truncation.
12+
// Returns the response regardless of Rcode, so callers can inspect Authority section.
13+
func queryRaw(resolver, domain string, qtype uint16, timeout time.Duration) (*dns.Msg, bool) {
1114
m := new(dns.Msg)
1215
m.SetQuestion(dns.Fqdn(domain), qtype)
1316
m.RecursionDesired = true
17+
m.SetEdns0(1232, false)
18+
19+
addr := net.JoinHostPort(resolver, "53")
1420

1521
c := new(dns.Client)
1622
c.Net = "udp"
@@ -19,8 +25,26 @@ func query(resolver, domain string, qtype uint16, timeout time.Duration) (*dns.M
1925
ctx, cancel := context.WithTimeout(context.Background(), timeout)
2026
defer cancel()
2127

22-
r, _, err := c.ExchangeContext(ctx, m, resolver+":53")
23-
if err != nil || r == nil || r.Rcode != dns.RcodeSuccess {
28+
r, _, err := c.ExchangeContext(ctx, m, addr)
29+
if err != nil || r == nil {
30+
return nil, false
31+
}
32+
33+
// Retry over TCP if response was truncated
34+
if r.Truncated {
35+
c.Net = "tcp"
36+
r, _, err = c.ExchangeContext(ctx, m, addr)
37+
if err != nil || r == nil {
38+
return nil, false
39+
}
40+
}
41+
42+
return r, true
43+
}
44+
45+
func query(resolver, domain string, qtype uint16, timeout time.Duration) (*dns.Msg, bool) {
46+
r, ok := queryRaw(resolver, domain, qtype, timeout)
47+
if !ok || r.Rcode != dns.RcodeSuccess {
2448
return nil, false
2549
}
2650
return r, true
@@ -35,7 +59,7 @@ func QueryA(resolver, domain string, timeout time.Duration) bool {
3559
}
3660

3761
func QueryNS(resolver, domain string, timeout time.Duration) ([]string, bool) {
38-
r, ok := query(resolver, domain, dns.TypeNS, timeout)
62+
r, ok := queryRaw(resolver, domain, dns.TypeNS, timeout)
3963
if !ok {
4064
return nil, false
4165
}

internal/scanner/doh.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ var dohHTTPClient = &http.Client{
2020
},
2121
}
2222

23-
// QueryDoH sends a DNS query to a DoH resolver URL and returns the response.
24-
func QueryDoH(resolverURL, domain string, qtype uint16, timeout time.Duration) (*dns.Msg, bool) {
23+
// queryDoHRaw sends a DNS query to a DoH resolver and returns the response
24+
// regardless of Rcode, so callers can inspect Authority section.
25+
func queryDoHRaw(resolverURL, domain string, qtype uint16, timeout time.Duration) (*dns.Msg, bool) {
2526
m := new(dns.Msg)
2627
m.SetQuestion(dns.Fqdn(domain), qtype)
2728
m.RecursionDesired = true
@@ -61,10 +62,16 @@ func QueryDoH(resolverURL, domain string, qtype uint16, timeout time.Duration) (
6162
return nil, false
6263
}
6364

64-
if reply.Rcode != dns.RcodeSuccess {
65+
return reply, true
66+
}
67+
68+
// QueryDoH sends a DNS query to a DoH resolver URL and returns the response.
69+
func QueryDoH(resolverURL, domain string, qtype uint16, timeout time.Duration) (*dns.Msg, bool) {
70+
r, ok := queryDoHRaw(resolverURL, domain, qtype, timeout)
71+
if !ok || r.Rcode != dns.RcodeSuccess {
6572
return nil, false
6673
}
67-
return reply, true
74+
return r, true
6875
}
6976

7077
// QueryDoHA tests if a DoH resolver can resolve an A record.
@@ -78,7 +85,7 @@ func QueryDoHA(resolverURL, domain string, timeout time.Duration) bool {
7885

7986
// QueryDoHNS queries NS records via DoH.
8087
func QueryDoHNS(resolverURL, domain string, timeout time.Duration) ([]string, bool) {
81-
r, ok := QueryDoH(resolverURL, domain, dns.TypeNS, timeout)
88+
r, ok := queryDoHRaw(resolverURL, domain, dns.TypeNS, timeout)
8289
if !ok {
8390
return nil, false
8491
}

internal/scanner/e2e.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"net"
78
"os/exec"
89
"runtime"
910
"strings"
@@ -47,7 +48,7 @@ func dnsttCheck(bin, domain, pubkey, testURL, proxyAuth string, ports chan int)
4748
start := time.Now()
4849

4950
cmd := execCommandContext(ctx, bin,
50-
"-udp", ip+":53",
51+
"-udp", net.JoinHostPort(ip, "53"),
5152
"-pubkey", pubkey,
5253
domain,
5354
fmt.Sprintf("127.0.0.1:%d", port))
@@ -108,7 +109,7 @@ func slipstreamCheck(bin, domain, certPath, testURL, proxyAuth string, ports cha
108109

109110
args := []string{
110111
"-d", domain,
111-
"-r", ip + ":53",
112+
"-r", net.JoinHostPort(ip, "53"),
112113
"-l", fmt.Sprintf("%d", port),
113114
}
114115
if certPath != "" {

internal/scanner/edns.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scanner
33
import (
44
"context"
55
"fmt"
6+
"net"
67
"time"
78

89
"github.com/miekg/dns"
@@ -55,7 +56,7 @@ func testEDNSPayload(resolver, domain string, payload uint16, timeout time.Durat
5556
c.Timeout = timeout
5657

5758
ctx, cancel := context.WithTimeout(context.Background(), timeout)
58-
r, _, err := c.ExchangeContext(ctx, m, resolver+":53")
59+
r, _, err := c.ExchangeContext(ctx, m, net.JoinHostPort(resolver, "53"))
5960
cancel()
6061

6162
if err != nil || r == nil {

internal/scanner/nxdomain.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scanner
33
import (
44
"fmt"
55
"math/rand"
6+
"net"
67
"time"
78

89
"github.com/miekg/dns"
@@ -34,7 +35,7 @@ func NXDomainCheck(count int) CheckFunc {
3435
c.Net = "udp"
3536
c.Timeout = timeout
3637

37-
r, _, err := c.Exchange(m, ip+":53")
38+
r, _, err := c.Exchange(m, net.JoinHostPort(ip, "53"))
3839
if err != nil || r == nil {
3940
continue
4041
}

0 commit comments

Comments
 (0)