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 }