net/dns/resolver: add envknob to debug exit node DNS queries on on Windows

Add the envknob TS_DEBUG_EXIT_NODE_DNS_NET_PKG, which enables more
verbose debug logging when calling the handleExitNodeDNSQueryWithNetPkg
function. This function is currently only called on Windows and Android.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: Ieb3ca7b98837d7dc69cd9ca47609c1c52e3afd7b
This commit is contained in:
Andrew Dunham 2023-02-03 11:25:31 -05:00
parent d2301db49c
commit 880a41bfcc
2 changed files with 35 additions and 5 deletions

View File

@ -23,6 +23,7 @@
"time" "time"
dns "golang.org/x/net/dns/dnsmessage" dns "golang.org/x/net/dns/dnsmessage"
"tailscale.com/envknob"
"tailscale.com/net/dns/resolvconffile" "tailscale.com/net/dns/resolvconffile"
"tailscale.com/net/netaddr" "tailscale.com/net/netaddr"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
@ -341,7 +342,7 @@ func (r *Resolver) HandleExitNodeDNSQuery(ctx context.Context, q []byte, from ne
default: default:
return nil, errors.New("unsupported exit node OS") return nil, errors.New("unsupported exit node OS")
case "windows", "android": case "windows", "android":
return handleExitNodeDNSQueryWithNetPkg(ctx, nil, resp) return handleExitNodeDNSQueryWithNetPkg(ctx, r.logf, nil, resp)
case "darwin": case "darwin":
// /etc/resolv.conf is a lie and only says one upstream DNS // /etc/resolv.conf is a lie and only says one upstream DNS
// but for now that's probably good enough. Later we'll // but for now that's probably good enough. Later we'll
@ -385,6 +386,8 @@ func (r *Resolver) HandleExitNodeDNSQuery(ctx context.Context, q []byte, from ne
} }
} }
var debugExitNodeDNSNetPkg = envknob.RegisterBool("TS_DEBUG_EXIT_NODE_DNS_NET_PKG")
// handleExitNodeDNSQueryWithNetPkg takes a DNS query message in q and // handleExitNodeDNSQueryWithNetPkg takes a DNS query message in q and
// return a reply (for the ExitDNS DoH service) using the net package's // return a reply (for the ExitDNS DoH service) using the net package's
// native APIs. This is only used on Windows for now. // native APIs. This is only used on Windows for now.
@ -393,7 +396,8 @@ func (r *Resolver) HandleExitNodeDNSQuery(ctx context.Context, q []byte, from ne
// //
// response contains the pre-serialized response, which notably // response contains the pre-serialized response, which notably
// includes the original question and its header. // includes the original question and its header.
func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, resolver *net.Resolver, resp *response) (res []byte, err error) { func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, logf logger.Logf, resolver *net.Resolver, resp *response) (res []byte, err error) {
logf = logger.WithPrefix(logf, "exitNodeDNSQueryWithNetPkg: ")
if resp.Question.Class != dns.ClassINET { if resp.Question.Class != dns.ClassINET {
return nil, errors.New("unsupported class") return nil, errors.New("unsupported class")
} }
@ -406,9 +410,16 @@ func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, resolver *net.Resolve
handleError := func(err error) (res []byte, _ error) { handleError := func(err error) (res []byte, _ error) {
if isGoNoSuchHostError(err) { if isGoNoSuchHostError(err) {
if debugExitNodeDNSNetPkg() {
logf(`converting Go "no such host" error to a NXDOMAIN: %v`, err)
}
resp.Header.RCode = dns.RCodeNameError resp.Header.RCode = dns.RCodeNameError
return marshalResponse(resp) return marshalResponse(resp)
} }
if debugExitNodeDNSNetPkg() {
logf("returning error: %v", err)
}
// TODO: map other errors to RCodeServerFailure? // TODO: map other errors to RCodeServerFailure?
// Or I guess our caller should do that? // Or I guess our caller should do that?
return nil, err return nil, err
@ -422,6 +433,9 @@ func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, resolver *net.Resolve
if resp.Question.Type == dns.TypeAAAA { if resp.Question.Type == dns.TypeAAAA {
network = "ip6" network = "ip6"
} }
if debugExitNodeDNSNetPkg() {
logf("resolving %s %q", network, name)
}
ips, err := r.LookupIP(ctx, network, name) ips, err := r.LookupIP(ctx, network, name)
if err != nil { if err != nil {
return handleError(err) return handleError(err)
@ -432,6 +446,9 @@ func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, resolver *net.Resolve
} }
} }
case dns.TypeTXT: case dns.TypeTXT:
if debugExitNodeDNSNetPkg() {
logf("resolving TXT %q", name)
}
strs, err := r.LookupTXT(ctx, name) strs, err := r.LookupTXT(ctx, name)
if err != nil { if err != nil {
return handleError(err) return handleError(err)
@ -443,6 +460,9 @@ func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, resolver *net.Resolve
// TODO: is this RCodeFormatError? // TODO: is this RCodeFormatError?
return nil, errors.New("bogus PTR name") return nil, errors.New("bogus PTR name")
} }
if debugExitNodeDNSNetPkg() {
logf("resolving PTR %q", ipStr)
}
addrs, err := r.LookupAddr(ctx, ipStr) addrs, err := r.LookupAddr(ctx, ipStr)
if err != nil { if err != nil {
return handleError(err) return handleError(err)
@ -451,12 +471,18 @@ func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, resolver *net.Resolve
resp.Name, _ = dnsname.ToFQDN(addrs[0]) resp.Name, _ = dnsname.ToFQDN(addrs[0])
} }
case dns.TypeCNAME: case dns.TypeCNAME:
if debugExitNodeDNSNetPkg() {
logf("resolving CNAME %q", name)
}
cname, err := r.LookupCNAME(ctx, name) cname, err := r.LookupCNAME(ctx, name)
if err != nil { if err != nil {
return handleError(err) return handleError(err)
} }
resp.CNAME = cname resp.CNAME = cname
case dns.TypeSRV: case dns.TypeSRV:
if debugExitNodeDNSNetPkg() {
logf("resolving SRV %q", name)
}
// Thanks, Go: "To accommodate services publishing SRV // Thanks, Go: "To accommodate services publishing SRV
// records under non-standard names, if both service // records under non-standard names, if both service
// and proto are empty strings, LookupSRV looks up // and proto are empty strings, LookupSRV looks up
@ -467,6 +493,9 @@ func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, resolver *net.Resolve
} }
resp.SRVs = srvs resp.SRVs = srvs
case dns.TypeNS: case dns.TypeNS:
if debugExitNodeDNSNetPkg() {
logf("resolving NS %q", name)
}
nss, err := r.LookupNS(ctx, name) nss, err := r.LookupNS(ctx, name)
if err != nil { if err != nil {
return handleError(err) return handleError(err)

View File

@ -10,6 +10,7 @@
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log"
"math/rand" "math/rand"
"net" "net"
"net/netip" "net/netip"
@ -1122,7 +1123,7 @@ func TestHandleExitNodeDNSQueryWithNetPkg(t *testing.T) {
} }
t.Run("no_such_host", func(t *testing.T) { t.Run("no_such_host", func(t *testing.T) {
res, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), backResolver, &response{ res, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), t.Logf, backResolver, &response{
Header: dnsmessage.Header{ Header: dnsmessage.Header{
ID: 123, ID: 123,
Response: true, Response: true,
@ -1233,7 +1234,7 @@ func TestHandleExitNodeDNSQueryWithNetPkg(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(fmt.Sprintf("%v_%v", tt.Type, strings.Trim(tt.Name, ".")), func(t *testing.T) { t.Run(fmt.Sprintf("%v_%v", tt.Type, strings.Trim(tt.Name, ".")), func(t *testing.T) {
got, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), backResolver, &response{ got, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), t.Logf, backResolver, &response{
Header: dnsmessage.Header{ Header: dnsmessage.Header{
ID: 123, ID: 123,
Response: true, Response: true,
@ -1395,7 +1396,7 @@ func (a *wrapResolverConn) WriteTo(q []byte, _ net.Addr) (n int, err error) {
if resp == nil { if resp == nil {
return 0, errors.New("bad query") return 0, errors.New("bad query")
} }
res, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), a.r, resp) res, err := handleExitNodeDNSQueryWithNetPkg(context.Background(), log.Printf, a.r, resp)
if err != nil { if err != nil {
return 0, err return 0, err
} }