github.com/darmach/terratest@v0.34.8-0.20210517103231-80931f95e3ff/modules/dns-helper/dns_local_server.go (about)

     1  package dns_helper
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/miekg/dns"
    12  )
    13  
    14  var testDomain = "gruntwork.io"
    15  
    16  // dnsDatabase stores a collection of DNSQuery with their respective DNSAnswers, to be used by a local dnsTestServer
    17  type dnsDatabase map[DNSQuery]DNSAnswers
    18  
    19  // dnsTestServer helper for testing this package using local DNS nameservers with test records
    20  type dnsTestServer struct {
    21  	Server           *dns.Server
    22  	DNSDatabase      dnsDatabase
    23  	DNSDatabaseRetry dnsDatabase
    24  }
    25  
    26  // newDNSTestServer returns a new instance of dnsTestServer
    27  func newDNSTestServer(server *dns.Server) *dnsTestServer {
    28  	return &dnsTestServer{Server: server, DNSDatabase: make(dnsDatabase), DNSDatabaseRetry: make(dnsDatabase)}
    29  }
    30  
    31  // Address returns the host:port string of the server listener
    32  func (s *dnsTestServer) Address() string {
    33  	return s.Server.PacketConn.LocalAddr().String()
    34  }
    35  
    36  // AddEntryToDNSDatabase adds DNSAnswers to the DNSQuery in the database of the server
    37  func (s *dnsTestServer) AddEntryToDNSDatabase(q DNSQuery, a DNSAnswers) {
    38  	s.DNSDatabase[q] = append(s.DNSDatabase[q], a...)
    39  }
    40  
    41  // AddEntryToDNSDatabaseRetry adds DNSAnswers to the DNSQuery in the database used when retrying
    42  func (s *dnsTestServer) AddEntryToDNSDatabaseRetry(q DNSQuery, a DNSAnswers) {
    43  	s.DNSDatabaseRetry[q] = append(s.DNSDatabaseRetry[q], a...)
    44  }
    45  
    46  // setupTestDNSServers runs and returns 2x local dnsTestServer, initialized with NS records for the testDomain pointing to themselves
    47  // it uses a handler that will send replies stored in their internal DNSDatabase
    48  func setupTestDNSServers(t *testing.T) (s1, s2 *dnsTestServer) {
    49  	s1 = runTestDNSServer(t, "0")
    50  	s2 = runTestDNSServer(t, "0")
    51  
    52  	q := DNSQuery{"NS", testDomain}
    53  	a := DNSAnswers{{"NS", s1.Address() + "."}, {"NS", s2.Address() + "."}}
    54  	s1.AddEntryToDNSDatabase(q, a)
    55  	s2.AddEntryToDNSDatabase(q, a)
    56  
    57  	s1.Server.Handler.(*dns.ServeMux).HandleFunc(testDomain+".", func(w dns.ResponseWriter, r *dns.Msg) {
    58  		stdDNSHandler(t, w, r, s1, false)
    59  	})
    60  	s2.Server.Handler.(*dns.ServeMux).HandleFunc(testDomain+".", func(w dns.ResponseWriter, r *dns.Msg) {
    61  		stdDNSHandler(t, w, r, s2, true)
    62  	})
    63  
    64  	return s1, s2
    65  }
    66  
    67  // setupTestDNSServersRetry runs and returns 2x local dnsTestServer, initialized with NS records for the testDomain pointing to themselves
    68  // it uses a handler that will send replies stored in their internal DNSDatabase, and then switch to their DNSDatabaseRetry after some time
    69  func setupTestDNSServersRetry(t *testing.T) (s1, s2 *dnsTestServer) {
    70  	s1 = runTestDNSServer(t, "0")
    71  	s2 = runTestDNSServer(t, "0")
    72  
    73  	q := DNSQuery{"NS", testDomain}
    74  	a := DNSAnswers{{"NS", s1.Address() + "."}, {"NS", s2.Address() + "."}}
    75  	s1.AddEntryToDNSDatabase(q, a)
    76  	s2.AddEntryToDNSDatabase(q, a)
    77  	s1.AddEntryToDNSDatabaseRetry(q, a)
    78  	s2.AddEntryToDNSDatabaseRetry(q, a)
    79  
    80  	s1.Server.Handler.(*dns.ServeMux).HandleFunc(testDomain+".", func(w dns.ResponseWriter, r *dns.Msg) {
    81  		retryDNSHandler(t, w, r, s1, false)
    82  	})
    83  	s2.Server.Handler.(*dns.ServeMux).HandleFunc(testDomain+".", func(w dns.ResponseWriter, r *dns.Msg) {
    84  		retryDNSHandler(t, w, r, s2, true)
    85  	})
    86  
    87  	return s1, s2
    88  }
    89  
    90  // runTestDNSServer starts and returns a new dnsTestServer listening in localhost and the given UDP port
    91  func runTestDNSServer(t *testing.T, port string) *dnsTestServer {
    92  	listener, err := net.ListenPacket("udp", "127.0.0.1:"+port)
    93  
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  
    98  	mux := dns.NewServeMux()
    99  	server := &dns.Server{PacketConn: listener, Net: "udp", Handler: mux}
   100  
   101  	go func() {
   102  		if err := server.ActivateAndServe(); err != nil {
   103  			log.Printf("Error in local DNS server: %s", err)
   104  		}
   105  	}()
   106  
   107  	return newDNSTestServer(server)
   108  }
   109  
   110  // doDNSAnswer sends replies to the DNS question from client, using the dnsDatabase to lookup the answers to the query
   111  // when invertAnswers is true, reverses the order of the answers from the dnsDatabase, useful to simulate realistic nameservers behaviours
   112  func doDNSAnswer(t *testing.T, w dns.ResponseWriter, r *dns.Msg, d dnsDatabase, invertAnswers bool) {
   113  	m := new(dns.Msg)
   114  	m.SetReply(r)
   115  	m.Authoritative = true
   116  	q := m.Question[0]
   117  	qtype := dns.TypeToString[q.Qtype]
   118  	answers := d[DNSQuery{qtype, strings.TrimSuffix(q.Name, ".")}]
   119  
   120  	var seen = make(map[DNSAnswer]bool)
   121  
   122  	for _, r := range answers {
   123  		if seen[r] {
   124  			continue
   125  		}
   126  		seen[r] = true
   127  
   128  		rr, err := dns.NewRR(fmt.Sprintf("%s %s", q.Name, r.String()))
   129  
   130  		if err != nil {
   131  			t.Fatalf("err: %s", err)
   132  		}
   133  
   134  		m.Answer = append(m.Answer, rr)
   135  	}
   136  
   137  	if invertAnswers {
   138  		for i, j := 0, len(m.Answer)-1; i < j; i, j = i+1, j-1 {
   139  			m.Answer[i], m.Answer[j] = m.Answer[j], m.Answer[i]
   140  		}
   141  	}
   142  
   143  	w.WriteMsg(m)
   144  }
   145  
   146  // stdDNSHandler uses the internal DNSDatabase to send answers to DNS queries
   147  func stdDNSHandler(t *testing.T, w dns.ResponseWriter, r *dns.Msg, s *dnsTestServer, invertAnswers bool) {
   148  	doDNSAnswer(t, w, r, s.DNSDatabase, invertAnswers)
   149  }
   150  
   151  var startTime = time.Now()
   152  
   153  // retryDNSHandler uses the internal DNSDatabase to send answers to DNS queries, and switches
   154  // to using the internal DNSDatabaseRetry after 3 seconds from startup
   155  func retryDNSHandler(t *testing.T, w dns.ResponseWriter, r *dns.Msg, s *dnsTestServer, invertAnswers bool) {
   156  	if time.Now().Sub(startTime).Seconds() > 3 {
   157  		doDNSAnswer(t, w, r, s.DNSDatabaseRetry, invertAnswers)
   158  	} else {
   159  		doDNSAnswer(t, w, r, s.DNSDatabase, invertAnswers)
   160  	}
   161  }