From 6002bd16fa77eb98d7aa96376410d7426ced8df2 Mon Sep 17 00:00:00 2001
From: Hugo Gomes <hugo@hugogomes.eu>
Date: Fri, 23 Jun 2023 21:31:10 +0100
Subject: [PATCH] part3 done

---
 dnsweekend/part3.go      | 62 ++++++++++++++++++++++++++++++++++++++++
 dnsweekend/part3_test.go | 22 ++++++++++++++
 2 files changed, 84 insertions(+)
 create mode 100644 dnsweekend/part3.go
 create mode 100644 dnsweekend/part3_test.go

diff --git a/dnsweekend/part3.go b/dnsweekend/part3.go
new file mode 100644
index 0000000..6138e9f
--- /dev/null
+++ b/dnsweekend/part3.go
@@ -0,0 +1,62 @@
+// Package dnsweekend https://implement-dns.wizardzines.com/
+package dnsweekend
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// Resolve a domain name to an IP address using DNS recursion
+// (i.e. keep asking other DNS servers until we get an answer)
+// This is a very simple implementation of a DNS resolver
+// (it doesn't support caching, timeouts, retries, etc.)
+// but it's enough to resolve most domain names to IP addresses
+// (including the root nameservers)
+func Resolve(domain string, recordType RecordType) (string, error) {
+	// From https://www.iana.org/domains/root/servers
+	// You can use any of these servers to resolve any domain
+	nameserver := "192.33.4.12"
+
+	for {
+		fmt.Printf("Querying %s for domain %s\n", nameserver, domain)
+		resp, _ := SendQuery(domain, nameserver+":53", recordType)
+		respReader := bytes.NewReader(resp)
+		dnsPacket := parseDNSPacket(respReader)
+
+		// If we got an answer, return it
+		// (we only care about the first answer)
+		for _, answer := range dnsPacket.Answers {
+			if answer.Type == uint16(A) {
+				ip := answer.data
+				return dataIPtoString(ip), nil
+			}
+		}
+		// If we didn't get an answer, try to find a nameserver
+		// and use that instead of the default nameserver
+		var newNameserver string
+		for _, additional := range dnsPacket.Additionals {
+			if additional.Type == uint16(A) {
+				newNameserver = dataIPtoString(additional.data)
+				if len(newNameserver) > 0 {
+					break
+				}
+			}
+		}
+		if newNameserver != "" {
+			nameserver = newNameserver
+			continue
+		}
+		// If we didn't find a nameserver, try to find an authority
+		// and use that instead of the default nameserver (and loop)
+		// until we find an IP address for a nameserver
+		for _, authority := range dnsPacket.Authorities {
+			if authority.Type == uint16(NS) {
+				nameserverDomain := string(authority.data)
+				nameserver, _ = Resolve(nameserverDomain, A)
+				break
+			}
+		}
+
+		panic("something went wrong")
+	}
+}
diff --git a/dnsweekend/part3_test.go b/dnsweekend/part3_test.go
new file mode 100644
index 0000000..07a0e8b
--- /dev/null
+++ b/dnsweekend/part3_test.go
@@ -0,0 +1,22 @@
+package dnsweekend
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestResolve(t *testing.T) {
+	domain1 := "google.com"
+	ip1, err := Resolve(domain1, A)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	fmt.Printf("%s: %s\n", domain1, ip1)
+
+	domain2 := "facebook.com"
+	ip2, err := Resolve(domain2, A)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	fmt.Printf("%s: %s\n", domain2, ip2)
+}