mirror of
https://github.com/andwati/dns-resolver.git
synced 2026-10-18 02:32:52 +00:00
completed the basic dns resolver
This commit is contained in:
@@ -2,6 +2,7 @@ package dns
|
||||
|
||||
const (
|
||||
QTypeA uint16 = 1
|
||||
QTypeNS = 2
|
||||
QTypeAAAA uint16 = 28
|
||||
QTypeCNAME uint16 = 30
|
||||
)
|
||||
|
||||
+44
-31
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user