diff --git a/middleware/chaos/chaos_test.go b/middleware/chaos/chaos_test.go new file mode 100644 index 000000000..4d01a456e --- /dev/null +++ b/middleware/chaos/chaos_test.go @@ -0,0 +1,83 @@ +package chaos + +import ( + "testing" + + "github.com/miekg/coredns/middleware" + + "github.com/miekg/dns" + "golang.org/x/net/context" +) + +func TestChaos(t *testing.T) { + em := Chaos{ + Version: version, + Authors: map[string]bool{"Miek Gieben": true}, + } + + tests := []struct { + next middleware.Handler + qname string + qtype uint16 + expectedCode int + expectedReply string + expectedErr error + }{ + { + next: genHandler(dns.RcodeSuccess, nil), + qname: "version.bind", + expectedCode: dns.RcodeSuccess, + expectedReply: version, + expectedErr: nil, + }, + { + next: genHandler(dns.RcodeSuccess, nil), + qname: "authors.bind", + expectedCode: dns.RcodeSuccess, + expectedReply: "Miek Gieben", + expectedErr: nil, + }, + { + next: genHandler(dns.RcodeSuccess, nil), + qname: "authors.bind", + qtype: dns.TypeSRV, + expectedCode: dns.RcodeSuccess, + expectedErr: nil, + }, + } + + ctx := context.TODO() + + for i, test := range tests { + req := new(dns.Msg) + if test.qtype == 0 { + test.qtype = dns.TypeTXT + } + req.SetQuestion(dns.Fqdn(test.qname), test.qtype) + em.Next = test.next + + rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) + code, err := em.ServeDNS(ctx, rec, req) + + if err != test.expectedErr { + t.Errorf("Test %d: Expected error %v, but got %v", i, test.expectedErr, err) + } + if code != int(test.expectedCode) { + t.Errorf("Test %d: Expected status code %d, but got %d", i, test.expectedCode, code) + } + if test.expectedReply != "" { + answer := rec.Msg().Answer[0].(*dns.TXT).Txt[0] + if answer != test.expectedReply { + t.Errorf("Test %d: Expected answer %s, but got %s", i, test.expectedReply, answer) + } + } + } +} + +func genHandler(rcode int, err error) middleware.Handler { + return middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + return rcode, err + }) +} + +const version = "CoreDNS-001" diff --git a/middleware/etcd/group_test.go b/middleware/etcd/group_test.go new file mode 100644 index 000000000..2d5b635c2 --- /dev/null +++ b/middleware/etcd/group_test.go @@ -0,0 +1,112 @@ +// +build etcd + +package etcd + +// etcd needs to be running on http://127.0.0.1:2379 +// *and* needs connectivity to the internet for remotely resolving +// names. + +import ( + "sort" + "testing" + + "github.com/miekg/coredns/middleware" + "github.com/miekg/coredns/middleware/etcd/msg" + + "github.com/miekg/dns" +) + +func TestGroupLookup(t *testing.T) { + for _, serv := range servicesGroup { + set(t, etc, serv.Key, 0, serv) + defer delete(t, etc, serv.Key) + } + for _, tc := range dnsTestCasesGroup { + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + + rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) + _, err := etc.ServeDNS(ctx, rec, m) + if err != nil { + t.Errorf("expected no error, got %v\n", err) + return + } + resp := rec.Msg() + + sort.Sort(rrSet(resp.Answer)) + sort.Sort(rrSet(resp.Ns)) + sort.Sort(rrSet(resp.Extra)) + + if resp.Rcode != tc.Rcode { + t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + t.Logf("%v\n", resp) + continue + } + + 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)) + t.Logf("%v\n", resp) + continue + } + 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)) + t.Logf("%v\n", resp) + continue + } + 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)) + t.Logf("%v\n", resp) + continue + } + + if !checkSection(t, tc, Answer, resp.Answer) { + t.Logf("%v\n", resp) + } + if !checkSection(t, tc, Ns, resp.Ns) { + t.Logf("%v\n", resp) + + } + if !checkSection(t, tc, Extra, resp.Extra) { + t.Logf("%v\n", resp) + } + } +} + +// Note the key is encoded as DNS name, while in "reality" it is a etcd path. +var servicesGroup = []*msg.Service{ + {Host: "127.0.0.1", Key: "a.dom.skydns.test.", Group: "g1"}, + {Host: "127.0.0.2", Key: "b.sub.dom.skydns.test.", Group: "g1"}, + + {Host: "127.0.0.1", Key: "a.dom2.skydns.test.", Group: "g1"}, + {Host: "127.0.0.2", Key: "b.sub.dom2.skydns.test.", Group: ""}, + + {Host: "127.0.0.1", Key: "a.dom1.skydns.test.", Group: "g1"}, + {Host: "127.0.0.2", Key: "b.sub.dom1.skydns.test.", Group: "g2"}, +} + +var dnsTestCasesGroup = []dnsTestCase{ + // Groups + { + // hits the group 'g1' and only includes those records + Qname: "dom.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{ + newA("dom.skydns.test. 300 IN A 127.0.0.1"), + newA("dom.skydns.test. 300 IN A 127.0.0.2"), + }, + }, + { + // One has group, the other has not... Include the non-group always. + Qname: "dom2.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{ + newA("dom2.skydns.test. 300 IN A 127.0.0.1"), + newA("dom2.skydns.test. 300 IN A 127.0.0.2"), + }, + }, + { + // The groups differ. + Qname: "dom1.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{ + newA("dom1.skydns.test. 300 IN A 127.0.0.1"), + }, + }, +} diff --git a/middleware/etcd/handler.go b/middleware/etcd/handler.go index dd8bb8cc3..be4e1a3be 100644 --- a/middleware/etcd/handler.go +++ b/middleware/etcd/handler.go @@ -41,6 +41,7 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i // rwrite and return // Nodata response // also catch other types, so that they return NODATA + // TODO(miek) nodata function see below return 0, nil } if isEtcdNameError(err) { @@ -55,11 +56,12 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i } if len(records) == 0 { + // NODATE function, see below m := new(dns.Msg) m.SetReply(state.Req) m.Ns = []dns.RR{e.SOA(zone, state)} state.W.WriteMsg(m) - return dns.RcodeNameError, nil + return dns.RcodeSuccess, nil } if len(records) > 0 { @@ -68,6 +70,9 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i if len(extra) > 0 { m.Extra = append(m.Extra, extra...) } + + m = dedup(m) + state.W.WriteMsg(m) return 0, nil } @@ -76,3 +81,11 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i func (e Etcd) NoData(zone string, state middleware.State) { // TODO(miek): write it } + +func dedup(m *dns.Msg) *dns.Msg { + ma := make(map[string]dns.RR) + m.Answer = dns.Dedup(m.Answer, ma) + m.Ns = dns.Dedup(m.Ns, ma) + m.Extra = dns.Dedup(m.Extra, ma) + return m +} diff --git a/middleware/etcd/lookup_test.go b/middleware/etcd/lookup_test.go index 17c22aeb1..5efe6a314 100644 --- a/middleware/etcd/lookup_test.go +++ b/middleware/etcd/lookup_test.go @@ -36,6 +36,11 @@ var dnsTestCases = []dnsTestCase{ Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeSRV, Answer: []dns.RR{newSRV("a.server1.dev.region1.skydns.test. 300 SRV 10 100 8080 dev.server1.")}, }, + // SRV Test (case test) + { + Qname: "a.SERVer1.dEv.region1.skydns.tEst.", Qtype: dns.TypeSRV, + Answer: []dns.RR{newSRV("a.SERVer1.dEv.region1.skydns.tEst. 300 SRV 10 100 8080 dev.server1.")}, + }, // NXDOMAIN Test { Qname: "doesnotexist.skydns.test.", Qtype: dns.TypeA, diff --git a/middleware/etcd/other_test.go b/middleware/etcd/other_test.go new file mode 100644 index 000000000..44a376da5 --- /dev/null +++ b/middleware/etcd/other_test.go @@ -0,0 +1,154 @@ +// +build etcd + +// tests mx and txt records + +package etcd + +// etcd needs to be running on http://127.0.0.1:2379 +// *and* needs connectivity to the internet for remotely resolving +// names. + +import ( + "sort" + "testing" + + "github.com/miekg/coredns/middleware" + "github.com/miekg/coredns/middleware/etcd/msg" + + "github.com/miekg/dns" +) + +func TestOtherLookup(t *testing.T) { + for _, serv := range servicesOther { + set(t, etc, serv.Key, 0, serv) + defer delete(t, etc, serv.Key) + } + for _, tc := range dnsTestCasesOther { + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + + rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) + _, err := etc.ServeDNS(ctx, rec, m) + if err != nil { + t.Errorf("expected no error, got %v\n", err) + return + } + resp := rec.Msg() + + sort.Sort(rrSet(resp.Answer)) + sort.Sort(rrSet(resp.Ns)) + sort.Sort(rrSet(resp.Extra)) + + if resp.Rcode != tc.Rcode { + t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + t.Logf("%v\n", resp) + continue + } + + 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)) + t.Logf("%v\n", resp) + continue + } + 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)) + t.Logf("%v\n", resp) + continue + } + 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)) + t.Logf("%v\n", resp) + continue + } + + if !checkSection(t, tc, Answer, resp.Answer) { + t.Logf("%v\n", resp) + } + if !checkSection(t, tc, Ns, resp.Ns) { + t.Logf("%v\n", resp) + + } + if !checkSection(t, tc, Extra, resp.Extra) { + t.Logf("%v\n", resp) + } + } +} + +// Note the key is encoded as DNS name, while in "reality" it is a etcd path. +var servicesOther = []*msg.Service{ + {Host: "dev.server1", Port: 8080, Key: "a.server1.dev.region1.skydns.test."}, + + // mx + {Host: "mx.skydns.test", Priority: 50, Mail: true, Key: "a.mail.skydns.test."}, + {Host: "mx.miek.nl", Priority: 50, Mail: true, Key: "b.mail.skydns.test."}, + {Host: "a.ipaddr.skydns.test", Priority: 30, Mail: true, Key: "a.mx.skydns.test."}, + + {Host: "a.ipaddr.skydns.test", Mail: true, Key: "a.mx2.skydns.test."}, + {Host: "b.ipaddr.skydns.test", Mail: true, Key: "b.mx2.skydns.test."}, + + {Host: "172.16.1.1", Key: "a.ipaddr.skydns.test."}, + {Host: "172.16.1.2", Key: "b.ipaddr.skydns.test."}, + + // txt + {Text: "abc", Key: "a1.txt.skydns.test."}, + {Text: "abc abc", Key: "a2.txt.skydns.test."}, + + // duplicate ip address + {Host: "10.11.11.10", Key: "http.multiport.http.skydns.test.", Port: 80}, + {Host: "10.11.11.10", Key: "https.multiport.http.skydns.test.", Port: 443}, +} + +var dnsTestCasesOther = []dnsTestCase{ + // MX Tests + { + // NODATA as this is not an Mail: true record. + Qname: "a.server1.dev.region1.skydns.test.", Qtype: dns.TypeMX, + Ns: []dns.RR{ + newSOA("skydns.test. 300 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), + }, + }, + { + Qname: "a.mail.skydns.test.", Qtype: dns.TypeMX, + Answer: []dns.RR{newMX("a.mail.skydns.test. 300 IN MX 50 mx.skydns.test.")}, + Extra: []dns.RR{ + newA("a.ipaddr.skydns.test. 300 IN A 172.16.1.1"), + newCNAME("mx.skydns.test. 300 IN CNAME a.ipaddr.skydns.test."), + }, + }, + { + Qname: "mx2.skydns.test.", Qtype: dns.TypeMX, + Answer: []dns.RR{ + newMX("mx2.skydns.test. 300 IN MX 10 a.ipaddr.skydns.test."), + newMX("mx2.skydns.test. 300 IN MX 10 b.ipaddr.skydns.test."), + }, + Extra: []dns.RR{ + newA("a.ipaddr.skydns.test. 300 A 172.16.1.1"), + newA("b.ipaddr.skydns.test. 300 A 172.16.1.2"), + }, + }, + // Txt + { + Qname: "a1.txt.skydns.test.", Qtype: dns.TypeTXT, + Answer: []dns.RR{ + newTXT("a1.txt.skydns.test. 300 IN TXT \"abc\""), + }, + }, + { + Qname: "a2.txt.skydns.test.", Qtype: dns.TypeTXT, + Answer: []dns.RR{ + newTXT("a2.txt.skydns.test. 300 IN TXT \"abc abc\""), + }, + }, + { + Qname: "txt.skydns.test.", Qtype: dns.TypeTXT, + Answer: []dns.RR{ + newTXT("txt.skydns.test. 300 IN TXT \"abc abc\""), + newTXT("txt.skydns.test. 300 IN TXT \"abc\""), + }, + }, + // Duplicate IP address test + { + Qname: "multiport.http.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{newA("multiport.http.skydns.test. 300 IN A 10.11.11.10")}, + }, +} diff --git a/middleware/etcd/setup_test.go b/middleware/etcd/setup_test.go index cc48093b7..838c1b93a 100644 --- a/middleware/etcd/setup_test.go +++ b/middleware/etcd/setup_test.go @@ -37,7 +37,7 @@ const ( ) func init() { - ctx, _ = context.WithTimeout(ctx.Background(), etcdTimeout) + ctx, _ = context.WithTimeout(context.Background(), etcdTimeout) etcdCfg := etcdc.Config{ Endpoints: []string{"http://localhost:2379"}, @@ -88,7 +88,7 @@ func TestLookup(t *testing.T) { t.Errorf("expected no error, got %v\n", err) return } - resp := rec.Reply() + resp := rec.Msg() sort.Sort(rrSet(resp.Answer)) sort.Sort(rrSet(resp.Ns)) diff --git a/middleware/etcd/stub.go b/middleware/etcd/stub.go index aae44df12..4bc877fb2 100644 --- a/middleware/etcd/stub.go +++ b/middleware/etcd/stub.go @@ -37,8 +37,8 @@ func addStubEdns0(m *dns.Msg) *dns.Msg { return m } -// Look in .../dns/stub//xx for msg.Services. Loop through them -// extract and add them as forwarders (ip:port-combos) for +// Look in .../dns/stub//xx for msg.Services. Loop through them +// extract and add them as forwarders (ip:port-combos) for // the stub zones. Only numeric (i.e. IP address) hosts are used. // TODO(miek): makes this Startup Function. func (e Etcd) UpdateStubZones(zone string) error {