Skip to content

Instantly share code, notes, and snippets.

@s-macke
Last active January 26, 2024 13:56
Show Gist options
  • Save s-macke/361715a580fd7c602ab8f09707e717bd to your computer and use it in GitHub Desktop.
Save s-macke/361715a580fd7c602ab8f09707e717bd to your computer and use it in GitHub Desktop.
Top-Down iterative DNS resolver to understand DNS
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