1package main
2
3import (
4	"errors"
5	"fmt"
6	"net"
7	"os"
8	"strings"
9	"time"
10
11	"github.com/miekg/dns"
12)
13
14const myResolverHost string = "resolver.dnscrypt.info."
15const nonexistentName string = "nonexistent-zone.dnscrypt-test."
16
17func resolveQuery(server string, qName string, qType uint16) (*dns.Msg, error) {
18	client := new(dns.Client)
19	client.ReadTimeout = 2 * time.Second
20	msg := &dns.Msg{
21		MsgHdr: dns.MsgHdr{
22			RecursionDesired: true,
23			Opcode:           dns.OpcodeQuery,
24		},
25		Question: make([]dns.Question, 1),
26	}
27	options := &dns.OPT{
28		Hdr: dns.RR_Header{
29			Name:   ".",
30			Rrtype: dns.TypeOPT,
31		},
32	}
33	msg.Extra = append(msg.Extra, options)
34	options.SetDo()
35	options.SetUDPSize(uint16(MaxDNSPacketSize))
36	msg.Question[0] = dns.Question{Name: qName, Qtype: qType, Qclass: dns.ClassINET}
37	msg.Id = dns.Id()
38	for i := 0; i < 3; i++ {
39		response, rtt, err := client.Exchange(msg, server)
40		if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
41			client.ReadTimeout *= 2
42			continue
43		}
44		_ = rtt
45		if err != nil {
46			return nil, err
47		}
48		return response, nil
49	}
50	return nil, errors.New("Timeout")
51}
52
53func Resolve(server string, name string, singleResolver bool) {
54	parts := strings.SplitN(name, ",", 2)
55	if len(parts) == 2 {
56		name, server = parts[0], parts[1]
57		singleResolver = true
58	}
59
60	host, port := ExtractHostAndPort(server, 53)
61	if host == "0.0.0.0" {
62		host = "127.0.0.1"
63	} else if host == "[::]" {
64		host = "[::1]"
65	}
66	server = fmt.Sprintf("%s:%d", host, port)
67
68	fmt.Printf("Resolving [%s] using %s port %d\n\n", name, host, port)
69	name = dns.Fqdn(name)
70
71	cname := name
72
73	for once := true; once; once = false {
74		response, err := resolveQuery(server, myResolverHost, dns.TypeA)
75		if err != nil {
76			fmt.Printf("Unable to resolve: [%s]\n", err)
77			os.Exit(1)
78		}
79		fmt.Printf("Resolver      : ")
80		res := make([]string, 0)
81		for _, answer := range response.Answer {
82			if answer.Header().Class != dns.ClassINET {
83				continue
84			}
85			var ip string
86			if answer.Header().Rrtype == dns.TypeA {
87				ip = answer.(*dns.A).A.String()
88			} else if answer.Header().Rrtype == dns.TypeAAAA {
89				ip = answer.(*dns.AAAA).AAAA.String()
90			}
91			if rev, err := dns.ReverseAddr(ip); err == nil {
92				response, err = resolveQuery(server, rev, dns.TypePTR)
93				if err != nil {
94					break
95				}
96				for _, answer := range response.Answer {
97					if answer.Header().Rrtype != dns.TypePTR || answer.Header().Class != dns.ClassINET {
98						continue
99					}
100					ip = ip + " (" + answer.(*dns.PTR).Ptr + ")"
101					break
102				}
103			}
104			res = append(res, ip)
105		}
106		if len(res) == 0 {
107			fmt.Println("-")
108		} else {
109			fmt.Println(strings.Join(res, ", "))
110		}
111	}
112
113	if singleResolver {
114		for once := true; once; once = false {
115			fmt.Printf("Lying         : ")
116			response, err := resolveQuery(server, nonexistentName, dns.TypeA)
117			if err != nil {
118				break
119			}
120			if response.Rcode == dns.RcodeSuccess {
121				fmt.Println("yes. That resolver returns wrong responses")
122			} else if response.Rcode == dns.RcodeNameError {
123				fmt.Println("no")
124			} else {
125				fmt.Printf("unknown - query returned %s\n", dns.RcodeToString[response.Rcode])
126			}
127
128			if response.Rcode == dns.RcodeNameError {
129				fmt.Printf("DNSSEC        : ")
130				if response.AuthenticatedData {
131					fmt.Println("yes, the resolver supports DNSSEC")
132				} else {
133					fmt.Println("no, the resolver doesn't support DNSSEC")
134				}
135			}
136		}
137	}
138
139	fmt.Println("")
140
141cname:
142	for once := true; once; once = false {
143		fmt.Printf("Canonical name: ")
144		for i := 0; i < 100; i++ {
145			response, err := resolveQuery(server, cname, dns.TypeCNAME)
146			if err != nil {
147				break cname
148			}
149			found := false
150			for _, answer := range response.Answer {
151				if answer.Header().Rrtype != dns.TypeCNAME || answer.Header().Class != dns.ClassINET {
152					continue
153				}
154				cname = answer.(*dns.CNAME).Target
155				found = true
156				break
157			}
158			if !found {
159				break
160			}
161		}
162		fmt.Println(cname)
163	}
164
165	fmt.Println("")
166
167	for once := true; once; once = false {
168		fmt.Printf("IPv4 addresses: ")
169		response, err := resolveQuery(server, cname, dns.TypeA)
170		if err != nil {
171			break
172		}
173		ipv4 := make([]string, 0)
174		for _, answer := range response.Answer {
175			if answer.Header().Rrtype != dns.TypeA || answer.Header().Class != dns.ClassINET {
176				continue
177			}
178			ipv4 = append(ipv4, answer.(*dns.A).A.String())
179		}
180		if len(ipv4) == 0 {
181			fmt.Println("-")
182		} else {
183			fmt.Println(strings.Join(ipv4, ", "))
184		}
185	}
186
187	for once := true; once; once = false {
188		fmt.Printf("IPv6 addresses: ")
189		response, err := resolveQuery(server, cname, dns.TypeAAAA)
190		if err != nil {
191			break
192		}
193		ipv6 := make([]string, 0)
194		for _, answer := range response.Answer {
195			if answer.Header().Rrtype != dns.TypeAAAA || answer.Header().Class != dns.ClassINET {
196				continue
197			}
198			ipv6 = append(ipv6, answer.(*dns.AAAA).AAAA.String())
199		}
200		if len(ipv6) == 0 {
201			fmt.Println("-")
202		} else {
203			fmt.Println(strings.Join(ipv6, ", "))
204		}
205	}
206
207	fmt.Println("")
208
209	for once := true; once; once = false {
210		fmt.Printf("Name servers  : ")
211		response, err := resolveQuery(server, cname, dns.TypeNS)
212		if err != nil {
213			break
214		}
215		nss := make([]string, 0)
216		for _, answer := range response.Answer {
217			if answer.Header().Rrtype != dns.TypeNS || answer.Header().Class != dns.ClassINET {
218				continue
219			}
220			nss = append(nss, answer.(*dns.NS).Ns)
221		}
222		if response.Rcode == dns.RcodeNameError {
223			fmt.Println("name does not exist")
224		} else if response.Rcode != dns.RcodeSuccess {
225			fmt.Printf("server returned %s", dns.RcodeToString[response.Rcode])
226		} else if len(nss) == 0 {
227			fmt.Println("no name servers found")
228		} else {
229			fmt.Println(strings.Join(nss, ", "))
230		}
231		fmt.Printf("DNSSEC signed : ")
232		if response.AuthenticatedData {
233			fmt.Println("yes")
234		} else {
235			fmt.Println("no")
236		}
237	}
238
239	for once := true; once; once = false {
240		fmt.Printf("Mail servers  : ")
241		response, err := resolveQuery(server, cname, dns.TypeMX)
242		if err != nil {
243			break
244		}
245		mxs := make([]string, 0)
246		for _, answer := range response.Answer {
247			if answer.Header().Rrtype != dns.TypeMX || answer.Header().Class != dns.ClassINET {
248				continue
249			}
250			mxs = append(mxs, answer.(*dns.MX).Mx)
251		}
252		if len(mxs) == 0 {
253			fmt.Println("no mail servers found")
254		} else if len(mxs) > 1 {
255			fmt.Printf("%d mail servers found\n", len(mxs))
256		} else {
257			fmt.Println("1 mail servers found")
258		}
259	}
260
261	fmt.Println("")
262
263	for once := true; once; once = false {
264		fmt.Printf("HTTPS alias   : ")
265		response, err := resolveQuery(server, cname, dns.TypeHTTPS)
266		if err != nil {
267			break
268		}
269		aliases := make([]string, 0)
270		for _, answer := range response.Answer {
271			if answer.Header().Rrtype != dns.TypeHTTPS || answer.Header().Class != dns.ClassINET {
272				continue
273			}
274			https := answer.(*dns.HTTPS)
275			if https.Priority != 0 || len(https.Target) < 2 {
276				continue
277			}
278			aliases = append(aliases, https.Target)
279		}
280		if len(aliases) == 0 {
281			fmt.Println("-")
282		} else {
283			fmt.Println(strings.Join(aliases, ", "))
284		}
285
286		fmt.Printf("HTTPS info    : ")
287		info := make([]string, 0)
288		for _, answer := range response.Answer {
289			if answer.Header().Rrtype != dns.TypeHTTPS || answer.Header().Class != dns.ClassINET {
290				continue
291			}
292			https := answer.(*dns.HTTPS)
293			if https.Priority == 0 || len(https.Target) > 1 {
294				continue
295			}
296			for _, value := range https.Value {
297				info = append(info, fmt.Sprintf("[%s]=[%s]", value.Key(), value.String()))
298			}
299		}
300		if len(info) == 0 {
301			fmt.Println("-")
302		} else {
303			fmt.Println(strings.Join(info, ", "))
304		}
305	}
306
307	fmt.Println("")
308
309	for once := true; once; once = false {
310		fmt.Printf("Host info     : ")
311		response, err := resolveQuery(server, cname, dns.TypeHINFO)
312		if err != nil {
313			break
314		}
315		hinfo := make([]string, 0)
316		for _, answer := range response.Answer {
317			if answer.Header().Rrtype != dns.TypeHINFO || answer.Header().Class != dns.ClassINET {
318				continue
319			}
320			hinfo = append(hinfo, fmt.Sprintf("%s %s", answer.(*dns.HINFO).Cpu, answer.(*dns.HINFO).Os))
321		}
322		if len(hinfo) == 0 {
323			fmt.Println("-")
324		} else {
325			fmt.Println(strings.Join(hinfo, ", "))
326		}
327	}
328
329	for once := true; once; once = false {
330		fmt.Printf("TXT records   : ")
331		response, err := resolveQuery(server, cname, dns.TypeTXT)
332		if err != nil {
333			break
334		}
335		txt := make([]string, 0)
336		for _, answer := range response.Answer {
337			if answer.Header().Rrtype != dns.TypeTXT || answer.Header().Class != dns.ClassINET {
338				continue
339			}
340			txt = append(txt, strings.Join(answer.(*dns.TXT).Txt, " "))
341		}
342		if len(txt) == 0 {
343			fmt.Println("-")
344		} else {
345			fmt.Println(strings.Join(txt, ", "))
346		}
347	}
348
349	fmt.Println("")
350}
351