commit a7c06b85bf15297629c13f8aae6cf51c828119b2 from: Oliver Lowe date: Fri Dec 17 08:26:13 2021 UTC recursor: actually, move recursive stuff back to cmd it's too specific to the implementation of a recursive resolver. More stuff will be added there later I reckon. commit - 2c109646ee0be071d39acc68f8cda66157d37582 commit + a7c06b85bf15297629c13f8aae6cf51c828119b2 blob - 90337d381fc684d9d17c40a0cea994840f60d762 blob + 22a93824298b83d275c54c8a58450244b4037ec9 --- cmd/recursor/recursor.go +++ cmd/recursor/recursor.go @@ -45,7 +45,7 @@ func handler(w dns.ResponseWriter, qmsg *dnsmessage.Me } cache.RUnlock() - resolved, err := dns.ResolveFromRoot(q) + resolved, err := resolveFromRoot(q) if err != nil { fmt.Fprintln(os.Stderr, err) rmsg.Header.RCode = dnsmessage.RCodeServerFailure blob - /dev/null blob + 5744b2f2b490d44a1acbcea6014b23a663aaf424 (mode 644) --- /dev/null +++ cmd/recursor/resolve.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "net" + "strings" + "golang.org/x/net/dns/dnsmessage" + + "olowe.co/dns" +) + +// 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 = 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) + } + + // 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 +} blob - 39c88b25d1be4259e854be48c3c550445493d4d4 (mode 644) blob + /dev/null --- resolve.go +++ /dev/null @@ -1,88 +0,0 @@ -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 -}