From 129ad07abf913477e522479a0bd8238b7f7f5cf0 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Singh Date: Mon, 27 May 2024 02:31:04 +0530 Subject: [PATCH] fix: correctly reading the resource entries and parsing each type of records separately Signed-off-by: Aditya Kumar Singh --- README.md | 552 ++++++++++++++++++++++---------------------- cmd/main.go | 65 ++++-- internal/dns/dns.go | 44 ++-- 3 files changed, 342 insertions(+), 319 deletions(-) diff --git a/README.md b/README.md index 0950526..5e6302c 100644 --- a/README.md +++ b/README.md @@ -31,281 +31,280 @@ You will see responses on both the terminals similar to this: Toggle to see output of `server` terminal ```text -Received query: DnsQuestion { name: "www.reddit.com", qtype: A } -attempting lookup of A www.reddit.com with ns 198.41.0.4 -Ok( - DnsPacket { - header: DnsHeader { - id: 6666, - recursion_desired: true, - truncated_message: true, - authoritative_answer: false, - opcode: 0, - response: true, - rescode: NOERROR, - checking_disabled: false, - authed_data: false, - z: false, - recursion_available: false, - questions: 1, - answers: 0, - authoritative_entries: 13, - resource_entries: 11, +DNS server is listening on port 2053... +Received query: &dns.DnsQuestion{Name:"www.reddit.com", QType:1} + +Attempting lookup of 1 www.reddit.com with NS 198.41.0.4 +{ + "header": { + "ID": 6666, + "RecursionDesired": true, + "TruncatedMessage": true, + "AuthoritativeAnswer": false, + "Opcode": 0, + "Response": true, + "ResultCode": 0, + "CheckingDisabled": false, + "AuthedData": false, + "Z": false, + "RecursionAvailable": false, + "Questions": 1, + "Answers": 0, + "AuthoritativeEntries": 13, + "ResourceEntries": 11 + }, + "questions": [ + { + "Name": "www.reddit.com", + "QType": 1 + } + ], + "answers": [], + "authorities": [ + { + "Domain": "com", + "Host": "l.gtld-servers.net", + "TTL": 172800 }, - questions: [ - DnsQuestion { - name: "www.reddit.com", - qtype: A, - }, - ], - answers: [], - authorities: [ - NS { - domain: "com", - host: "e.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "b.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "j.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "m.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "i.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "f.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "a.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "g.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "h.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "l.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "k.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "c.gtld-servers.net", - ttl: 172800, - }, - NS { - domain: "com", - host: "d.gtld-servers.net", - ttl: 172800, - }, - ], - resources: [ - A { - domain: "e.gtld-servers.net", - addr: 192.12.94.30, - ttl: 172800, - }, - AAAA { - domain: "e.gtld-servers.net", - addr: 2001:502:1ca1::30, - ttl: 172800, - }, - A { - domain: "b.gtld-servers.net", - addr: 192.33.14.30, - ttl: 172800, - }, - AAAA { - domain: "b.gtld-servers.net", - addr: 2001:503:231d::2:30, - ttl: 172800, - }, - A { - domain: "j.gtld-servers.net", - addr: 192.48.79.30, - ttl: 172800, - }, - AAAA { - domain: "j.gtld-servers.net", - addr: 2001:502:7094::30, - ttl: 172800, - }, - A { - domain: "m.gtld-servers.net", - addr: 192.55.83.30, - ttl: 172800, - }, - AAAA { - domain: "m.gtld-servers.net", - addr: 2001:501:b1f9::30, - ttl: 172800, - }, - A { - domain: "i.gtld-servers.net", - addr: 192.43.172.30, - ttl: 172800, - }, - AAAA { - domain: "i.gtld-servers.net", - addr: 2001:503:39c1::30, - ttl: 172800, - }, - A { - domain: "f.gtld-servers.net", - addr: 192.35.51.30, - ttl: 172800, - }, - ], - }, -) -attempting lookup of A www.reddit.com with ns 192.12.94.30 -Ok( - DnsPacket { - header: DnsHeader { - id: 6666, - recursion_desired: true, - truncated_message: false, - authoritative_answer: false, - opcode: 0, - response: true, - rescode: NOERROR, - checking_disabled: false, - authed_data: false, - z: false, - recursion_available: false, - questions: 1, - answers: 0, - authoritative_entries: 4, - resource_entries: 1, + { + "Domain": "com", + "Host": "j.gtld-servers.net", + "TTL": 172800 }, - questions: [ - DnsQuestion { - name: "www.reddit.com", - qtype: A, - }, - ], - answers: [], - authorities: [ - NS { - domain: "reddit.com", - host: "ns-557.awsdns-05.net", - ttl: 172800, - }, - NS { - domain: "reddit.com", - host: "ns-378.awsdns-47.com", - ttl: 172800, - }, - NS { - domain: "reddit.com", - host: "ns-1029.awsdns-00.org", - ttl: 172800, - }, - NS { - domain: "reddit.com", - host: "ns-1887.awsdns-43.co.uk", - ttl: 172800, - }, - ], - resources: [ - A { - domain: "ns-378.awsdns-47.com", - addr: 205.251.193.122, - ttl: 172800, - }, - ], - }, -) -attempting lookup of A www.reddit.com with ns 205.251.193.122 -Ok( - DnsPacket { - header: DnsHeader { - id: 6666, - recursion_desired: true, - truncated_message: false, - authoritative_answer: true, - opcode: 0, - response: true, - rescode: NOERROR, - checking_disabled: false, - authed_data: false, - z: false, - recursion_available: false, - questions: 1, - answers: 1, - authoritative_entries: 4, - resource_entries: 0, + { + "Domain": "com", + "Host": "h.gtld-servers.net", + "TTL": 172800 }, - questions: [ - DnsQuestion { - name: "www.reddit.com", - qtype: A, - }, - ], - answers: [ - CNAME { - domain: "www.reddit.com", - host: "reddit.map.fastly.net", - ttl: 10800, - }, - ], - authorities: [ - NS { - domain: "reddit.com", - host: "ns-1029.awsdns-00.org", - ttl: 172800, - }, - NS { - domain: "reddit.com", - host: "ns-1887.awsdns-43.co.uk", - ttl: 172800, - }, - NS { - domain: "reddit.com", - host: "ns-378.awsdns-47.com", - ttl: 172800, - }, - NS { - domain: "reddit.com", - host: "ns-557.awsdns-05.net", - ttl: 172800, - }, - ], - resources: [], - }, -) -Answer: CNAME { domain: "www.reddit.com", host: "reddit.map.fastly.net", ttl: 10800 } -Authority: NS { domain: "reddit.com", host: "ns-1029.awsdns-00.org", ttl: 172800 } -Authority: NS { domain: "reddit.com", host: "ns-1887.awsdns-43.co.uk", ttl: 172800 } -Authority: NS { domain: "reddit.com", host: "ns-378.awsdns-47.com", ttl: 172800 } -Authority: NS { domain: "reddit.com", host: "ns-557.awsdns-05.net", ttl: 172800 } + { + "Domain": "com", + "Host": "d.gtld-servers.net", + "TTL": 172800 + }, + { + "Domain": "com", + "Host": "b.gtld-servers.net", + "TTL": 172800 + }, + { + "Domain": "com", + "Host": "f.gtld-servers.net", + "TTL": 172800 + }, + { + "Domain": "com", + "Host": "k.gtld-servers.net", + "TTL": 172800 + }, + { + "Domain": "com", + "Host": "m.gtld-servers.net", + "TTL": 172800 + }, + { + "Domain": "com", + "Host": "i.gtld-servers.net", + "TTL": 172800 + }, + { + "Domain": "com", + "Host": "g.gtld-servers.net", + "TTL": 172800 + }, + { + "Domain": "com", + "Host": "a.gtld-servers.net", + "TTL": 172800 + }, + { + "Domain": "com", + "Host": "c.gtld-servers.net", + "TTL": 172800 + }, + { + "Domain": "com", + "Host": "e.gtld-servers.net", + "TTL": 172800 + } + ], + "resources": [ + { + "Domain": "l.gtld-servers.net", + "Addr": "192.41.162.30", + "TTL": 172800 + }, + { + "Domain": "l.gtld-servers.net", + "Addr": "2001:500:d937::30", + "TTL": 172800 + }, + { + "Domain": "j.gtld-servers.net", + "Addr": "192.48.79.30", + "TTL": 172800 + }, + { + "Domain": "j.gtld-servers.net", + "Addr": "2001:502:7094::30", + "TTL": 172800 + }, + { + "Domain": "h.gtld-servers.net", + "Addr": "192.54.112.30", + "TTL": 172800 + }, + { + "Domain": "h.gtld-servers.net", + "Addr": "2001:502:8cc::30", + "TTL": 172800 + }, + { + "Domain": "d.gtld-servers.net", + "Addr": "192.31.80.30", + "TTL": 172800 + }, + { + "Domain": "d.gtld-servers.net", + "Addr": "2001:500:856e::30", + "TTL": 172800 + }, + { + "Domain": "b.gtld-servers.net", + "Addr": "192.33.14.30", + "TTL": 172800 + }, + { + "Domain": "b.gtld-servers.net", + "Addr": "2001:503:231d::2:30", + "TTL": 172800 + }, + { + "Domain": "f.gtld-servers.net", + "Addr": "192.35.51.30", + "TTL": 172800 + } + ] + } + +Attempting lookup of 1 www.reddit.com with NS 192.41.162.30 +{ + "header": { + "ID": 6666, + "RecursionDesired": true, + "TruncatedMessage": false, + "AuthoritativeAnswer": false, + "Opcode": 0, + "Response": true, + "ResultCode": 0, + "CheckingDisabled": false, + "AuthedData": false, + "Z": false, + "RecursionAvailable": false, + "Questions": 1, + "Answers": 0, + "AuthoritativeEntries": 4, + "ResourceEntries": 1 + }, + "questions": [ + { + "Name": "www.reddit.com", + "QType": 1 + } + ], + "answers": [], + "authorities": [ + { + "Domain": "reddit.com", + "Host": "ns-557.awsdns-05.net", + "TTL": 172800 + }, + { + "Domain": "reddit.com", + "Host": "ns-378.awsdns-47.com", + "TTL": 172800 + }, + { + "Domain": "reddit.com", + "Host": "ns-1029.awsdns-00.org", + "TTL": 172800 + }, + { + "Domain": "reddit.com", + "Host": "ns-1887.awsdns-43.co.uk", + "TTL": 172800 + } + ], + "resources": [ + { + "Domain": "ns-378.awsdns-47.com", + "Addr": "205.251.193.122", + "TTL": 172800 + } + ] + } + +Attempting lookup of 1 www.reddit.com with NS 205.251.193.122 +{ + "header": { + "ID": 6666, + "RecursionDesired": true, + "TruncatedMessage": false, + "AuthoritativeAnswer": true, + "Opcode": 0, + "Response": true, + "ResultCode": 0, + "CheckingDisabled": false, + "AuthedData": false, + "Z": false, + "RecursionAvailable": false, + "Questions": 1, + "Answers": 1, + "AuthoritativeEntries": 4, + "ResourceEntries": 0 + }, + "questions": [ + { + "Name": "www.reddit.com", + "QType": 1 + } + ], + "answers": [ + { + "Domain": "www.reddit.com", + "Host": "reddit.map.fastly.net", + "TTL": 10800 + } + ], + "authorities": [ + { + "Domain": "reddit.com", + "Host": "ns-1029.awsdns-00.org", + "TTL": 172800 + }, + { + "Domain": "reddit.com", + "Host": "ns-1887.awsdns-43.co.uk", + "TTL": 172800 + }, + { + "Domain": "reddit.com", + "Host": "ns-378.awsdns-47.com", + "TTL": 172800 + }, + { + "Domain": "reddit.com", + "Host": "ns-557.awsdns-05.net", + "TTL": 172800 + } + ], + "resources": [] + } +Answer: &dns.NSRecord{Domain:"www.reddit.com", Host:"reddit.map.fastly.net", TTL:0x2a30} +Authority: &dns.NSRecord{Domain:"reddit.com", Host:"ns-1029.awsdns-00.org", TTL:0x2a300} +Authority: &dns.NSRecord{Domain:"reddit.com", Host:"ns-1887.awsdns-43.co.uk", TTL:0x2a300} +Authority: &dns.NSRecord{Domain:"reddit.com", Host:"ns-378.awsdns-47.com", TTL:0x2a300} +Authority: &dns.NSRecord{Domain:"reddit.com", Host:"ns-557.awsdns-05.net", TTL:0x2a300} + ```
@@ -314,18 +313,19 @@ Authority: NS { domain: "reddit.com", host: "ns-557.awsdns-05.net", ttl: 172800 Toggle to see output of `dig` terminal ```text + ; <<>> DiG 9.10.6 <<>> @127.0.0.1 -p 2053 www.reddit.com ; (1 server found) ;; global options: +cmd ;; Got answer: -;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35824 +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 11817 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 0 ;; QUESTION SECTION: ;www.reddit.com. IN A ;; ANSWER SECTION: -www.reddit.com. 10800 IN CNAME reddit.map.fastly.net. +www.reddit.com. 10800 IN NS reddit.map.fastly.net. ;; AUTHORITY SECTION: reddit.com. 172800 IN NS ns-1029.awsdns-00.org. @@ -333,9 +333,9 @@ reddit.com. 172800 IN NS ns-1887.awsdns-43.co.uk. reddit.com. 172800 IN NS ns-378.awsdns-47.com. reddit.com. 172800 IN NS ns-557.awsdns-05.net. -;; Query time: 436 msec +;; Query time: 363 msec ;; SERVER: 127.0.0.1#2053(127.0.0.1) -;; WHEN: Mon Aug 28 08:37:37 IST 2023 +;; WHEN: Mon May 27 02:27:37 IST 2024 ;; MSG SIZE rcvd: 261 ``` diff --git a/cmd/main.go b/cmd/main.go index 8f6facd..ceec58e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,14 +1,19 @@ package main import ( + "encoding/json" "fmt" "net" "os" + "time" dns "github.com/sadityakumar9211/go-res/internal/dns" buf "github.com/sadityakumar9211/go-res/pkg/bytepacketbuffer" ) +// For now we're always starting with *a.root-servers.net*. +const rootNameServer = "198.41.0.4" + func lookup(qname string, qtype dns.QueryType, server string) (*dns.DnsPacket, error) { // Forward queries to the specified DNS server @@ -18,6 +23,9 @@ func lookup(qname string, qtype dns.QueryType, server string) (*dns.DnsPacket, e } defer socket.Close() + // 5 second deadline for read and write operation to this socket. + socket.SetDeadline(time.Now().Add(5 * time.Second)) + packet := dns.NewDnsPacket() packet.Header.ID = 6666 packet.Header.Questions = 1 @@ -46,49 +54,58 @@ func lookup(qname string, qtype dns.QueryType, server string) (*dns.DnsPacket, e if err != nil { return nil, err } - - fmt.Printf("%#v\n", resPacket) + jsonData, err := json.MarshalIndent(resPacket, " ", " ") + if err != nil { + fmt.Println("Error marshling to JSON: ", err) + return resPacket, nil + } + fmt.Println(string(jsonData)) return resPacket, nil } func recursiveLookup(qname string, qtype dns.QueryType) (*dns.DnsPacket, error) { - // For now we're always starting with *a.root-servers.net*. - ns := net.ParseIP("198.41.0.4") + ns := net.ParseIP(rootNameServer) // Since it might take an arbitrary number of steps, we enter an unbounded loop. for { - fmt.Printf("Attempting lookup of %v %v with NS %v\n", qtype, qname, ns) + fmt.Printf("\nAttempting lookup of %v %v with NS %v\n", qtype, qname, ns) - server := fmt.Sprintf("%s:53", ns.String()) - response, err := lookup(qname, qtype, server) + addr := fmt.Sprintf("%s:53", ns.String()) + response, err := lookup(qname, qtype, addr) if err != nil { return nil, err } if len(response.Answers) > 0 && response.Header.ResultCode == dns.NOERROR { return response, nil - } - - if response.Header.ResultCode == dns.NXDOMAIN { + } else if response.Header.ResultCode == dns.NXDOMAIN { return response, nil } + // Otherwise, we'll try to find a new nameserver based on NS and a corresponding A + // record in the additional section. If this succeeds, we can switch name server + // and retry the loop. newNS := response.GetResolvedNS(qname) if newNS != nil { ns = newNS continue } - newNSName := response.GetUnresolvedNS(qname) - if newNSName != "" { - recursiveResponse, err := recursiveLookup(newNSName, dns.A) - if err != nil { - return nil, err - } - - newNS = recursiveResponse.GetRandomA() - if newNS != nil { - ns = newNS + // If not, we'll have to resolve the ip of a NS record. If no NS records exist, + // we'll go with what the last server told us. + for candidateNS := range response.GetNS(qname) { + if candidateNS.Host != "" { + recursiveResponse, err := recursiveLookup(candidateNS.Host, dns.A) + if err != nil { + continue + } + newNS := recursiveResponse.GetRandomA() + if newNS != nil { + ns = newNS + break + } else { + return response, nil + } } else { return response, nil } @@ -106,7 +123,7 @@ func handleQuery(socket *net.UDPConn) error { // We're not interested in the length, but we need to keep track of the // source in order to send our reply later on. - // Taking input from `dig` + // Taking input from `dig`. _, src, err := socket.ReadFromUDP(reqBuffer.Buf[:]) if err != nil { return err @@ -117,8 +134,6 @@ func handleQuery(socket *net.UDPConn) error { return err } - fmt.Printf("%#v\n", request) - // Create and initialize the response packet response := dns.NewDnsPacket() response.Header.ID = request.Header.ID @@ -159,6 +174,7 @@ func handleQuery(socket *net.UDPConn) error { // need make sure that a question is actually present. If not, we return `FORMERR` // to indicate that the sender made something wrong. response.Header.ResultCode = dns.FORMERR + fmt.Println("More than one question present...") } // The only thing remaining is to encode our response and send it off! @@ -175,7 +191,7 @@ func handleQuery(socket *net.UDPConn) error { return nil } -func main() { +func main() { // endpoint for sending and receiving packets // Bind a UDP socket on port 2053 to listen for DNS queries // Listening to all available network interfaces at port 2053. addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:2053") @@ -184,7 +200,6 @@ func main() { os.Exit(1) } - // endpoint for sending and receiving packets socket, err := net.ListenUDP("udp", addr) if err != nil { fmt.Println("Error binding UDP socket:", err) diff --git a/internal/dns/dns.go b/internal/dns/dns.go index fd07da7..12b623d 100644 --- a/internal/dns/dns.go +++ b/internal/dns/dns.go @@ -1,6 +1,7 @@ package dns import ( + "fmt" "net" "strings" @@ -712,8 +713,8 @@ func (u *UNKNOWNRecord) Read(buffer *bytepacketbuffer.BytePacketBuffer) error { // Write writes UNKNOWNRecord data to the buffer. func (u *UNKNOWNRecord) Write(buffer *bytepacketbuffer.BytePacketBuffer) (uint, error) { - // st := fmt.Sprintf("Skipping record %v", u) - // fmt.Println(st) + st := fmt.Sprintf("Skipping record %v", u) + fmt.Println(st) return 0, nil } @@ -727,11 +728,11 @@ func (a *UNKNOWNRecord) GetDomain() string { // DnsPacket represents a DNS packet. type DnsPacket struct { - Header *DnsHeader - Questions []*DnsQuestion - Answers []DnsRecord - Authorities []DnsRecord - Resources []DnsRecord + Header *DnsHeader `json:"header"` + Questions []*DnsQuestion `json:"questions"` + Answers []DnsRecord `json:"answers"` + Authorities []DnsRecord `json:"authorities"` + Resources []DnsRecord `json:"resources"` } // NewDnsPacket creates a new DNS packet with default values. @@ -906,7 +907,7 @@ func FromBuffer(buffer *bytepacketbuffer.BytePacketBuffer) (*DnsPacket, error) { packet.Authorities = append(packet.Authorities, rec) } // Reading Resources from the buffer - for i := uint16(0); i < uint16(len(packet.Resources)); i++ { + for i := uint16(0); i < packet.Header.ResourceEntries; i++ { rec, err := ReadDNSRecord(buffer) if err != nil { return nil, err @@ -953,18 +954,23 @@ func (p *DnsPacket) Write(buffer *bytepacketbuffer.BytePacketBuffer) error { return nil } -// / It's useful to be able to pick a random A record from a packet. When we -// / get multiple IP's for a single name, it doesn't matter which one we -// / choose, so in those cases we can now pick one at random. +// It's useful to be able to pick a random A record from a packet. When we +// get multiple IP's for a single name, it doesn't matter which one we +// choose, so in those cases we can now pick one at random. // GetRandomA returns a random IPv4 address (A record) from the DNS packet's list of answers. func (p *DnsPacket) GetRandomA() net.IP { for _, record := range p.Answers { // if a, ok := record.(DnsRecord); ok { // Only A records return the address and others return nil - if addr := record.ExtractIPv4(); addr != nil { - return addr + switch record.(type) { + case *ARecord: + addr := record.ExtractIPv4() + if addr != nil { + return addr + } + default: + continue } - // } } return nil // Return nil if no IPv4 address is found } @@ -1003,18 +1009,19 @@ func (p *DnsPacket) GetNS(qname string) <-chan struct { } // GetResolvedNS returns the resolved IP for an NS record if possible. -// / We'll use the fact that name servers often bundle the corresponding -// / A records when replying to an NS query to implement a function that -// / returns the actual IP for an NS record if possible. +// We'll use the fact that name servers often bundle the corresponding +// A records when replying to an NS query to implement a function that +// returns the actual IP for an NS record if possible. func (p *DnsPacket) GetResolvedNS(qname string) net.IP { for ns := range p.GetNS(qname) { + // Looking if there are any additional records sent so that we don't have to perform second lookup. for _, record := range p.Resources { if aRecord, ok := record.(*ARecord); ok && aRecord.Domain == ns.Host { return aRecord.Addr } } } - + // no additional A records sent. return nil // Return nil for no match } @@ -1025,6 +1032,7 @@ func (p *DnsPacket) GetResolvedNS(qname string) net.IP { // GetUnresolvedNS returns the host name of an appropriate name server. func (p *DnsPacket) GetUnresolvedNS(qname string) string { for ns := range p.GetNS(qname) { + fmt.Printf("ns from unresolved NS: %+v", ns) return ns.Host }