Skip to content

Commit 949acab

Browse files
committed
search domain discovery: add whois lookup fallback
Lookup the contact information for the external public IP to derive potential search domains
1 parent 78637ab commit 949acab

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

hinting/dns_search_domain_fallbacks.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ Fallback:
5858
}
5959
}
6060

61+
// Collect domain information from WHOIS contact information
62+
if len(searchDomainSet) == 0 {
63+
// attempt fallback to WHOIS contact information heuristics as
64+
// reverse DNS lookups provided no results.
65+
for _, ip := range ips {
66+
domains := reverseLookupWhois(ip)
67+
for _, searchDomain := range domains {
68+
searchDomainSet[searchDomain] = struct{}{}
69+
}
70+
}
71+
}
72+
6173
resolvers := make([]netip.Addr, 0, len(resolverSet))
6274
for k := range resolverSet {
6375
resolvers = append(resolvers, k)
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package hinting
2+
3+
import (
4+
"io"
5+
"net"
6+
"net/mail"
7+
"net/netip"
8+
"slices"
9+
10+
//"slices"
11+
"strings"
12+
)
13+
14+
var (
15+
ianaWHOIS = "whois.iana.org"
16+
rirWHOIS = []string{
17+
"whois.afrinic.net",
18+
"whois.lacnic.net",
19+
"whois.apnic.net",
20+
"whois.ripe.net",
21+
"whois.arin.net",
22+
}
23+
)
24+
25+
func reverseLookupWhois(addr netip.Addr) (domains []string) {
26+
response, err := resolveWhoisRedirects(addr, ianaWHOIS)
27+
if err != nil {
28+
return
29+
}
30+
return extractEmailDomains(response)
31+
}
32+
33+
func resolveWhoisRedirects(addr netip.Addr, server string) (response string, err error) {
34+
whoisServer := server
35+
for i := 0; i < 10; i++ {
36+
// arbitrary upper limit of 10 redirects allowed, usually no more than 3 (IANA, RIR legacy registration, RIR)
37+
var raddr *net.TCPAddr
38+
raddr, err = net.ResolveTCPAddr("tcp4", net.JoinHostPort(whoisServer, "43"))
39+
if err != nil {
40+
return
41+
}
42+
response, err = queryWhois(raddr, addr)
43+
if err != nil {
44+
return
45+
}
46+
var entry string
47+
for _, entry = range strings.Split(response, "\n") {
48+
if strings.HasPrefix(entry, "refer:") ||
49+
strings.HasPrefix(entry, "ReferralServer:") {
50+
break
51+
}
52+
}
53+
if strings.HasPrefix(entry, "refer:") {
54+
value := strings.TrimPrefix(entry, "refer:")
55+
whoisRefer := strings.TrimSpace(value)
56+
if slices.Contains(rirWHOIS, whoisRefer) {
57+
whoisServer = whoisRefer
58+
continue
59+
}
60+
}
61+
if strings.HasPrefix(entry, "ReferralServer:") {
62+
value := strings.TrimPrefix(entry, "ReferralServer:")
63+
whoisRefer := strings.TrimPrefix(strings.TrimSpace(value), "whois://")
64+
//if slices.Contains(rirWHOIS, whoisRefer) {
65+
// whoisServer = whoisRefer
66+
// continue
67+
//}
68+
match := false
69+
for _, rserver := range rirWHOIS {
70+
if match = rserver == whoisRefer; match {
71+
break
72+
}
73+
}
74+
if match {
75+
whoisServer = whoisRefer
76+
continue
77+
}
78+
}
79+
break
80+
}
81+
return
82+
}
83+
84+
func queryWhois(serverTCPAddr *net.TCPAddr, queryAddr netip.Addr) (response string, err error) {
85+
var tcpConn *net.TCPConn
86+
var responseBuff []byte
87+
tcpConn, err = net.DialTCP("tcp", nil, serverTCPAddr)
88+
if err != nil {
89+
return
90+
}
91+
defer tcpConn.Close()
92+
if _, err = tcpConn.Write([]byte(queryAddr.String() + "\n")); err != nil {
93+
return
94+
}
95+
responseBuff, err = io.ReadAll(tcpConn)
96+
if err != nil {
97+
return
98+
}
99+
response = string(responseBuff)
100+
return
101+
}
102+
103+
func extractEmailDomains(response string) (domains []string) {
104+
var emailsFields []string
105+
for _, entry := range strings.Split(response, "\n") {
106+
if strings.Contains(entry, "abuse@") ||
107+
strings.Contains(entry, "security@") ||
108+
strings.Contains(entry, "noc@") {
109+
fields := strings.Fields(entry)
110+
for _, field := range fields {
111+
if strings.Contains(field, "@") {
112+
emailsFields = append(emailsFields, field)
113+
}
114+
}
115+
}
116+
}
117+
var hostnames []string
118+
for _, email := range emailsFields {
119+
if address, err := mail.ParseAddress(email); err == nil {
120+
addressLabels := strings.Split(address.Address, "@")
121+
domain := "." + strings.Trim(addressLabels[len(addressLabels)-1], "'.")
122+
hostnames = append(hostnames, domain)
123+
}
124+
}
125+
// filter out RIR domains
126+
return domainsFromHostnames(hostnames)
127+
}

0 commit comments

Comments
 (0)