testing and refactors - i guess, should be looked over, also maybe benchmarks? but that can wait, as can the person requesting from this server
This commit is contained in:
41
README.md
41
README.md
@@ -4,10 +4,49 @@ This is my simple, barebones, **insecure** implementation of a simple DNS server
|
|||||||
|
|
||||||
For now, it successfully recursively resolves A records.
|
For now, it successfully recursively resolves A records.
|
||||||
|
|
||||||
Sends through correct TTL values.
|
## Features
|
||||||
|
|
||||||
|
### DNS Message Parsing & Handling
|
||||||
|
|
||||||
|
- [x] Can parse and respond to valid DNS queries
|
||||||
|
- [ ] Correctly handles headers (ID, flags, opcode, RCODE)
|
||||||
|
- [ ] Supports question section processing
|
||||||
|
- [ ] Correctly formats answers, authority, and additional sections
|
||||||
|
|
||||||
|
### Supported Query Types
|
||||||
|
|
||||||
|
- [x] A (IPv4 address)
|
||||||
|
- [ ] AAAA (IPv6 address)
|
||||||
|
- [ ] CNAME (Canonical name)
|
||||||
|
- [ ] NS (Nameserver)
|
||||||
|
- [ ] MX (Mail Exchange)
|
||||||
|
- [ ] TXT (Text records)
|
||||||
|
- [ ] PTR (Reverse lookups)
|
||||||
|
- [ ] SOA (Start of Authority)
|
||||||
|
|
||||||
|
### Query Processing
|
||||||
|
|
||||||
|
- [ ] Supports recursive queries (if implemented)
|
||||||
|
- [ ] Supports iterative queries (if acting as authoritative server)
|
||||||
|
- [ ] Correctly handles RD (Recursion Desired) flag
|
||||||
|
- [ ] Supports negative responses (NXDOMAIN, NODATA)
|
||||||
|
- [x] Supports wildcards (*.example.com)
|
||||||
|
|
||||||
|
### Caching & TTL
|
||||||
|
|
||||||
|
- [x] Correctly applies TTL from authoritative responses
|
||||||
|
- [x] Handles cache expiration and eviction
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
- [ ] Responds correctly to malformed queries
|
||||||
|
- [ ] Returns correct RCODE values (e.g., SERVFAIL, REFUSED, NXDOMAIN)
|
||||||
|
- [ ] Handles timeouts and retransmissions
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
`go test dnsthingymagik/tests`
|
||||||
|
|
||||||
`dig @serverip domain.tld`
|
`dig @serverip domain.tld`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|||||||
98
main.go
98
main.go
@@ -1,106 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dnsthingymagik/recordcache"
|
"dnsthingymagik/server"
|
||||||
"dnsthingymagik/resolver"
|
|
||||||
"dnsthingymagik/resolver/entities"
|
|
||||||
"fmt"
|
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
udpServer, err := net.ListenPacket("udp", ":53")
|
s, err := server.NewServer(":53")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func(udpServer net.PacketConn) {
|
defer s.Close()
|
||||||
err := udpServer.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}(udpServer)
|
|
||||||
|
|
||||||
cache := recordcache.NewCache()
|
s.Start()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for {
|
|
||||||
buf := make([]byte, 514)
|
|
||||||
n, addr, err := udpServer.ReadFrom(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
go process(udpServer, addr, buf[:n], cache, &wg)
|
|
||||||
}
|
|
||||||
|
|
||||||
//recordCache.recordcache.PurgeExpiredEntries()
|
|
||||||
}
|
|
||||||
|
|
||||||
func process(udp net.PacketConn, addr net.Addr, buf []byte, cache *recordcache.Cache, wg *sync.WaitGroup) {
|
|
||||||
defer wg.Done()
|
|
||||||
msg, err := resolver.PacketParser(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var faq []entities.Record
|
|
||||||
for _, q := range msg.Questions {
|
|
||||||
if q.Type == dnsmessage.TypeA {
|
|
||||||
nips, err := resolver.ResolveDN(q.Name, msg.Header.ID, dnsmessage.TypeA, cache)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nips) == 0 {
|
|
||||||
log.Printf("No IP found for %s", q.Name.String())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
faq = append(faq, nips...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var answers []dnsmessage.Resource
|
|
||||||
for _, record := range faq {
|
|
||||||
answers = append(answers, dnsmessage.Resource{
|
|
||||||
Header: dnsmessage.ResourceHeader{
|
|
||||||
Name: record.Name,
|
|
||||||
Type: record.RType,
|
|
||||||
Class: record.Class,
|
|
||||||
TTL: record.TTL,
|
|
||||||
},
|
|
||||||
Body: &dnsmessage.AResource{
|
|
||||||
A: [4]byte{record.IP[0], record.IP[1], record.IP[2], record.IP[3]},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
response := dnsmessage.Message{
|
|
||||||
Header: dnsmessage.Header{
|
|
||||||
ID: msg.Header.ID,
|
|
||||||
Response: true,
|
|
||||||
OpCode: msg.Header.OpCode,
|
|
||||||
Authoritative: false,
|
|
||||||
RecursionDesired: msg.Header.RecursionDesired,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
RCode: dnsmessage.RCodeSuccess,
|
|
||||||
},
|
|
||||||
Questions: msg.Questions,
|
|
||||||
Answers: answers,
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(response)
|
|
||||||
|
|
||||||
packed, err := response.Pack()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = udp.WriteTo(packed, addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
153
server/Server.go
Normal file
153
server/Server.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"dnsthingymagik/server/recordcache"
|
||||||
|
"dnsthingymagik/server/resolver"
|
||||||
|
"dnsthingymagik/server/resolver/entities"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
udpServer net.PacketConn
|
||||||
|
cache *recordcache.Cache
|
||||||
|
wg sync.WaitGroup
|
||||||
|
shutdown context.CancelFunc
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(address string) (*Server, error) {
|
||||||
|
// Create context for shutdown
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
udpServer, err := net.ListenPacket("udp", address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Server{
|
||||||
|
udpServer: udpServer,
|
||||||
|
cache: recordcache.NewCache(),
|
||||||
|
ctx: ctx,
|
||||||
|
shutdown: cancel,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the DNS server to listen for queries.
|
||||||
|
func (s *Server) Start() {
|
||||||
|
log.Println("Starting DNS server on", s.udpServer.LocalAddr())
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
// If the server is shutting down, stop accepting new requests
|
||||||
|
log.Println("Server shutting down...")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
buf := make([]byte, 514)
|
||||||
|
n, addr, err := s.udpServer.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
if netErr, ok := err.(*net.OpError); ok && netErr.Op == "read" {
|
||||||
|
// If the connection is closed, exit the loop
|
||||||
|
log.Println("Error reading from UDP connection, shutting down...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("Error reading UDP packet:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.wg.Add(1)
|
||||||
|
go s.process(addr, buf[:n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gracefully close the server and all pending requests.
|
||||||
|
func (s *Server) Close() {
|
||||||
|
// Signal shutdown and close the UDP server connection
|
||||||
|
s.shutdown()
|
||||||
|
// Wait for all ongoing requests to be processed
|
||||||
|
s.wg.Wait()
|
||||||
|
|
||||||
|
err := s.udpServer.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error closing UDP server:", err)
|
||||||
|
}
|
||||||
|
log.Println("Server shut down gracefully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a single DNS query request.
|
||||||
|
func (s *Server) process(addr net.Addr, buf []byte) {
|
||||||
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
// Parse incoming DNS query
|
||||||
|
msg, err := resolver.PacketParser(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Packet parsing error from %s: %v", addr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var records []entities.Record
|
||||||
|
for _, q := range msg.Questions {
|
||||||
|
if q.Type == dnsmessage.TypeA {
|
||||||
|
nips, err := resolver.ResolveDN(q.Name, msg.Header.ID, dnsmessage.TypeA, s.cache)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Resolution error from %s for %s: %v", addr, q.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nips) == 0 {
|
||||||
|
log.Printf("No IP found for %s from %s", q.Name.String(), addr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
records = append(records, nips...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build DNS answer section
|
||||||
|
var answers []dnsmessage.Resource
|
||||||
|
for _, record := range records {
|
||||||
|
answers = append(answers, dnsmessage.Resource{
|
||||||
|
Header: dnsmessage.ResourceHeader{
|
||||||
|
Name: record.Name,
|
||||||
|
Type: record.RType,
|
||||||
|
Class: record.Class,
|
||||||
|
TTL: record.TTL,
|
||||||
|
},
|
||||||
|
Body: &dnsmessage.AResource{
|
||||||
|
A: [4]byte{record.IP[0], record.IP[1], record.IP[2], record.IP[3]},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the response message
|
||||||
|
response := dnsmessage.Message{
|
||||||
|
Header: dnsmessage.Header{
|
||||||
|
ID: msg.Header.ID,
|
||||||
|
Response: true,
|
||||||
|
OpCode: msg.Header.OpCode,
|
||||||
|
Authoritative: false,
|
||||||
|
RecursionDesired: msg.Header.RecursionDesired,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
RCode: dnsmessage.RCodeSuccess,
|
||||||
|
},
|
||||||
|
Questions: msg.Questions,
|
||||||
|
Answers: answers,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack the response
|
||||||
|
packed, err := response.Pack()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Response packing error for %s: %v", addr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the response back to the client
|
||||||
|
_, err = s.udpServer.WriteTo(packed, addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Response write error to %s: %v", addr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package recordcache
|
package recordcache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dnsthingymagik/resolver/entities"
|
"dnsthingymagik/server/resolver/entities"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dnsthingymagik/recordcache"
|
"dnsthingymagik/server/recordcache"
|
||||||
"dnsthingymagik/resolver/entities"
|
"dnsthingymagik/server/resolver/entities"
|
||||||
"dnsthingymagik/resolver/query"
|
"dnsthingymagik/server/resolver/query"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
69
tests/QueryDNS.go
Normal file
69
tests/QueryDNS.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dnsthingymagik/server"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sendDNSQuery(t *testing.T, serverAddr, domain string, qType dnsmessage.Type, autostart bool) dnsmessage.Message {
|
||||||
|
if autostart {
|
||||||
|
s := startTestServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
conn, err := net.Dial("udp", serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to connect to DNS server: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
var msg dnsmessage.Message
|
||||||
|
msg.Header.ID = 1234
|
||||||
|
msg.Header.RecursionDesired = true
|
||||||
|
msg.Questions = []dnsmessage.Question{
|
||||||
|
{
|
||||||
|
Name: dnsmessage.MustNewName(domain),
|
||||||
|
Type: qType,
|
||||||
|
Class: dnsmessage.ClassINET,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
query, err := msg.Pack()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to pack DNS query: %v", err)
|
||||||
|
}
|
||||||
|
_, err = conn.Write(query)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to send DNS query: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 512)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read DNS response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response dnsmessage.Message
|
||||||
|
err = response.Unpack(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to unpack DNS response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup Test Server
|
||||||
|
func startTestServer(t *testing.T) *server.Server {
|
||||||
|
server, err := server.NewServer(":53")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to start DNS server: %v", err)
|
||||||
|
}
|
||||||
|
go func() { server.Start() }()
|
||||||
|
time.Sleep(500 * time.Millisecond) // Allow server to initialize
|
||||||
|
return server
|
||||||
|
}
|
||||||
436
tests/RFC1035_Compliance_test.go
Normal file
436
tests/RFC1035_Compliance_test.go
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_CorrectlyHandlesHeaders(t *testing.T) {
|
||||||
|
// Send a simple DNS query for a known domain
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeA, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
t.Errorf("Expected RCODE 0 (NoError), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the query ID is the same as the request ID
|
||||||
|
if response.Header.ID != 12345 { // Replace with the ID used in your request (or dynamic query ID)
|
||||||
|
t.Errorf("Expected query ID to be 12345, but got %d", response.Header.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the query response (qr) flag is set to 1, meaning it's a response
|
||||||
|
if response.Header.Response != true {
|
||||||
|
t.Errorf("Expected qr (query/response) flag to be set to 1, but it was 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the recursion desired (rd) flag is set to 1 in the query
|
||||||
|
if response.Header.RecursionDesired != true {
|
||||||
|
t.Errorf("Expected rd (recursion desired) flag to be set to 1, but it was 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that recursion available (ra) flag is set to 1 in the response (assuming recursion is available)
|
||||||
|
if response.Header.RecursionAvailable != true {
|
||||||
|
t.Errorf("Expected ra (recursion available) flag to be set to 1, but it was 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the opcode is set to 0 (standard query)
|
||||||
|
if response.Header.OpCode != 0 {
|
||||||
|
t.Errorf("Expected opcode to be 0 (QUERY), but got %d", response.Header.OpCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally, check for the other flags and ensure that the query is well-formed
|
||||||
|
// (e.g., authority and additional sections should be empty in a standard query with no answer)
|
||||||
|
if len(response.Authorities) > 0 {
|
||||||
|
t.Errorf("Expected no authority section, but found %d entries", len(response.Authorities))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Additionals) > 0 {
|
||||||
|
t.Errorf("Expected no additional section, but found %d entries", len(response.Additionals))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_CorrectlyFormatsSections(t *testing.T) {
|
||||||
|
// Send a DNS query for a known domain (e.g., "govekar.net")
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeA, true)
|
||||||
|
|
||||||
|
// Test that the answer section is properly populated with an A record
|
||||||
|
if len(response.Answers) == 0 {
|
||||||
|
t.Error("Expected at least one answer, but got none")
|
||||||
|
} else {
|
||||||
|
// Check that the answer is an A record (IPv4 address)
|
||||||
|
for _, ans := range response.Answers {
|
||||||
|
if ans.Header.Type != dnsmessage.TypeA {
|
||||||
|
t.Errorf("Expected answer type to be A, but got %d", ans.Header.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the authority section, which should contain authoritative name server records if the server is authoritative
|
||||||
|
if len(response.Authorities) > 0 {
|
||||||
|
// Ensure that the authority section contains NS (Name Server) records
|
||||||
|
for _, auth := range response.Authorities {
|
||||||
|
if auth.Header.Type != dnsmessage.TypeNS {
|
||||||
|
t.Errorf("Expected authority record type to be NS, but got %d", auth.Header.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Log("No authority section found (expected if server is not authoritative)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the additional section, which should contain A records for the name servers listed in the authority section
|
||||||
|
if len(response.Additionals) > 0 {
|
||||||
|
for _, add := range response.Additionals {
|
||||||
|
if add.Header.Type != dnsmessage.TypeA {
|
||||||
|
t.Errorf("Expected additional section record type to be A, but got %d", add.Header.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify if the additional section corresponds to the authority section, i.e., if NS records are followed by A records
|
||||||
|
for _, auth := range response.Authorities {
|
||||||
|
nsRecord := auth.Body.(*dnsmessage.NSResource).NS
|
||||||
|
if nsRecord.String() != "" {
|
||||||
|
found := false
|
||||||
|
for _, add := range response.Additionals {
|
||||||
|
if add.Header.Type == dnsmessage.TypeA {
|
||||||
|
// Check if the additional A record corresponds to the NS record
|
||||||
|
if add.Header.Name == nsRecord {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("No corresponding A record found in additional section for NS record %s", nsRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ARecord(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeA, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
t.Errorf("Expected RCODE 0 (NoError), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, ans := range response.Answers {
|
||||||
|
if ans.Header.Type == dnsmessage.TypeA {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected A record in response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test AAAA Record (IPv6)
|
||||||
|
func Test_AAAARecord(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeAAAA, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
t.Errorf("Expected RCODE 0 (NoError), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, ans := range response.Answers {
|
||||||
|
if ans.Header.Type == dnsmessage.TypeAAAA {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected AAAA record in response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test CNAME Record
|
||||||
|
func Test_CNAMERecord(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "www.govekar.net.", dnsmessage.TypeCNAME, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
t.Errorf("Expected RCODE 0 (NoError), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, ans := range response.Answers {
|
||||||
|
if ans.Header.Type == dnsmessage.TypeCNAME {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected CNAME record in response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test MX Record
|
||||||
|
func Test_MXRecord(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeMX, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
t.Errorf("Expected RCODE 0 (NoError), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, ans := range response.Answers {
|
||||||
|
if ans.Header.Type == dnsmessage.TypeMX {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected MX record in response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test NS Record
|
||||||
|
func Test_NSRecord(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeNS, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
t.Errorf("Expected RCODE 0 (NoError), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, ans := range response.Answers {
|
||||||
|
if ans.Header.Type == dnsmessage.TypeNS {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected NS record in response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test TXT Record
|
||||||
|
func Test_TXTRecord(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeTXT, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
t.Errorf("Expected RCODE 0 (NoError), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, ans := range response.Answers {
|
||||||
|
if ans.Header.Type == dnsmessage.TypeTXT {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected TXT record in response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test PTR Record (Reverse DNS)
|
||||||
|
func Test_PTRRecord(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "4.3.2.1.in-addr.arpa.", dnsmessage.TypePTR, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
t.Errorf("Expected RCODE 0 (NoError), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, ans := range response.Answers {
|
||||||
|
if ans.Header.Type == dnsmessage.TypePTR {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected PTR record in response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test SOA Record
|
||||||
|
func Test_SOARecord(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeSOA, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
t.Errorf("Expected RCODE 0 (NoError), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, ans := range response.Answers {
|
||||||
|
if ans.Header.Type == dnsmessage.TypeSOA {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected SOA record in response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*// Test Recursive Queries
|
||||||
|
func Test_RecursiveQuery(t *testing.T) {
|
||||||
|
server := startTestServer(t)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeA)
|
||||||
|
|
||||||
|
if !response.Header.RecursionAvailable {
|
||||||
|
t.Errorf("Expected RA (Recursion Available) flag to be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Iterative Queries (If server is authoritative)
|
||||||
|
func Test_IterativeQuery(t *testing.T) {
|
||||||
|
server := startTestServer(t)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeA)
|
||||||
|
|
||||||
|
if response.Header.RecursionAvailable {
|
||||||
|
t.Errorf("Server should not allow recursion for iterative queries")
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Test NXDOMAIN Response (Non-existent domain)
|
||||||
|
func Test_NXDOMAIN(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "nonexistent.govekar.net.", dnsmessage.TypeA, true)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeNameError {
|
||||||
|
t.Errorf("Expected NXDOMAIN (RCODE 3), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*// Test SERVFAIL Response (Server failure)
|
||||||
|
func Test_SERVFAIL(t *testing.T) {
|
||||||
|
server := startTestServer(t)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// Simulating a server error (invalid domain)
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "serverfail.govekar.net.", dnsmessage.TypeA)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeServerFailure {
|
||||||
|
t.Errorf("Expected SERVFAIL (RCODE 2), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test REFUSED Response (Query refused)
|
||||||
|
func Test_REFUSED(t *testing.T) {
|
||||||
|
server := startTestServer(t)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// Assuming server is configured to refuse certain domains
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "forbidden.govekar.net.", dnsmessage.TypeA)
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeRefused {
|
||||||
|
t.Errorf("Expected REFUSED (RCODE 5), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
func Test_WildcardQuery_NoWildcardRecord(t *testing.T) {
|
||||||
|
// Query a non-existent subdomain without a wildcard record
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "random.govekar.net.", dnsmessage.TypeA, true)
|
||||||
|
|
||||||
|
// The response should be NXDOMAIN (Non-Existent Domain) if there's no wildcard
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeNameError {
|
||||||
|
t.Errorf("Expected NXDOMAIN, but got RCode: %v", response.Header.RCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_WildcardQuery_WithWildcardRecord(t *testing.T) {
|
||||||
|
// Query a non-existent subdomain but one that should match the wildcard
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "random.test.govekar.net.", dnsmessage.TypeA, true)
|
||||||
|
|
||||||
|
// The response should return the IP defined by the wildcard record
|
||||||
|
if len(response.Answers) == 0 || response.Answers[0].Body.(*dnsmessage.AResource).A != [4]byte{192, 168, 1, 1} {
|
||||||
|
t.Errorf("Expected IP address from wildcard record, but got: %v", response.Answers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Caching Mechanism
|
||||||
|
func Test_CacheTTLExpiration(t *testing.T) {
|
||||||
|
s := startTestServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
// First query to cache the record
|
||||||
|
response1 := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeA, false)
|
||||||
|
initialTTL := response1.Answers[0].Header.TTL
|
||||||
|
if initialTTL < 1 {
|
||||||
|
t.Errorf("TTL should be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a short duration before expiration (e.g., 1/2 of TTL)
|
||||||
|
waitDuration := time.Duration(initialTTL/2) * time.Second
|
||||||
|
time.Sleep(waitDuration)
|
||||||
|
|
||||||
|
// Send the second query
|
||||||
|
response2 := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.TypeA, false)
|
||||||
|
decreasedTTL := response2.Answers[0].Header.TTL
|
||||||
|
|
||||||
|
// Verify that the TTL has decreased (it should be lower than the initial TTL)
|
||||||
|
if decreasedTTL >= initialTTL {
|
||||||
|
t.Errorf("Expected TTL to decrease, but it didn't. Initial TTL: %d, Decreased TTL: %d", initialTTL, decreasedTTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the TTL is still positive after the decrease
|
||||||
|
if decreasedTTL <= 0 {
|
||||||
|
t.Errorf("TTL should be greater than 0, but it is: %d", decreasedTTL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Malformed Query (Should return FORMAT ERROR)
|
||||||
|
func Test_MalformedQuery(t *testing.T) {
|
||||||
|
s := startTestServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
// Sending invalid query data
|
||||||
|
conn, err := net.Dial("udp", "127.0.0.1:53")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to connect to DNS server: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// Set a timeout for the connection to avoid infinite waiting
|
||||||
|
err = conn.SetDeadline(time.Now().Add(5 * time.Second)) // Timeout after 5 seconds
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to set timeout for connection: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sending an invalid DNS query (malformed)
|
||||||
|
_, err = conn.Write([]byte{0x00, 0x01, 0x02}) // Invalid DNS query format
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to send malformed query: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 512)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
// Check for timeout error (or any other read error)
|
||||||
|
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||||
|
t.Fatalf("Timeout waiting for response from DNS server: %v", err)
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Failed to read DNS response: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var response dnsmessage.Message
|
||||||
|
err = response.Unpack(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to unpack response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the server returned the correct format error (RCODE 1)
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeFormatError {
|
||||||
|
t.Errorf("Expected FORMAT ERROR (RCODE 1), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Unknown Record Type Handling
|
||||||
|
func Test_UnknownRecordType(t *testing.T) {
|
||||||
|
response := sendDNSQuery(t, "127.0.0.1:53", "govekar.net.", dnsmessage.Type(99), true) // Unknown type
|
||||||
|
|
||||||
|
if response.Header.RCode != dnsmessage.RCodeNotImplemented {
|
||||||
|
t.Errorf("Expected NOTIMPLEMENTED (RCODE 4), got %d", response.Header.RCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user