diff --git a/internal/dns/constants.go b/internal/dns/constants.go index 61e72a2..f74a119 100644 --- a/internal/dns/constants.go +++ b/internal/dns/constants.go @@ -2,6 +2,7 @@ package dns const ( QTypeA uint16 = 1 + QTypeNS = 2 QTypeAAAA uint16 = 28 QTypeCNAME uint16 = 30 ) diff --git a/internal/dns/response.go b/internal/dns/response.go index 9c95a09..e2c54b4 100644 --- a/internal/dns/response.go +++ b/internal/dns/response.go @@ -48,52 +48,65 @@ func parseName(data []byte, offset int) (string, int) { return name[:len(name)-1], offset } -func ParseResponse(response []byte) ([]DNSAnswer, error) { +func ParseFullResponse(response []byte) ([]DNSAnswer, []DNSAnswer, []DNSAnswer, error) { if len(response) < 12 { - return nil, fmt.Errorf("response too short to contain header") + return nil, nil, nil, fmt.Errorf("response too short to contain header") } header := parseHeader(response[:12]) - if header.Flags&0x8000 == 0 { - return nil, fmt.Errorf("not a response") - } - - if header.Flags&0x000F != 0 { - return nil, fmt.Errorf("response contains errors") + if header.Flags&0x8000 == 0 { // Check QR bit + return nil, nil, nil, fmt.Errorf("not a response") } offset := 12 + for i := 0; i < int(header.QDCount); i++ { _, newOffset := parseName(response, offset) offset = newOffset + 4 } answers := []DNSAnswer{} - for i := 0; i < int(header.ANCount); i++ { - name, newOffset := parseName(response, offset) - offset = newOffset - - answer := DNSAnswer{ - Name: name, - } - - // Extract type, class, TTL, and data length - answer.Type = binary.BigEndian.Uint16(response[offset : offset+2]) - answer.Class = binary.BigEndian.Uint16(response[offset+2 : offset+4]) - answer.TTL = binary.BigEndian.Uint32(response[offset+4 : offset+8]) - dataLen := binary.BigEndian.Uint16(response[offset+8 : offset+10]) - offset += 10 - - if answer.Type == QTypeA && dataLen == 4 { - answer.Data = fmt.Sprintf("%d.%d.%d.%d", response[offset], response[offset+1], response[offset+2], response[offset+3]) - } else { - answer.Data = fmt.Sprintf("unsupported type %d", answer.Type) - } - offset += int(dataLen) - + answer, newOffset := parseResourceRecord(response, offset) answers = append(answers, answer) + offset = newOffset + } + authorities := []DNSAnswer{} + for i := 0; i < int(header.NSCount); i++ { + authority, newOffset := parseResourceRecord(response, offset) + authorities = append(authorities, authority) + offset = newOffset } - return answers, nil + additionals := []DNSAnswer{} + for i := 0; i < int(header.ARCount); i++ { + additional, newOffset := parseResourceRecord(response, offset) + additionals = append(additionals, additional) + offset = newOffset + } + + return answers, authorities, additionals, nil +} + +func parseResourceRecord(data []byte, offset int) (DNSAnswer, int) { + name, newOffset := parseName(data, offset) + offset = newOffset + + answer := DNSAnswer{ + Name: name, + Type: binary.BigEndian.Uint16(data[offset : offset+2]), + Class: binary.BigEndian.Uint16(data[offset+2 : offset+4]), + TTL: binary.BigEndian.Uint32(data[offset+4 : offset+8]), + } + dataLen := binary.BigEndian.Uint16(data[offset+8 : offset+10]) + offset += 10 + + if answer.Type == QTypeA && dataLen == 4 { + answer.Data = fmt.Sprintf("%d.%d.%d.%d", data[offset], data[offset+1], data[offset+2], data[offset+3]) + } else if answer.Type == QTypeCNAME || answer.Type == QTypeNS { + answer.Data, _ = parseName(data, offset) + } + offset += int(dataLen) + + return answer, offset } diff --git a/main.go b/main.go index 06d8ff6..a2db2e1 100644 --- a/main.go +++ b/main.go @@ -10,41 +10,80 @@ import ( func main() { domain := "dns.google.com" - header := dns.DNSHeader{ - ID: dns.GenerateRandomID(), - Flags: 0x0100, - QDCount: 1, - } - - question := dns.DNSQuestion{ - QName: domain, - QType: dns.QTypeA, - QClass: dns.QClassIN, - } - - message := dns.DNSMessage{ - Header: header, - Question: question, - } - - serializedMessage := message.Serialize() - - response, err := network.SendDNSQuery(serializedMessage, network.DefaultServer) - + rootServer := "198.41.0.4:53" + ip, err := resolve(domain, rootServer) if err != nil { - log.Fatalf("Error sending DNS query: %s", err) - } - - fmt.Printf("DNS Response: %x\n", response) - - answers, err := dns.ParseResponse(response) - if err != nil { - log.Fatalf("Error parsing response: %s", err) - } - - for _, answer := range answers { - fmt.Printf("Name: %s, Type: %d, Class: %d, TTL: %d, Data: %s\n", - answer.Name, answer.Type, answer.Class, answer.TTL, answer.Data) + log.Fatalf("Failed to resolve %s: %v", domain, err) } + fmt.Printf("Resolved %s to IP: %s\n", domain, ip) +} + +func resolve(domain, nameServer string) (string, error) { + for { + fmt.Printf("Querying %s for %s\n", nameServer, domain) + + header := dns.DNSHeader{ + ID: dns.GenerateRandomID(), + Flags: 0, + QDCount: 1, + } + + question := dns.DNSQuestion{ + QName: domain, + QType: dns.QTypeA, + QClass: dns.QClassIN, + } + + message := dns.DNSMessage{ + Header: header, + Question: question, + } + query := message.Serialize() + + response, err := network.SendDNSQuery(query, nameServer) + if err != nil { + return "", fmt.Errorf("failed to send query: %w", err) + } + + answers, authorities, additionals, err := dns.ParseFullResponse(response) + if err != nil { + return "", fmt.Errorf("failed to parse response: %w", err) + } + + for _, answer := range answers { + if answer.Type == dns.QTypeA { + return answer.Data, nil + } + if answer.Type == dns.QTypeCNAME { + domain = answer.Data + } + } + + var nextNameServer string + for _, additional := range additionals { + if additional.Type == dns.QTypeA { + nextNameServer = additional.Data + break + } + } + + if nextNameServer == "" && len(authorities) > 0 { + for _, authority := range authorities { + if authority.Type == dns.QTypeNS { + ns := authority.Data + nextNameServer, err = resolve(ns, "198.41.0.4:53") // Use root server for NS IP + if err == nil { + break + } + } + } + } + + if nextNameServer == "" { + return "", fmt.Errorf("could not find next name server") + } + + nameServer = nextNameServer + ":53" + } }