Commit Diff


commit - ded9109ce8770cd39b01c1fb5a8afdc1bd1fc335
commit + a5e7854c436639d68f9ccb98f3168d94cdd5eb0b
blob - 4b1c81b5517c84971733bfca0c64fb78887e973e
blob + ca9087535c873b2e3b6c49b86ed6a39ac8dd432b
--- cmd/recursor/recursor.go
+++ cmd/recursor/recursor.go
@@ -3,93 +3,12 @@ package main
 import (
 	"os"
 	"fmt"
-	"net"
-	"strings"
-	"sync"
 	"golang.org/x/net/dns/dnsmessage"
+	"sync"
+
 	"olowe.co/dns"
 )
 
-const rootA = "198.41.0.4"
-const rootB = "199.9.14.201"
-const rootC = "192.33.4.12"
-const rootD = "199.7.91.13"
-const rootE = "192.203.230.10"
-var roots []net.IP = []net.IP{net.ParseIP(rootA), net.ParseIP(rootB), net.ParseIP(rootC)}
-
-func isIPv6(ip net.IP) bool {
-	return strings.Contains(ip.String(), ":")
-}
-
-// appends the DNS port to the IP to be used in a dial string.
-func ip2dial(ip net.IP) string {
-	return net.JoinHostPort(ip.String(), "domain")
-}
-
-func nextServerAddrs(resources []dnsmessage.Resource) []net.IP {
-	var next []net.IP
-	for _, r := range resources {
-		switch b := r.Body.(type) {
-		case *dnsmessage.AResource:
-			next = append(next, net.IP(b.A[:]))
-		case *dnsmessage.AAAAResource:
-			next = append(next, net.IP(b.AAAA[:]))
-		}
-	}
-	return next
-}
-
-func resolve(q dnsmessage.Question, next []net.IP) (dnsmessage.Message, error) {
-	var rmsg dnsmessage.Message
-	var err error
-	for _, ip := range next {
-		// Aussie Broadband doesn't support IPv6 yet!
-		if isIPv6(ip) {
-			continue
-		}
-		fmt.Fprintf(os.Stderr, "asking %s about %s\n", ip, q.Name)
-		rmsg, err = dns.Ask(q, ip2dial(ip))
-		if rmsg.Header.Authoritative {
-			return rmsg, err
-		} else if rmsg.Header.RCode == dnsmessage.RCodeSuccess && err == nil {
-			break
-		}
-	}
-	if err != nil {
-		return dnsmessage.Message{}, fmt.Errorf("resolve %s: %w", q.Name, err)
-	}
-
-	fmt.Fprintf(os.Stderr, "no answer for %s %s, checking additionals\n", q.Name, q.Type)
-	if len(rmsg.Additionals) > 0 {
-		return resolve(q, nextServerAddrs(rmsg.Additionals))
-	}
-
-	fmt.Fprintf(os.Stderr, "no additionals for %s %s, checking authorities\n", q.Name, q.Type)
-	if len(rmsg.Authorities) > 0 {
-		for _, a := range rmsg.Authorities {
-			switch b := a.Body.(type) {
-			case *dnsmessage.NSResource:
-				newq := dnsmessage.Question{Name: b.NS, Type: dnsmessage.TypeA, Class: q.Class}
-				rmsg, err = resolveFromRoot(newq)
-				if err != nil {
-					continue
-				}
-				if len(rmsg.Answers) > 0 {
-					return resolve(q, nextServerAddrs(rmsg.Answers))
-				}
-				return resolve(q, nextServerAddrs(rmsg.Additionals))
-			}
-		}
-	}
-
-	return rmsg, nil
-	// return rmsg, fmt.Errorf("resolve %s %s: no more servers to query", q.Name, q.Type)
-}
-
-func resolveFromRoot(q dnsmessage.Question) (dnsmessage.Message, error) {
-	return resolve(q, roots)
-}
-
 func handler(w dns.ResponseWriter, qmsg *dnsmessage.Message) {
 	var rmsg dnsmessage.Message
 	rmsg.Header.ID = qmsg.Header.ID
@@ -126,7 +45,7 @@ func handler(w dns.ResponseWriter, qmsg *dnsmessage.Me
 	}
 	cache.RUnlock()
 
-	resolved, err := resolveFromRoot(q)
+	resolved, err := dns.ResolveFromRoot(q)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err)
 		rmsg.Header.RCode = dnsmessage.RCodeServerFailure
blob - /dev/null
blob + 39c88b25d1be4259e854be48c3c550445493d4d4 (mode 644)
--- /dev/null
+++ resolve.go
@@ -0,0 +1,88 @@
+package dns
+
+import (
+	"fmt"
+	"net"
+	"strings"
+	"golang.org/x/net/dns/dnsmessage"
+)
+
+
+// appends the DNS port to the IP to be used in a dial string.
+func ip2dial(ip net.IP) string {
+	return net.JoinHostPort(ip.String(), "domain")
+}
+
+func isIPv6(ip net.IP) bool {
+	return strings.Contains(ip.String(), ":")
+}
+
+func nextServerAddrs(resources []dnsmessage.Resource) []net.IP {
+	var next []net.IP
+	for _, r := range resources {
+		switch b := r.Body.(type) {
+		case *dnsmessage.AResource:
+			next = append(next, net.IP(b.A[:]))
+		case *dnsmessage.AAAAResource:
+			next = append(next, net.IP(b.AAAA[:]))
+		}
+	}
+	return next
+}
+
+const rootA = "198.41.0.4"
+const rootB = "199.9.14.201"
+const rootC = "192.33.4.12"
+const rootD = "199.7.91.13"
+const rootE = "192.203.230.10"
+var roots []net.IP = []net.IP{net.ParseIP(rootA), net.ParseIP(rootB), net.ParseIP(rootC)}
+
+func ResolveFromRoot(q dnsmessage.Question) (dnsmessage.Message, error) {
+	return Resolve(q, roots)
+}
+
+func Resolve(q dnsmessage.Question, next []net.IP) (dnsmessage.Message, error) {
+	var rmsg dnsmessage.Message
+	var err error
+	for _, ip := range next {
+		// Aussie Broadband doesn't support IPv6 yet!
+		if isIPv6(ip) {
+			continue
+		}
+		rmsg, err = Ask(q, ip2dial(ip))
+		if rmsg.Header.Authoritative {
+			return rmsg, err
+		} else if rmsg.Header.RCode == dnsmessage.RCodeSuccess && err == nil {
+			break
+		}
+	}
+	if err != nil {
+		return dnsmessage.Message{}, fmt.Errorf("resolve %s: %w", q.Name, err)
+	}
+
+	// no authoritative answer, so start looking for hints of who to ask next
+	if len(rmsg.Additionals) > 0 {
+		return Resolve(q, nextServerAddrs(rmsg.Additionals))
+	}
+
+	// no hints in additionals, check authorities
+	if len(rmsg.Authorities) > 0 {
+		for _, a := range rmsg.Authorities {
+			switch b := a.Body.(type) {
+			case *dnsmessage.NSResource:
+				newq := dnsmessage.Question{Name: b.NS, Type: dnsmessage.TypeA, Class: q.Class}
+				rmsg, err = ResolveFromRoot(newq)
+				if err != nil {
+					continue
+				}
+				if len(rmsg.Answers) > 0 {
+					return Resolve(q, nextServerAddrs(rmsg.Answers))
+				}
+				return Resolve(q, nextServerAddrs(rmsg.Additionals))
+			}
+		}
+	}
+
+	// No real answer, no more servers to ask; return our best guess
+	return rmsg, nil
+}