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.
|
||||
|
||||
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
|
||||
|
||||
`go test dnsthingymagik/tests`
|
||||
|
||||
`dig @serverip domain.tld`
|
||||
|
||||
Example:
|
||||
|
||||
98
main.go
98
main.go
@@ -1,106 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"dnsthingymagik/recordcache"
|
||||
"dnsthingymagik/resolver"
|
||||
"dnsthingymagik/resolver/entities"
|
||||
"fmt"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"dnsthingymagik/server"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func main() {
|
||||
udpServer, err := net.ListenPacket("udp", ":53")
|
||||
s, err := server.NewServer(":53")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer func(udpServer net.PacketConn) {
|
||||
err := udpServer.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}(udpServer)
|
||||
defer s.Close()
|
||||
|
||||
cache := recordcache.NewCache()
|
||||
|
||||
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
|
||||
}
|
||||
s.Start()
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"dnsthingymagik/resolver/entities"
|
||||
"dnsthingymagik/server/resolver/entities"
|
||||
"fmt"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"sync"
|
||||
@@ -1,9 +1,9 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"dnsthingymagik/recordcache"
|
||||
"dnsthingymagik/resolver/entities"
|
||||
"dnsthingymagik/resolver/query"
|
||||
"dnsthingymagik/server/recordcache"
|
||||
"dnsthingymagik/server/resolver/entities"
|
||||
"dnsthingymagik/server/resolver/query"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"log"
|
||||
"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