git » dnss » commit 9e7d1cf

Autodetect https resolver mode

author Alberto Bertogli
2018-07-21 11:13:10 UTC
committer Alberto Bertogli
2018-07-21 11:13:39 UTC
parent 3f473ebca2b022d79af6bd19fe12f4dff02ed368

Autodetect https resolver mode

This patch implements autodetection in the httpresolver, and makes dnss
use it by default.

A new flag is introduced to force the mode, which can be useful in some
cases and also for testing purposes.

dnss.go +9 -4
dnss_test.go +9 -2
internal/httpresolver/resolver.go +40 -8
tests/external.sh +37 -5

diff --git a/dnss.go b/dnss.go
index 25d0e53..53f61dc 100644
--- a/dnss.go
+++ b/dnss.go
@@ -70,8 +70,8 @@ var (
 	insecureForTesting = flag.Bool("testing__insecure_http", false,
 		"INSECURE, for testing only")
 
-	dohMode = flag.Bool("experimental__doh_mode", false,
-		"DoH mode (experimental)")
+	forceMode = flag.String("force_mode", "",
+		"Force HTTPS resolver mode ('JSON', 'DoH', 'autodetect' (default))")
 
 	// Deprecated flags that no longer make sense; we keep them for backwards
 	// compatibility but may be removed in the future.
@@ -108,10 +108,15 @@ func main() {
 		}
 
 		var resolver dnsserver.Resolver
-		if *dohMode {
+		switch *forceMode {
+		case "DoH":
 			resolver = httpresolver.NewDoH(upstream, *httpsClientCAFile)
-		} else {
+		case "JSON":
 			resolver = httpresolver.NewJSON(upstream, *httpsClientCAFile)
+		case "", "autodetect":
+			resolver = httpresolver.New(upstream, *httpsClientCAFile)
+		default:
+			log.Fatalf("-force_mode=%q is not a valid mode", *forceMode)
 		}
 
 		if *enableCache {
diff --git a/dnss_test.go b/dnss_test.go
index d0cde94..38fcc51 100644
--- a/dnss_test.go
+++ b/dnss_test.go
@@ -52,11 +52,17 @@ func Setup(tb testing.TB, mode string) string {
 	}
 
 	var r dnsserver.Resolver
-	if mode == "DoH" {
+	switch mode {
+	case "DoH":
 		r = httpresolver.NewDoH(HTTPSToDNSURL, "")
-	} else {
+	case "JSON":
 		r = httpresolver.NewJSON(HTTPSToDNSURL, "")
+	case "autodetect":
+		r = httpresolver.New(HTTPSToDNSURL, "")
+	default:
+		tb.Fatalf("%q is not a valid mode", mode)
 	}
+
 	dtoh := dnsserver.New(DNSToHTTPSAddr, r, "")
 	go dtoh.ListenAndServe()
 
@@ -144,6 +150,7 @@ func handleTestDNS(w dns.ResponseWriter, r *dns.Msg) {
 func TestEndToEnd(t *testing.T) {
 	t.Run("mode=JSON", func(t *testing.T) { testEndToEnd(t, "JSON") })
 	t.Run("mode=DoH", func(t *testing.T) { testEndToEnd(t, "DoH") })
+	t.Run("mode=autodetect", func(t *testing.T) { testEndToEnd(t, "autodetect") })
 }
 
 func testEndToEnd(t *testing.T, mode string) {
diff --git a/internal/httpresolver/resolver.go b/internal/httpresolver/resolver.go
index 17c5fb2..8bc2090 100644
--- a/internal/httpresolver/resolver.go
+++ b/internal/httpresolver/resolver.go
@@ -47,6 +47,17 @@ func loadCertPool(caFile string) (*x509.CertPool, error) {
 	return pool, nil
 }
 
+// New creates a new HTTPS resolver, which uses the given upstream URL to
+// resolve queries. It will auto-detect the mode (JSON or DoH) by doing a
+// resolution at initialization time.
+func New(upstream *url.URL, caFile string) *httpsResolver {
+	return &httpsResolver{
+		Upstream: upstream,
+		CAFile:   caFile,
+		mode:     "autodetect",
+	}
+}
+
 // NewJSON creates a new JSON resolver which uses the given upstream URL to
 // resolve queries.
 func NewJSON(upstream *url.URL, caFile string) *httpsResolver {
@@ -83,22 +94,43 @@ func (r *httpsResolver) Init() error {
 
 	// If CAFile is empty, we're ok with the defaults (use the system default
 	// CA database).
-	if r.CAFile == "" {
-		return nil
-	}
+	if r.CAFile != "" {
+		pool, err := loadCertPool(r.CAFile)
+		if err != nil {
+			return err
+		}
 
-	pool, err := loadCertPool(r.CAFile)
-	if err != nil {
-		return err
+		transport.TLSClientConfig = &tls.Config{
+			ClientCAs: pool,
+		}
 	}
 
-	transport.TLSClientConfig = &tls.Config{
-		ClientCAs: pool,
+	if r.mode == "autodetect" {
+		if err := r.autodetect(); err != nil {
+			return err
+		}
 	}
 
 	return nil
 }
 
+func (r *httpsResolver) autodetect() error {
+	tr := trace.New("httpsresolver", "Autodetect")
+	defer tr.Finish()
+
+	m := &dns.Msg{}
+	m.SetQuestion("example.com.", dns.TypeA)
+
+	for _, mode := range []string{"DoH", "JSON"} {
+		r.mode = mode
+		if _, err := r.Query(m, tr); err == nil {
+			return nil
+		}
+	}
+
+	return fmt.Errorf("Failed to autodetect resolver mode")
+}
+
 func (r *httpsResolver) Maintain() {
 }
 
diff --git a/tests/external.sh b/tests/external.sh
index 178b6bb..5e0e23b 100755
--- a/tests/external.sh
+++ b/tests/external.sh
@@ -42,7 +42,6 @@ function dnss() {
 
 	./dnss $COVER_ARGS \
 		-v 3 -monitoring_listen_addr :1900 \
-		-testing__insecure_http \
 		"$@" > .dnss.log 2>&1 &
 	PID=$!
 }
@@ -88,14 +87,25 @@ fi
 
 
 echo "## Launching HTTPS server"
-dnss -enable_https_to_dns -https_server_addr "localhost:1999"
+dnss -enable_https_to_dns \
+	-testing__insecure_http -https_server_addr "localhost:1999"
 HTTP_PID=$PID
 mv .dnss.log .dnss.http.log
 
 wait_until_ready tcp 1999
 
+echo "## Autodetect against dnss"
+dnss -enable_dns_to_https -dns_listen_addr "localhost:1053" \
+	-testing__insecure_http \
+	-https_upstream "http://localhost:1999/dns-query"
+
+resolve
+kill $PID
+
 echo "## JSON against dnss"
 dnss -enable_dns_to_https -dns_listen_addr "localhost:1053" \
+	-testing__insecure_http \
+	-force_mode="JSON" \
 	-https_upstream "http://localhost:1999/dns-query"
 
 resolve
@@ -103,7 +113,8 @@ kill $PID
 
 echo "## DoH against dnss"
 dnss -enable_dns_to_https -dns_listen_addr "localhost:1053" \
-	-experimental__doh_mode \
+	-testing__insecure_http \
+	-force_mode="DoH" \
 	-https_upstream "http://localhost:1999/dns-query"
 
 # Exercise DoH via GET (dnss always uses POST).
@@ -125,16 +136,37 @@ kill $PID
 kill $HTTP_PID
 
 
+echo "## Autodetect against 1.1.1.1"
+dnss -enable_dns_to_https -dns_listen_addr "localhost:1053" \
+	-https_upstream "https://1.1.1.1/dns-query"
+
+resolve
+kill $PID
+
 echo "## DoH against 1.1.1.1"
 dnss -enable_dns_to_https -dns_listen_addr "localhost:1053" \
-	-experimental__doh_mode \
+	-force_mode="DoH" \
 	-https_upstream "https://1.1.1.1/dns-query"
 
 resolve
 kill $PID
 
+echo "## Autodetect against dns.google.com"
+dnss -enable_dns_to_https -dns_listen_addr "localhost:1053" \
+	-https_upstream "https://dns.google.com/resolve"
+
+resolve
+kill $PID
+
+echo "## JSON against dns.google.com"
+dnss -enable_dns_to_https -dns_listen_addr "localhost:1053" \
+	-force_mode="JSON" \
+	-https_upstream "https://dns.google.com/resolve"
+
+resolve
+kill $PID
 
-echo "## JSON against default (checks default works)"
+echo "## Defaults"
 dnss -enable_dns_to_https -dns_listen_addr "localhost:1053"
 
 resolve