dns

DNS client and server implementations using the Go project's dnsmessage package
Log | Files | Refs | README | LICENSE

resolve.go (3449B)


      1 package main
      2 
      3 import (
      4 	"fmt"
      5 	"golang.org/x/net/dns/dnsmessage"
      6 	"net"
      7 	"os"
      8 	"strings"
      9 
     10 	"olowe.co/dns"
     11 )
     12 
     13 const rootA = "198.41.0.4"
     14 const rootB = "199.9.14.201"
     15 const rootC = "192.33.4.12"
     16 const rootD = "199.7.91.13"
     17 const rootE = "192.203.230.10"
     18 
     19 var roots []net.IP = []net.IP{net.ParseIP(rootA), net.ParseIP(rootB), net.ParseIP(rootC)}
     20 
     21 // appends the DNS port to the IP to be used in a dial string.
     22 func ip2dial(ip net.IP) string {
     23 	return net.JoinHostPort(ip.String(), "domain")
     24 }
     25 
     26 func isIPv6(ip net.IP) bool {
     27 	return strings.Contains(ip.String(), ":")
     28 }
     29 
     30 func filterRRs(rrs []dnsmessage.Resource, n dnsmessage.Name, t dnsmessage.Type) []dnsmessage.Resource {
     31 	var matches []dnsmessage.Resource
     32 	for _, r := range rrs {
     33 		if r.Header.Name == n && r.Header.Type == t {
     34 			matches = append(matches, r)
     35 		}
     36 	}
     37 	return matches
     38 }
     39 
     40 func resolveFromRoot(q dnsmessage.Question) (dnsmessage.Message, error) {
     41 	return resolve(q, roots, 0)
     42 }
     43 
     44 func resolve(q dnsmessage.Question, next []net.IP, depth int) (dnsmessage.Message, error) {
     45 	var rmsg dnsmessage.Message
     46 	var err error
     47 	if rrs, ok := lookup(q.Name, q.Type); ok {
     48 		fmt.Fprintln(os.Stderr, "cache served", q.Name, q.Type)
     49 		return dnsmessage.Message{Answers: rrs}, nil
     50 	}
     51 	fmt.Fprintln(os.Stderr, "cache miss", q.Name, q.Type)
     52 
     53 	if depth > 12 {
     54 		return dnsmessage.Message{}, fmt.Errorf("query loop")
     55 	}
     56 
     57 	for _, ip := range next {
     58 		// Aussie Broadband doesn't support IPv6 yet!
     59 		if isIPv6(ip) {
     60 			continue
     61 		}
     62 		fmt.Fprintf(os.Stderr, "asking %s for %s %s\n", ip, q.Name, q.Type)
     63 		rmsg, err = dns.Ask(q, ip2dial(ip))
     64 		if rmsg.Header.Authoritative {
     65 			fmt.Println("got auth answer")
     66 			insert(q.Name, q.Type, rmsg.Answers)
     67 			fmt.Fprintln(os.Stderr, "cached", q.Name, q.Type)
     68 			return rmsg, err
     69 		} else if rmsg.Header.RCode == dnsmessage.RCodeSuccess && err == nil {
     70 			break
     71 		}
     72 	}
     73 	if err != nil {
     74 		return dnsmessage.Message{}, fmt.Errorf("resolve %s: %w", q.Name, err)
     75 	}
     76 	fmt.Println("no auth answer")
     77 
     78 	// cache resource records from authorities, additionals sections if we
     79 	// don't have them already (i.e. from authoritative answers)
     80 	if len(rmsg.Authorities) > 0 {
     81 		if _, ok := lookup(rmsg.Authorities[0].Header.Name, rmsg.Authorities[0].Header.Type); !ok {
     82 			insert(rmsg.Authorities[0].Header.Name, rmsg.Authorities[0].Header.Type, rmsg.Authorities)
     83 			fmt.Fprintln(os.Stderr, "cached", q.Name, q.Type)
     84 		}
     85 	}
     86 	for _, a := range rmsg.Additionals {
     87 		if _, ok := lookup(a.Header.Name, a.Header.Type); !ok {
     88 			matches := filterRRs(rmsg.Additionals, a.Header.Name, a.Header.Type)
     89 			insert(a.Header.Name, a.Header.Type, matches)
     90 			fmt.Fprintln(os.Stderr, "cached", q.Name, q.Type)
     91 		}
     92 	}
     93 
     94 	// get the IP addresses of the nameservers we were told about, then
     95 	// ask the same question to them
     96 	if len(rmsg.Authorities) > 0 {
     97 		for _, a := range rmsg.Authorities {
     98 			switch b := a.Body.(type) {
     99 			case *dnsmessage.NSResource:
    100 				newq := dnsmessage.Question{Name: b.NS, Type: dnsmessage.TypeA, Class: q.Class}
    101 				rmsg, err = resolveFromRoot(newq)
    102 				if err != nil {
    103 					continue
    104 				}
    105 				if len(rmsg.Answers) > 0 {
    106 					return resolve(q, dns.ExtractIPs(rmsg.Answers), depth+1)
    107 				}
    108 				return resolve(q, dns.ExtractIPs(rmsg.Additionals), depth+1)
    109 			default:
    110 				return rmsg, fmt.Errorf("unexpected authority resource type %s", a.Header.Type)
    111 			}
    112 		}
    113 	}
    114 
    115 	// return our best guess anyway
    116 	return rmsg, fmt.Errorf("resolve %s: no more nameservers to ask", q.Name)
    117 }