Last active
January 26, 2024 13:56
-
-
Save s-macke/361715a580fd7c602ab8f09707e717bd to your computer and use it in GitHub Desktop.
Top-Down iterative DNS resolver to understand DNS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"github.com/miekg/dns" | |
"log" | |
"net/http" | |
) | |
const rootdnsv4 = "198.41.0.4" | |
const rootdnsv6 = "[2001:503:ba3e::2:30]" | |
var rootdns = "" | |
//const rootdns = "8.8.8.8" | |
type DnsRequest struct { | |
output string | |
qtype uint16 | |
} | |
func SearchInExtraForIp(name string, qtype uint16, response *dns.Msg) string { | |
for _, r := range response.Extra { | |
if r.Header().Class != dns.ClassINET { | |
continue | |
} | |
if r.Header().Rrtype != qtype { | |
continue | |
} | |
if r.Header().Name != name { | |
continue | |
} | |
if qtype == dns.TypeA { | |
return r.(*dns.A).A.String() | |
} else { | |
return "[" + r.(*dns.AAAA).AAAA.String() + "]" | |
} | |
//mt.Println(r.Header().Rrtype, r.Header().Name, r.Header().Class) | |
} | |
return "" | |
} | |
func Exchange(domain string, dnsserver string, d *DnsRequest, iter int) string { | |
if iter > 20 { | |
return "" | |
} | |
c := new(dns.Client) | |
c.Net = "udp" // udp4 or udp6 | |
m := &dns.Msg{ | |
MsgHdr: dns.MsgHdr{ | |
Authoritative: false, | |
AuthenticatedData: false, | |
CheckingDisabled: false, | |
RecursionDesired: false, | |
Opcode: dns.OpcodeQuery, | |
}, | |
Question: make([]dns.Question, 1), | |
} | |
m.Question[0] = dns.Question{Name: dns.Fqdn(domain), Qtype: d.qtype, Qclass: dns.ClassINET} | |
m.Id = dns.Id() | |
response, _, err := c.Exchange(m, dnsserver) | |
if err != nil { | |
fmt.Printf(";; %s\n", err.Error()) | |
panic("Stooping") | |
} | |
if response.Id != m.Id { | |
panic("Id mismatch") | |
} | |
fmt.Println("") | |
fmt.Printf("Question for '%s' DNS Server '%s'\n", domain, dnsserver) | |
d.output += fmt.Sprintf("<li>Question DNS Server '%s' for '%s'<br>", dnsserver, domain) | |
d.output += fmt.Sprintf("<pre>\n%v\n</pre></li>", response) | |
for _, r := range response.Answer { | |
if r.Header().Class != dns.ClassINET { | |
continue | |
} | |
if r.Header().Rrtype != d.qtype { | |
continue | |
} | |
ip := "" | |
if d.qtype == dns.TypeA { | |
ip = r.(*dns.A).A.String() | |
} else { | |
ip = "[" + r.(*dns.AAAA).AAAA.String() + "]" | |
} | |
fmt.Println("Answer received:", ip) | |
d.output += fmt.Sprintf("Got answer '%s'<br>", ip) | |
return ip | |
} | |
ip := "" | |
name := "" | |
for _, r := range response.Ns { | |
if r.Header().Class != dns.ClassINET { | |
continue | |
} | |
if r.Header().Rrtype == dns.TypeNS { | |
name = r.(*dns.NS).Ns | |
//fmt.Println(name) | |
ip = SearchInExtraForIp(name, d.qtype, response) | |
if len(ip) > 0 { | |
break | |
} | |
} | |
} | |
if len(ip) > 0 { | |
fmt.Println("Got ip for NS: ", ip) | |
d.output += fmt.Sprintf("Next: Follow ip '%s'<br><br>", ip) | |
return Exchange(domain, ip+":53", d, iter+1) | |
} else if len(name) > 0 && name != domain { | |
fmt.Println("Got domain for NS") | |
d.output += fmt.Sprintf("Next: Resolve domain name '%s'<br><br>", name) | |
d.output += "\n<ul class=\"nested\">\n" | |
d.output += `<p><h3>Question root DNS Server for '` + name + `''</h3></p>` | |
result := Exchange(name, rootdns+":53", d, iter+1) | |
fmt.Println("result:", result) | |
d.output += "\n</ul>\n" | |
if len(result) > 0 { | |
return Exchange(domain, result+":53", d, iter+1) | |
} | |
} | |
return "" | |
} | |
func handler(w http.ResponseWriter, r *http.Request) { | |
domain := r.URL.Query().Get("domain") | |
if len(domain) == 0 { | |
domain = "www.simulationcorner.net" | |
} | |
var qtype uint16 | |
qtype = dns.TypeA | |
checkedipv4 := "checked" | |
checkedipv6 := "" | |
rootdns = rootdnsv4 | |
ipx := r.URL.Query().Get("ipx") | |
if ipx == "IPv6" { | |
qtype = dns.TypeAAAA | |
checkedipv4 = "" | |
checkedipv6 = "checked" | |
rootdns = rootdnsv6 | |
} | |
output := ` | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<style> | |
form { | |
display: inline-block; | |
border:2px solid Black; | |
} | |
pre { | |
display: inline-block; | |
border:2px solid Black; | |
} | |
li > * { | |
vertical-align: text-top; | |
} | |
/* | |
ul { | |
vertical-align: text-top; | |
} | |
li { | |
vertical-align: text-top; | |
list-style-type: circle; | |
} | |
*/ | |
</style> | |
</head> | |
<body> | |
<h3>DNS Trace for Root DNS IPV4: `+ rootdnsv4+` or IPV6: ` + rootdnsv6+`</h3> | |
<form action="/" method="GET"> | |
<label for="domain">Domain:</label><br> | |
<input type="text" id="domain" name="domain" value="` + domain + `"><br> | |
<input type="radio" id="ip4" name="ipx" value="IPv4"` + checkedipv4 + `> | |
<label for="ip4">IPv4</label><br> | |
<input type="radio" id="ip6" name="ipx" value="IPv6"` + checkedipv6 + `> | |
<label for="ip6">IPv6</label><br> | |
<input type="submit" value="Submit"> | |
</form> | |
<hr/> | |
<ul id="myUL"> | |
<p><h3>Question root DNS Server for ` + domain + `</h3></p>` | |
output += ExchangeAndBuildDocument(domain, qtype) | |
output += ` | |
</ul> | |
</body> | |
</html> | |
` | |
_, _ = fmt.Fprintf(w, "%s", output) | |
} | |
func Listen() { | |
//fs := http.FileServer(http.Dir("./static")) | |
//http.Handle("/", fs) | |
http.HandleFunc("/", handler) | |
log.Println("Listening on :3000...") | |
err := http.ListenAndServe(":3000", nil) | |
if err != nil { | |
log.Fatal(err) | |
} | |
} | |
func ExchangeAndBuildDocument(domain string, qtype uint16) string { | |
var d DnsRequest | |
d.qtype = qtype | |
d.output = ` | |
<style> | |
/* Remove default bullets */ | |
ul, #myUL { | |
list-style-type: none; | |
} | |
/* Remove margins and padding from the parent ul */ | |
#myUL { | |
margin: 0; | |
padding: 0; | |
} | |
/* Style the caret/arrow */ | |
.caret { | |
cursor: pointer; | |
user-select: none; /* Prevent text selection */ | |
} | |
/* Create the caret/arrow with a unicode, and style it */ | |
.caret::before { | |
content: "\25B6"; | |
color: black; | |
display: inline-block; | |
margin-right: 6px; | |
} | |
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */ | |
.caret-down::before { | |
transform: rotate(90deg); | |
} | |
/* Hide the nested list */ | |
.nested { | |
display: none; | |
} | |
/* Show the nested list when the user clicks on the caret/arrow (with JavaScript) */ | |
.active { | |
display: block; | |
} | |
</style> | |
` | |
d.output = "" | |
Exchange(domain, rootdns+":53", &d, 0) | |
return d.output | |
} | |
func main() { | |
Listen() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment