|
| 1 | +package hinting |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "errors" |
| 6 | + "github.com/miekg/dns" |
| 7 | + "math/rand" |
| 8 | + "net" |
| 9 | + "net/netip" |
| 10 | +) |
| 11 | + |
| 12 | +var ( |
| 13 | + akamaiDomain = "akamai.net." |
| 14 | + akamaiNameserver = "zh.akamaitech.net" |
| 15 | + quad9DNSResolver = "9.9.9.9:53" |
| 16 | +) |
| 17 | + |
| 18 | +// reverseLookupDomains obtains the reverse DNS entries for IP addr to derive DNS search domain candidates. |
| 19 | +// Uses in-addr.arpa and ip6.arpa domains to lookup reverse pointer records. |
| 20 | +func reverseLookupDomains(addr netip.Addr) (domains []string) { |
| 21 | + hostnames, err := net.LookupAddr(addr.String()) |
| 22 | + if err != nil { |
| 23 | + return |
| 24 | + } |
| 25 | + return domainsFromHostnames(hostnames) |
| 26 | +} |
| 27 | + |
| 28 | +// Fallbacks to obtain an external public IP address using DNS. |
| 29 | + |
| 30 | +// getAkaNS returns one random authoritative nameserver for akamai.net. |
| 31 | +func getAkaNS() (nameserver string, err error) { |
| 32 | + // try default resolver |
| 33 | + resolver := net.Resolver{} |
| 34 | + ctx := context.TODO() |
| 35 | + nameservers, err := resolver.LookupNS(ctx, akamaiDomain) |
| 36 | + if err == nil { |
| 37 | + return nameservers[rand.Intn(len(nameservers))].Host, err |
| 38 | + } |
| 39 | + |
| 40 | + m := new(dns.Msg) |
| 41 | + m.SetQuestion(akamaiDomain, dns.TypeNS) |
| 42 | + // try Quad9 |
| 43 | + in, err := dns.Exchange(m, quad9DNSResolver) |
| 44 | + if err != nil { |
| 45 | + return "", err |
| 46 | + } |
| 47 | + if len(in.Answer) < 1 { |
| 48 | + err = errors.New("getAkaNS: No DNS RR answer") |
| 49 | + return "", err |
| 50 | + } |
| 51 | + if ns, ok := in.Answer[rand.Intn(len(in.Answer))].(*dns.NS); ok { |
| 52 | + return ns.Ns, nil |
| 53 | + } |
| 54 | + return "", errors.New("getAkaNS: Invalid NS record") |
| 55 | +} |
| 56 | + |
| 57 | +// getExternalIP returns the external IP used for DNS resolution of the executing host using nameserver. |
| 58 | +func getExternalIP(nameserver string) (addr netip.Addr, err error) { |
| 59 | + if nameserver == "" { |
| 60 | + // Default external authoritative nameserver |
| 61 | + nameserver = akamaiNameserver |
| 62 | + } |
| 63 | + m := new(dns.Msg) |
| 64 | + // The akamai.net nameservers reply to queries for the name `whoami` |
| 65 | + // with the IP address of the host sending the query. |
| 66 | + m.SetQuestion("whoami.akamai.net.", dns.TypeA) |
| 67 | + in, err := dns.Exchange(m, net.JoinHostPort(nameserver, "53")) |
| 68 | + if err != nil { |
| 69 | + return netip.Addr{}, err |
| 70 | + } |
| 71 | + if len(in.Answer) < 1 { |
| 72 | + err = errors.New("getExternalIP: No DNS RR answer") |
| 73 | + return netip.Addr{}, err |
| 74 | + } |
| 75 | + if a, ok := in.Answer[0].(*dns.A); ok { |
| 76 | + if addr, ok := netip.AddrFromSlice(a.A); ok { |
| 77 | + return addr, nil |
| 78 | + } |
| 79 | + return netip.Addr{}, &net.AddrError{Err: "invalid IP address", Addr: a.A.String()} |
| 80 | + } |
| 81 | + return netip.Addr{}, errors.New("getExternalIP: Invalid A record") |
| 82 | +} |
| 83 | + |
| 84 | +// queryExternalIP returns the external IP used for DNS resolution of the executing host. |
| 85 | +func queryExternalIP() (addr netip.Addr, err error) { |
| 86 | + // Try with default NS |
| 87 | + addr, err = getExternalIP("") |
| 88 | + if err != nil { |
| 89 | + // try with looking up alternative NS |
| 90 | + ns, err := getAkaNS() |
| 91 | + if err == nil { |
| 92 | + addr, err = getExternalIP(ns) |
| 93 | + } |
| 94 | + } |
| 95 | + return |
| 96 | +} |
0 commit comments