diff options
Diffstat (limited to 'plugin/test')
-rw-r--r-- | plugin/test/doc.go | 2 | ||||
-rw-r--r-- | plugin/test/file.go | 107 | ||||
-rw-r--r-- | plugin/test/file_test.go | 11 | ||||
-rw-r--r-- | plugin/test/helpers.go | 348 | ||||
-rw-r--r-- | plugin/test/responsewriter.go | 61 | ||||
-rw-r--r-- | plugin/test/server.go | 52 |
6 files changed, 581 insertions, 0 deletions
diff --git a/plugin/test/doc.go b/plugin/test/doc.go new file mode 100644 index 000000000..75281ed8b --- /dev/null +++ b/plugin/test/doc.go @@ -0,0 +1,2 @@ +// Package test contains helper functions for writing plugin tests. +package test diff --git a/plugin/test/file.go b/plugin/test/file.go new file mode 100644 index 000000000..f87300e55 --- /dev/null +++ b/plugin/test/file.go @@ -0,0 +1,107 @@ +package test + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +// TempFile will create a temporary file on disk and returns the name and a cleanup function to remove it later. +func TempFile(dir, content string) (string, func(), error) { + f, err := ioutil.TempFile(dir, "go-test-tmpfile") + if err != nil { + return "", nil, err + } + if err := ioutil.WriteFile(f.Name(), []byte(content), 0644); err != nil { + return "", nil, err + } + rmFunc := func() { os.Remove(f.Name()) } + return f.Name(), rmFunc, nil +} + +// WritePEMFiles creates a tmp dir with ca.pem, cert.pem, and key.pem and the func to remove it +func WritePEMFiles(dir string) (string, func(), error) { + tempDir, err := ioutil.TempDir(dir, "go-test-pemfiles") + if err != nil { + return "", nil, err + } + + data := `-----BEGIN CERTIFICATE----- +MIIC9zCCAd+gAwIBAgIJALGtqdMzpDemMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV +BAMMB2t1YmUtY2EwHhcNMTYxMDE5MTU1NDI0WhcNNDQwMzA2MTU1NDI0WjASMRAw +DgYDVQQDDAdrdWJlLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +pa4Wu/WkpJNRr8pMVE6jjwzNUOx5mIyoDr8WILSxVQcEeyVPPmAqbmYXtVZO11p9 +jTzoEqF7Kgts3HVYGCk5abqbE14a8Ru/DmV5avU2hJ/NvSjtNi/O+V6SzCbg5yR9 +lBR53uADDlzuJEQT9RHq7A5KitFkx4vUcXnjOQCbDogWFoYuOgNEwJPy0Raz3NJc +ViVfDqSJ0QHg02kCOMxcGFNRQ9F5aoW7QXZXZXD0tn3wLRlu4+GYyqt8fw5iNdLJ +t79yKp8I+vMTmMPz4YKUO+eCl5EY10Qs7wvoG/8QNbjH01BRN3L8iDT2WfxdvjTu +1RjPxFL92i+B7HZO7jGLfQIDAQABo1AwTjAdBgNVHQ4EFgQUZTrg+Xt87tkxDhlB +gKk9FdTOW3IwHwYDVR0jBBgwFoAUZTrg+Xt87tkxDhlBgKk9FdTOW3IwDAYDVR0T +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApB7JFVrZpGSOXNO3W7SlN6OCPXv9 +C7rIBc8rwOrzi2mZWcBmWheQrqBo8xHif2rlFNVQxtq3JcQ8kfg/m1fHeQ/Ygzel +Z+U1OqozynDySBZdNn9i+kXXgAUCqDPp3hEQWe0os/RRpIwo9yOloBxdiX6S0NIf +VB8n8kAynFPkH7pYrGrL1HQgDFCSfa4tUJ3+9sppnCu0pNtq5AdhYx9xFb2sn+8G +xGbtCkhVk2VQ+BiCWnjYXJ6ZMzabP7wiOFDP9Pvr2ik22PRItsW/TLfHFXM1jDmc +I1rs/VUGKzcJGVIWbHrgjP68CTStGAvKgbsTqw7aLXTSqtPw88N9XVSyRg== +-----END CERTIFICATE-----` + path := filepath.Join(tempDir, "ca.pem") + if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil { + return "", nil, err + } + data = `-----BEGIN CERTIFICATE----- +MIICozCCAYsCCQCRlf5BrvPuqjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdr +dWJlLWNhMB4XDTE2MTAxOTE2MDUxOFoXDTE3MTAxOTE2MDUxOFowFTETMBEGA1UE +AwwKa3ViZS1hZG1pbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTw +a7wCFoiCad/N53aURfjrme+KR7FS0yf5Ur9OR/oM3BoS9stYu5Flzr35oL5T6t5G +c2ey78mUs/Cs07psnjUdKH55bDpJSdG7zW9mXNyeLwIefFcj/38SS5NBSotmLo8u +scJMGXeQpCQtfVuVJSP2bfU5u5d0KTLSg/Cor6UYonqrRB82HbOuuk8Wjaww4VHo +nCq7X8o948V6HN5ZibQOgMMo+nf0wORREHBjvwc4W7ewbaTcfoe1VNAo/QnkqxTF +ueMb2HxgghArqQSK8b44O05V0zrde25dVnmnte6sPjcV0plqMJ37jViISxsOPUFh +/ZW7zbIM/7CMcDekCiECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYZE8OxwRR7GR +kdd5aIriDwWfcl56cq5ICyx87U8hAZhBxk46a6a901LZPzt3xKyWIFQSRj/NYiQ+ +/thjGLZI2lhkVgYtyAD4BNxDiuppQSCbkjY9tLVDdExGttEVN7+UYDWJBHy6X16Y +xSG9FE3Dvp9LI89Nq8E3dRh+Q8wu52q9HaQXjS5YtzQOtDFKPBkihXu/c6gEHj4Y +bZVk8rFiH8/CvcQxAuvNI3VVCFUKd2LeQtqwYQQ//qoiuA15krTq5Ut9eXJ8zxAw +zhDEPP4FhY+Sz+y1yWirphl7A1aZwhXVPcfWIGqpQ3jzNwUeocbH27kuLh+U4hQo +qeg10RdFnw== +-----END CERTIFICATE-----` + path = filepath.Join(tempDir, "cert.pem") + if err = ioutil.WriteFile(path, []byte(data), 0644); err != nil { + return "", nil, err + } + + data = `-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEAxPBrvAIWiIJp383ndpRF+OuZ74pHsVLTJ/lSv05H+gzcGhL2 +y1i7kWXOvfmgvlPq3kZzZ7LvyZSz8KzTumyeNR0ofnlsOklJ0bvNb2Zc3J4vAh58 +VyP/fxJLk0FKi2Yujy6xwkwZd5CkJC19W5UlI/Zt9Tm7l3QpMtKD8KivpRiieqtE +HzYds666TxaNrDDhUeicKrtfyj3jxXoc3lmJtA6Awyj6d/TA5FEQcGO/Bzhbt7Bt +pNx+h7VU0Cj9CeSrFMW54xvYfGCCECupBIrxvjg7TlXTOt17bl1Weae17qw+NxXS +mWownfuNWIhLGw49QWH9lbvNsgz/sIxwN6QKIQIDAQABAoIBAQDCXq9V7ZGjxWMN +OkFaLVkqJg3V91puztoMt+xNV8t+JTcOnOzrIXZuOFbl9PwLHPPP0SSRkm9LOvKl +dU26zv0OWureeKSymia7U2mcqyC3tX+bzc7WinbeSYZBnc0e7AjD1EgpBcaU1TLL +agIxY3A2oD9CKmrVPhZzTIZf/XztqTYjhvs5I2kBeT0imdYGpXkdndRyGX4I5/JQ +fnp3Czj+AW3zX7RvVnXOh4OtIAcfoG9xoNyD5LOSlJkkX0MwTS8pEBeZA+A4nb+C +ivjnOSgXWD+liisI+LpBgBbwYZ/E49x5ghZYrJt8QXSk7Bl/+UOyv6XZAm2mev6j +RLAZtoABAoGBAP2P+1PoKOwsk+d/AmHqyTCUQm0UG18LOLB/5PyWfXs/6caDmdIe +DZWeZWng1jUQLEadmoEw/CBY5+tPfHlzwzMNhT7KwUfIDQCIBoS7dzHYnwrJ3VZh +qYA05cuGHAAHqwb6UWz3y6Pa4AEVSHX6CM83CAi9jdWZ1rdZybWG+qYBAoGBAMbV +FsR/Ft+tK5ALgXGoG83TlmxzZYuZ1SnNje1OSdCQdMFCJB10gwoaRrw1ICzi40Xk +ydJwV1upGz1om9ReDAD1zQM9artmQx6+TVLiVPALuARdZE70+NrA6w3ZvxUgJjdN +ngvXUr+8SdvaYUAwFu7BulfJlwXjUS711hHW/KQhAoGBALY41QuV2mLwHlLNie7I +hlGtGpe9TXZeYB0nrG6B0CfU5LJPPSotguG1dXhDpm138/nDpZeWlnrAqdsHwpKd +yPhVjR51I7XsZLuvBdA50Q03egSM0c4UXXXPjh1XgaPb3uMi3YWMBwL4ducQXoS6 +bb5M9C8j2lxZNF+L3VPhbxwBAoGBAIEWDvX7XKpTDxkxnxRfA84ZNGusb5y2fsHp +Bd+vGBUj8+kUO8Yzwm9op8vA4ebCVrMl2jGZZd3IaDryE1lIxZpJ+pPD5+tKdQEc +o67P6jz+HrYWu+zW9klvPit71qasfKMi7Rza6oo4f+sQWFsH3ZucgpJD+pyD/Ez0 +pcpnPRaBAoGBANT/xgHBfIWt4U2rtmRLIIiZxKr+3mGnQdpA1J2BCh+/6AvrEx// +E/WObVJXDnBdViu0L9abE9iaTToBVri4cmlDlZagLuKVR+TFTCN/DSlVZTDkqkLI +8chzqtkH6b2b2R73hyRysWjsomys34ma3mEEPTX/aXeAF2MSZ/EWT9yL +-----END RSA PRIVATE KEY-----` + path = filepath.Join(tempDir, "key.pem") + if err = ioutil.WriteFile(path, []byte(data), 0644); err != nil { + return "", nil, err + } + + rmFunc := func() { os.RemoveAll(tempDir) } + return tempDir, rmFunc, nil +} diff --git a/plugin/test/file_test.go b/plugin/test/file_test.go new file mode 100644 index 000000000..ed86a8260 --- /dev/null +++ b/plugin/test/file_test.go @@ -0,0 +1,11 @@ +package test + +import "testing" + +func TestTempFile(t *testing.T) { + _, f, e := TempFile(".", "test") + if e != nil { + t.Fatalf("failed to create temp file: %s", e) + } + defer f() +} diff --git a/plugin/test/helpers.go b/plugin/test/helpers.go new file mode 100644 index 000000000..065cf8935 --- /dev/null +++ b/plugin/test/helpers.go @@ -0,0 +1,348 @@ +package test + +import ( + "sort" + "testing" + + "github.com/miekg/dns" + "golang.org/x/net/context" +) + +type sect int + +const ( + // Answer is the answer section in an Msg. + Answer sect = iota + // Ns is the authoritative section in an Msg. + Ns + // Extra is the additional section in an Msg. + Extra +) + +// RRSet represents a list of RRs. +type RRSet []dns.RR + +func (p RRSet) Len() int { return len(p) } +func (p RRSet) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p RRSet) Less(i, j int) bool { return p[i].String() < p[j].String() } + +// Case represents a test case that encapsulates various data from a query and response. +// Note that is the TTL of a record is 303 we don't compare it with the TTL. +type Case struct { + Qname string + Qtype uint16 + Rcode int + Do bool + Answer []dns.RR + Ns []dns.RR + Extra []dns.RR + Error error +} + +// Msg returns a *dns.Msg embedded in c. +func (c Case) Msg() *dns.Msg { + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(c.Qname), c.Qtype) + if c.Do { + o := new(dns.OPT) + o.Hdr.Name = "." + o.Hdr.Rrtype = dns.TypeOPT + o.SetDo() + o.SetUDPSize(4096) + m.Extra = []dns.RR{o} + } + return m +} + +// A returns an A record from rr. It panics on errors. +func A(rr string) *dns.A { r, _ := dns.NewRR(rr); return r.(*dns.A) } + +// AAAA returns an AAAA record from rr. It panics on errors. +func AAAA(rr string) *dns.AAAA { r, _ := dns.NewRR(rr); return r.(*dns.AAAA) } + +// CNAME returns a CNAME record from rr. It panics on errors. +func CNAME(rr string) *dns.CNAME { r, _ := dns.NewRR(rr); return r.(*dns.CNAME) } + +// DNAME returns a DNAME record from rr. It panics on errors. +func DNAME(rr string) *dns.DNAME { r, _ := dns.NewRR(rr); return r.(*dns.DNAME) } + +// SRV returns a SRV record from rr. It panics on errors. +func SRV(rr string) *dns.SRV { r, _ := dns.NewRR(rr); return r.(*dns.SRV) } + +// SOA returns a SOA record from rr. It panics on errors. +func SOA(rr string) *dns.SOA { r, _ := dns.NewRR(rr); return r.(*dns.SOA) } + +// NS returns an NS record from rr. It panics on errors. +func NS(rr string) *dns.NS { r, _ := dns.NewRR(rr); return r.(*dns.NS) } + +// PTR returns a PTR record from rr. It panics on errors. +func PTR(rr string) *dns.PTR { r, _ := dns.NewRR(rr); return r.(*dns.PTR) } + +// TXT returns a TXT record from rr. It panics on errors. +func TXT(rr string) *dns.TXT { r, _ := dns.NewRR(rr); return r.(*dns.TXT) } + +// MX returns an MX record from rr. It panics on errors. +func MX(rr string) *dns.MX { r, _ := dns.NewRR(rr); return r.(*dns.MX) } + +// RRSIG returns an RRSIG record from rr. It panics on errors. +func RRSIG(rr string) *dns.RRSIG { r, _ := dns.NewRR(rr); return r.(*dns.RRSIG) } + +// NSEC returns an NSEC record from rr. It panics on errors. +func NSEC(rr string) *dns.NSEC { r, _ := dns.NewRR(rr); return r.(*dns.NSEC) } + +// DNSKEY returns a DNSKEY record from rr. It panics on errors. +func DNSKEY(rr string) *dns.DNSKEY { r, _ := dns.NewRR(rr); return r.(*dns.DNSKEY) } + +// DS returns a DS record from rr. It panics on errors. +func DS(rr string) *dns.DS { r, _ := dns.NewRR(rr); return r.(*dns.DS) } + +// OPT returns an OPT record with UDP buffer size set to bufsize and the DO bit set to do. +func OPT(bufsize int, do bool) *dns.OPT { + o := new(dns.OPT) + o.Hdr.Name = "." + o.Hdr.Rrtype = dns.TypeOPT + o.SetVersion(0) + o.SetUDPSize(uint16(bufsize)) + if do { + o.SetDo() + } + return o +} + +// Header test if the header in resp matches the header as defined in tc. +func Header(t *testing.T, tc Case, resp *dns.Msg) bool { + if resp.Rcode != tc.Rcode { + t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + return false + } + + if len(resp.Answer) != len(tc.Answer) { + t.Errorf("answer for %q contained %d results, %d expected", tc.Qname, len(resp.Answer), len(tc.Answer)) + return false + } + if len(resp.Ns) != len(tc.Ns) { + t.Errorf("authority for %q contained %d results, %d expected", tc.Qname, len(resp.Ns), len(tc.Ns)) + return false + } + if len(resp.Extra) != len(tc.Extra) { + t.Errorf("additional for %q contained %d results, %d expected", tc.Qname, len(resp.Extra), len(tc.Extra)) + return false + } + return true +} + +// Section tests if the the section in tc matches rr. +func Section(t *testing.T, tc Case, sec sect, rr []dns.RR) bool { + section := []dns.RR{} + switch sec { + case 0: + section = tc.Answer + case 1: + section = tc.Ns + case 2: + section = tc.Extra + } + + for i, a := range rr { + if a.Header().Name != section[i].Header().Name { + t.Errorf("rr %d should have a Header Name of %q, but has %q", i, section[i].Header().Name, a.Header().Name) + return false + } + // 303 signals: don't care what the ttl is. + if section[i].Header().Ttl != 303 && a.Header().Ttl != section[i].Header().Ttl { + if _, ok := section[i].(*dns.OPT); !ok { + // we check edns0 bufize on this one + t.Errorf("rr %d should have a Header TTL of %d, but has %d", i, section[i].Header().Ttl, a.Header().Ttl) + return false + } + } + if a.Header().Rrtype != section[i].Header().Rrtype { + t.Errorf("rr %d should have a header rr type of %d, but has %d", i, section[i].Header().Rrtype, a.Header().Rrtype) + return false + } + + switch x := a.(type) { + case *dns.SRV: + if x.Priority != section[i].(*dns.SRV).Priority { + t.Errorf("rr %d should have a Priority of %d, but has %d", i, section[i].(*dns.SRV).Priority, x.Priority) + return false + } + if x.Weight != section[i].(*dns.SRV).Weight { + t.Errorf("rr %d should have a Weight of %d, but has %d", i, section[i].(*dns.SRV).Weight, x.Weight) + return false + } + if x.Port != section[i].(*dns.SRV).Port { + t.Errorf("rr %d should have a Port of %d, but has %d", i, section[i].(*dns.SRV).Port, x.Port) + return false + } + if x.Target != section[i].(*dns.SRV).Target { + t.Errorf("rr %d should have a Target of %q, but has %q", i, section[i].(*dns.SRV).Target, x.Target) + return false + } + case *dns.RRSIG: + if x.TypeCovered != section[i].(*dns.RRSIG).TypeCovered { + t.Errorf("rr %d should have a TypeCovered of %d, but has %d", i, section[i].(*dns.RRSIG).TypeCovered, x.TypeCovered) + return false + } + if x.Labels != section[i].(*dns.RRSIG).Labels { + t.Errorf("rr %d should have a Labels of %d, but has %d", i, section[i].(*dns.RRSIG).Labels, x.Labels) + return false + } + if x.SignerName != section[i].(*dns.RRSIG).SignerName { + t.Errorf("rr %d should have a SignerName of %s, but has %s", i, section[i].(*dns.RRSIG).SignerName, x.SignerName) + return false + } + case *dns.NSEC: + if x.NextDomain != section[i].(*dns.NSEC).NextDomain { + t.Errorf("rr %d should have a NextDomain of %s, but has %s", i, section[i].(*dns.NSEC).NextDomain, x.NextDomain) + return false + } + // TypeBitMap + case *dns.A: + if x.A.String() != section[i].(*dns.A).A.String() { + t.Errorf("rr %d should have a Address of %q, but has %q", i, section[i].(*dns.A).A.String(), x.A.String()) + return false + } + case *dns.AAAA: + if x.AAAA.String() != section[i].(*dns.AAAA).AAAA.String() { + t.Errorf("rr %d should have a Address of %q, but has %q", i, section[i].(*dns.AAAA).AAAA.String(), x.AAAA.String()) + return false + } + case *dns.TXT: + for j, txt := range x.Txt { + if txt != section[i].(*dns.TXT).Txt[j] { + t.Errorf("rr %d should have a Txt of %q, but has %q", i, section[i].(*dns.TXT).Txt[j], txt) + return false + } + } + case *dns.SOA: + tt := section[i].(*dns.SOA) + if x.Ns != tt.Ns { + t.Errorf("SOA nameserver should be %q, but is %q", tt.Ns, x.Ns) + return false + } + case *dns.PTR: + tt := section[i].(*dns.PTR) + if x.Ptr != tt.Ptr { + t.Errorf("PTR ptr should be %q, but is %q", tt.Ptr, x.Ptr) + return false + } + case *dns.CNAME: + tt := section[i].(*dns.CNAME) + if x.Target != tt.Target { + t.Errorf("CNAME target should be %q, but is %q", tt.Target, x.Target) + return false + } + case *dns.MX: + tt := section[i].(*dns.MX) + if x.Mx != tt.Mx { + t.Errorf("MX Mx should be %q, but is %q", tt.Mx, x.Mx) + return false + } + if x.Preference != tt.Preference { + t.Errorf("MX Preference should be %q, but is %q", tt.Preference, x.Preference) + return false + } + case *dns.NS: + tt := section[i].(*dns.NS) + if x.Ns != tt.Ns { + t.Errorf("NS nameserver should be %q, but is %q", tt.Ns, x.Ns) + return false + } + case *dns.OPT: + tt := section[i].(*dns.OPT) + if x.UDPSize() != tt.UDPSize() { + t.Errorf("OPT UDPSize should be %d, but is %d", tt.UDPSize(), x.UDPSize()) + return false + } + if x.Do() != tt.Do() { + t.Errorf("OPT DO should be %t, but is %t", tt.Do(), x.Do()) + return false + } + } + } + return true +} + +// CNAMEOrder makes sure that CNAMES do not appear after their target records +func CNAMEOrder(t *testing.T, res *dns.Msg) { + for i, c := range res.Answer { + if c.Header().Rrtype != dns.TypeCNAME { + continue + } + for _, a := range res.Answer[:i] { + if a.Header().Name != c.(*dns.CNAME).Target { + continue + } + t.Errorf("CNAME found after target record\n") + t.Logf("%v\n", res) + + } + } +} + +// SortAndCheck sorts resp and the checks the header and three sections against the testcase in tc. +func SortAndCheck(t *testing.T, resp *dns.Msg, tc Case) { + sort.Sort(RRSet(resp.Answer)) + sort.Sort(RRSet(resp.Ns)) + sort.Sort(RRSet(resp.Extra)) + + if !Header(t, tc, resp) { + t.Logf("%v\n", resp) + return + } + + if !Section(t, tc, Answer, resp.Answer) { + t.Logf("%v\n", resp) + return + } + if !Section(t, tc, Ns, resp.Ns) { + t.Logf("%v\n", resp) + return + + } + if !Section(t, tc, Extra, resp.Extra) { + t.Logf("%v\n", resp) + return + } + return +} + +// ErrorHandler returns a Handler that returns ServerFailure error when called. +func ErrorHandler() Handler { + return HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + m := new(dns.Msg) + m.SetRcode(r, dns.RcodeServerFailure) + w.WriteMsg(m) + return dns.RcodeServerFailure, nil + }) +} + +// NextHandler returns a Handler that returns rcode and err. +func NextHandler(rcode int, err error) Handler { + return HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + return rcode, err + }) +} + +// Copied here to prevent an import cycle, so that we can define to above handlers. + +type ( + // HandlerFunc is a convenience type like dns.HandlerFunc, except + // ServeDNS returns an rcode and an error. + HandlerFunc func(context.Context, dns.ResponseWriter, *dns.Msg) (int, error) + + // Handler interface defines a plugin. + Handler interface { + ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error) + Name() string + } +) + +// ServeDNS implements the Handler interface. +func (f HandlerFunc) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + return f(ctx, w, r) +} + +// Name implements the Handler interface. +func (f HandlerFunc) Name() string { return "handlerfunc" } diff --git a/plugin/test/responsewriter.go b/plugin/test/responsewriter.go new file mode 100644 index 000000000..79eaa00f3 --- /dev/null +++ b/plugin/test/responsewriter.go @@ -0,0 +1,61 @@ +package test + +import ( + "net" + + "github.com/miekg/dns" +) + +// ResponseWriter is useful for writing tests. It uses some fixed values for the client. The +// remote will always be 10.240.0.1 and port 40212. The local address is always 127.0.0.1 and +// port 53. +type ResponseWriter struct{} + +// LocalAddr returns the local address, always 127.0.0.1:53 (UDP). +func (t *ResponseWriter) LocalAddr() net.Addr { + ip := net.ParseIP("127.0.0.1") + port := 53 + return &net.UDPAddr{IP: ip, Port: port, Zone: ""} +} + +// RemoteAddr returns the remote address, always 10.240.0.1:40212 (UDP). +func (t *ResponseWriter) RemoteAddr() net.Addr { + ip := net.ParseIP("10.240.0.1") + port := 40212 + return &net.UDPAddr{IP: ip, Port: port, Zone: ""} +} + +// WriteMsg implement dns.ResponseWriter interface. +func (t *ResponseWriter) WriteMsg(m *dns.Msg) error { return nil } + +// Write implement dns.ResponseWriter interface. +func (t *ResponseWriter) Write(buf []byte) (int, error) { return len(buf), nil } + +// Close implement dns.ResponseWriter interface. +func (t *ResponseWriter) Close() error { return nil } + +// TsigStatus implement dns.ResponseWriter interface. +func (t *ResponseWriter) TsigStatus() error { return nil } + +// TsigTimersOnly implement dns.ResponseWriter interface. +func (t *ResponseWriter) TsigTimersOnly(bool) { return } + +// Hijack implement dns.ResponseWriter interface. +func (t *ResponseWriter) Hijack() { return } + +// RepsponseWrite6 returns fixed client and remote address in IPv6. The remote +// address is always fe80::42:ff:feca:4c65 and port 40212. The local address +// is always ::1 and port 53. +type ResponseWriter6 struct { + ResponseWriter +} + +// LocalAddr returns the local address, always ::1, port 53 (UDP). +func (t *ResponseWriter6) LocalAddr() net.Addr { + return &net.UDPAddr{IP: net.ParseIP("::1"), Port: 53, Zone: ""} +} + +// RemoteAddr returns the remote address, always fe80::42:ff:feca:4c65 port 40212 (UDP). +func (t *ResponseWriter6) RemoteAddr() net.Addr { + return &net.UDPAddr{IP: net.ParseIP("fe80::42:ff:feca:4c65"), Port: 40212, Zone: ""} +} diff --git a/plugin/test/server.go b/plugin/test/server.go new file mode 100644 index 000000000..eb39c7a5b --- /dev/null +++ b/plugin/test/server.go @@ -0,0 +1,52 @@ +package test + +import ( + "net" + "sync" + "time" + + "github.com/miekg/dns" +) + +// TCPServer starts a DNS server with a TCP listener on laddr. +func TCPServer(laddr string) (*dns.Server, string, error) { + l, err := net.Listen("tcp", laddr) + if err != nil { + return nil, "", err + } + + server := &dns.Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour} + + waitLock := sync.Mutex{} + waitLock.Lock() + server.NotifyStartedFunc = func() { waitLock.Unlock() } + + go func() { + server.ActivateAndServe() + l.Close() + }() + + waitLock.Lock() + return server, l.Addr().String(), nil +} + +// UDPServer starts a DNS server with an UDP listener on laddr. +func UDPServer(laddr string) (*dns.Server, string, error) { + pc, err := net.ListenPacket("udp", laddr) + if err != nil { + return nil, "", err + } + server := &dns.Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour} + + waitLock := sync.Mutex{} + waitLock.Lock() + server.NotifyStartedFunc = func() { waitLock.Unlock() } + + go func() { + server.ActivateAndServe() + pc.Close() + }() + + waitLock.Lock() + return server, pc.LocalAddr().String(), nil +} |