|
1 | 1 | package hinting
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "net" |
| 5 | + "net/netip" |
4 | 6 | "slices"
|
5 | 7 | "strings"
|
6 | 8 | )
|
7 | 9 |
|
| 10 | +// getFallbackSearchDomains provides DNS search domain candidates to dnsChanWritable. |
| 11 | +// The results are only retrieved and returned if no DNS search domains were provided by |
| 12 | +// the dispatcher DNSInfo channel. The number of external entities contacted is minimized. |
| 13 | +// The domains are obtained from reverse DNS lookups and alternatively whois contact info, |
| 14 | +// and returned along with the resolvers learned from the dispatcher DNSInfo channel. |
| 15 | +func getFallbackSearchDomains(dnsChanWritable chan<- DNSInfo) { |
| 16 | + resolverSet := make(map[netip.Addr]struct{}) |
| 17 | + searchDomainSet := make(map[string]struct{}) |
| 18 | + // fallback for DNS search domains was started, so dnsInfoDispatcher hinting.dispatcher |
| 19 | + // was already started. |
| 20 | + dnsChanReadable := dispatcher.getDNSConfig() |
| 21 | +Fallback: |
| 22 | + for { |
| 23 | + select { |
| 24 | + case dnsInfo := <-dnsChanReadable: |
| 25 | + // collect info from happy path |
| 26 | + for _, resolver := range dnsInfo.resolvers { |
| 27 | + resolverSet[resolver] = struct{}{} |
| 28 | + } |
| 29 | + for _, searchDomain := range dnsInfo.searchDomains { |
| 30 | + searchDomainSet[searchDomain] = struct{}{} |
| 31 | + } |
| 32 | + case <-dnsInfoDone: |
| 33 | + // start with fallback |
| 34 | + break Fallback |
| 35 | + } |
| 36 | + } |
| 37 | + if len(searchDomainSet) > 0 { |
| 38 | + // do not attempt fallback as authoritative locally configured |
| 39 | + // search domains were found. |
| 40 | + return |
| 41 | + } |
| 42 | + |
| 43 | + // Collect domain information from DNS reverse lookup |
| 44 | + ips := getPublicAddresses() |
| 45 | + if len(ips) == 0 { |
| 46 | + // attempt fallback to reverse lookup of externally observed IP, |
| 47 | + // if all configured IPs are private. |
| 48 | + ip, err := queryExternalIP() |
| 49 | + if err == nil { |
| 50 | + ips = append(ips, *ip) |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + for _, ip := range ips { |
| 55 | + domains := reverseLookupDomains(ip) |
| 56 | + for _, searchDomain := range domains { |
| 57 | + searchDomainSet[searchDomain] = struct{}{} |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + resolvers := make([]netip.Addr, 0, len(resolverSet)) |
| 62 | + for k := range resolverSet { |
| 63 | + resolvers = append(resolvers, k) |
| 64 | + } |
| 65 | + searchDomains := make([]string, 0, len(searchDomainSet)) |
| 66 | + for k := range searchDomainSet { |
| 67 | + searchDomains = append(searchDomains, k) |
| 68 | + } |
| 69 | + dnsInfo := DNSInfo{resolvers: resolvers, searchDomains: searchDomains} |
| 70 | + dnsInfoWriters.Add(1) |
| 71 | + select { |
| 72 | + case <-dnsInfoFallbackDone: |
| 73 | + // Ignore dnsInfo value, done publishing |
| 74 | + default: |
| 75 | + dnsChanWritable <- dnsInfo |
| 76 | + } |
| 77 | + dnsInfoWriters.Done() |
| 78 | + return |
| 79 | +} |
| 80 | + |
| 81 | +func getPublicAddresses() (ips []netip.Addr) { |
| 82 | + ifaces, err := net.Interfaces() |
| 83 | + if err != nil { |
| 84 | + return |
| 85 | + } |
| 86 | + for _, iface := range ifaces { |
| 87 | + addrs, err := iface.Addrs() |
| 88 | + if err != nil { |
| 89 | + continue |
| 90 | + } |
| 91 | + for _, addr := range addrs { |
| 92 | + ip, err := netip.ParseAddr(addr.String()) |
| 93 | + if err != nil { |
| 94 | + continue |
| 95 | + } |
| 96 | + if !ip.IsPrivate() { |
| 97 | + ips = append(ips, ip) |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + return |
| 102 | +} |
| 103 | + |
8 | 104 | func domainsFromHostnames(hostnames []string) (domains []string) {
|
9 | 105 | for _, hostname := range hostnames {
|
10 | 106 | labels := strings.Split(strings.TrimRight(hostname, "."), ".")
|
|
0 commit comments