commit a5e7854c436639d68f9ccb98f3168d94cdd5eb0b from: Oliver Lowe date: Thu Dec 16 07:03:31 2021 UTC dns: Move recursive resolution functions into package They seem generic enough to be in the package rather than in the server implementation - ignoring the cache, of course! 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 +}