diff --git a/.travis.yml b/.travis.yml index a9154fe68..5f16201cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,12 @@ language: go -sudo: false go: - 1.5 - 1.6 +before_script: + - go get github.com/coreos/etcd + - go get github.com/coreos/go-etcd/etcd + - go build -o $HOME/gopath/src/github.com/coreos/etcd/etcd.run github.com/coreos/etcd + - $HOME/gopath/src/github.com/coreos/etcd/etcd.run & + - sleep 2 script: - - go test -race -bench=. ./... + - go test -tags etcd -race -bench=. ./... diff --git a/core/caddy.go b/core/caddy.go index e76fa28f1..df84e679a 100644 --- a/core/caddy.go +++ b/core/caddy.go @@ -26,6 +26,7 @@ import ( "strings" "sync" "sync/atomic" + "testing" "time" "github.com/miekg/coredns/core/https" @@ -236,6 +237,7 @@ func startServers(groupings bindingGroup) error { // start the server // TODO(miek): for now will always be nil, so we will run ListenAndServe() + // TODO(miek): this is also why graceful restarts don't work. if ln != nil { //errChan <- s.Serve(ln) } else { @@ -386,3 +388,24 @@ type Input interface { // that could be loaded again later if requested. IsFile() bool } + +// TestServer returns a test server. +// The port can be retreived with ... . The testserver itself can be stopped +// with Stop(). It just takes a normal Corefile input, but doesn't use the port. +func TestServer(t *testing.T, corefile string) (*server.Server, error) { + + cdyfile := CaddyfileInput{Contents: []byte(corefile)} + configs, err := loadConfigs(path.Base(cdyfile.Path()), bytes.NewReader(cdyfile.Body())) + if err != nil { + return nil, err + } + groupings, err := arrangeBindings(configs) + if err != nil { + return nil, err + } + t.Logf("Starting %d servers", len(groupings)) + + group := groupings[0] + s, err := server.New(group.BindAddr.String(), group.Configs, time.Second) + return s, err +} diff --git a/middleware/etcd/cname_test.go b/middleware/etcd/cname_test.go index 33e42aac7..147498d64 100644 --- a/middleware/etcd/cname_test.go +++ b/middleware/etcd/cname_test.go @@ -31,36 +31,17 @@ func TestCnameLookup(t *testing.T) { } resp := rec.Msg() - if resp.Rcode != tc.Rcode { - t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + if !coretest.Header(t, tc, resp) { 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 !coretest.CheckSection(t, tc, coretest.Answer, resp.Answer) { + if !coretest.Section(t, tc, coretest.Answer, resp.Answer) { t.Logf("%v\n", resp) } - if !coretest.CheckSection(t, tc, coretest.Ns, resp.Ns) { + if !coretest.Section(t, tc, coretest.Ns, resp.Ns) { t.Logf("%v\n", resp) - } - if !coretest.CheckSection(t, tc, coretest.Extra, resp.Extra) { + if !coretest.Section(t, tc, coretest.Extra, resp.Extra) { t.Logf("%v\n", resp) } } diff --git a/middleware/etcd/group_test.go b/middleware/etcd/group_test.go index d7098c0c7..e37159f3f 100644 --- a/middleware/etcd/group_test.go +++ b/middleware/etcd/group_test.go @@ -37,36 +37,17 @@ func TestGroupLookup(t *testing.T) { sort.Sort(coretest.RRSet(resp.Ns)) sort.Sort(coretest.RRSet(resp.Extra)) - if resp.Rcode != tc.Rcode { - t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + if !coretest.Header(t, tc, resp) { 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 !coretest.CheckSection(t, tc, coretest.Answer, resp.Answer) { + if !coretest.Section(t, tc, coretest.Answer, resp.Answer) { t.Logf("%v\n", resp) } - if !coretest.CheckSection(t, tc, coretest.Ns, resp.Ns) { + if !coretest.Section(t, tc, coretest.Ns, resp.Ns) { t.Logf("%v\n", resp) - } - if !coretest.CheckSection(t, tc, coretest.Extra, resp.Extra) { + if !coretest.Section(t, tc, coretest.Extra, resp.Extra) { t.Logf("%v\n", resp) } } diff --git a/middleware/etcd/multi_test.go b/middleware/etcd/multi_test.go index cae43f18a..980b17a53 100644 --- a/middleware/etcd/multi_test.go +++ b/middleware/etcd/multi_test.go @@ -40,36 +40,17 @@ func TestMultiLookup(t *testing.T) { sort.Sort(coretest.RRSet(resp.Ns)) sort.Sort(coretest.RRSet(resp.Extra)) - if resp.Rcode != tc.Rcode { - t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + if !coretest.Header(t, tc, resp) { 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 !coretest.CheckSection(t, tc, coretest.Answer, resp.Answer) { + if !coretest.Section(t, tc, coretest.Answer, resp.Answer) { t.Logf("%v\n", resp) } - if !coretest.CheckSection(t, tc, coretest.Ns, resp.Ns) { + if !coretest.Section(t, tc, coretest.Ns, resp.Ns) { t.Logf("%v\n", resp) - } - if !coretest.CheckSection(t, tc, coretest.Extra, resp.Extra) { + if !coretest.Section(t, tc, coretest.Extra, resp.Extra) { t.Logf("%v\n", resp) } } diff --git a/middleware/etcd/other_test.go b/middleware/etcd/other_test.go index da1cc7d74..ac0db7d3e 100644 --- a/middleware/etcd/other_test.go +++ b/middleware/etcd/other_test.go @@ -39,36 +39,17 @@ func TestOtherLookup(t *testing.T) { sort.Sort(coretest.RRSet(resp.Ns)) sort.Sort(coretest.RRSet(resp.Extra)) - if resp.Rcode != tc.Rcode { - t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + if !coretest.Header(t, tc, resp) { 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 !coretest.CheckSection(t, tc, coretest.Answer, resp.Answer) { + if !coretest.Section(t, tc, coretest.Answer, resp.Answer) { t.Logf("%v\n", resp) } - if !coretest.CheckSection(t, tc, coretest.Ns, resp.Ns) { + if !coretest.Section(t, tc, coretest.Ns, resp.Ns) { t.Logf("%v\n", resp) - } - if !coretest.CheckSection(t, tc, coretest.Extra, resp.Extra) { + if !coretest.Section(t, tc, coretest.Extra, resp.Extra) { t.Logf("%v\n", resp) } } diff --git a/middleware/etcd/setup_test.go b/middleware/etcd/setup_test.go index acc062f1d..8fbfd1b5f 100644 --- a/middleware/etcd/setup_test.go +++ b/middleware/etcd/setup_test.go @@ -18,7 +18,6 @@ import ( coretest "github.com/miekg/coredns/middleware/testing" etcdc "github.com/coreos/etcd/client" - "github.com/miekg/dns" "golang.org/x/net/context" ) @@ -79,36 +78,17 @@ func TestLookup(t *testing.T) { sort.Sort(coretest.RRSet(resp.Ns)) sort.Sort(coretest.RRSet(resp.Extra)) - if resp.Rcode != tc.Rcode { - t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + if !coretest.Header(t, tc, resp) { 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 !coretest.CheckSection(t, tc, coretest.Answer, resp.Answer) { + if !coretest.Section(t, tc, coretest.Answer, resp.Answer) { t.Logf("%v\n", resp) } - if !coretest.CheckSection(t, tc, coretest.Ns, resp.Ns) { + if !coretest.Section(t, tc, coretest.Ns, resp.Ns) { t.Logf("%v\n", resp) - } - if !coretest.CheckSection(t, tc, coretest.Extra, resp.Extra) { + if !coretest.Section(t, tc, coretest.Extra, resp.Extra) { t.Logf("%v\n", resp) } } diff --git a/middleware/etcd/stub_test.go b/middleware/etcd/stub_test.go index 789809a50..37bd840a7 100644 --- a/middleware/etcd/stub_test.go +++ b/middleware/etcd/stub_test.go @@ -5,6 +5,7 @@ package etcd import "testing" func TestStubLookup(t *testing.T) { + // MOVE THIS TO etcd_Test.go in the main directory // e.updateStubZones() } diff --git a/middleware/file/wildcard_test.go b/middleware/file/wildcard_test.go index 703938a4e..896889208 100644 --- a/middleware/file/wildcard_test.go +++ b/middleware/file/wildcard_test.go @@ -73,7 +73,6 @@ func TestLookupWildcard(t *testing.T) { t.Logf("%v\n", resp) continue } - if !coretest.Section(t, tc, coretest.Answer, resp.Answer) { t.Logf("%v\n", resp) } diff --git a/server/server.go b/server/server.go index 1816fd1dc..e752d4287 100644 --- a/server/server.go +++ b/server/server.go @@ -29,9 +29,12 @@ import ( // the same address and the listener may be stopped for // graceful termination (POSIX only). type Server struct { - Addr string // Address we listen on - mux *dns.ServeMux - server [2]*dns.Server + Addr string // Address we listen on + mux *dns.ServeMux + server [2]*dns.Server // by convention 0 is tcp and 1 is udp + tcp net.Listener + udp net.PacketConn + tls bool // whether this server is serving all HTTPS hosts or not TLSConfig *tls.Config OnDemandTLS bool // whether this server supports on-demand TLS (load certs at handshake-time) @@ -132,6 +135,16 @@ func New(addr string, configs []Config, gracefulTimeout time.Duration) (*Server, return s, nil } +// LocalAddr return the addresses where the server is bound to. The TCP listener +// address is the first returned, the UDP conn address the second. +func (s *Server) LocalAddr() (net.Addr, net.Addr) { + s.listenerMu.Lock() + tcp := s.tcp.Addr() + udp := s.udp.LocalAddr() + s.listenerMu.Unlock() + return tcp, udp +} + // Serve starts the server with an existing listener. It blocks until the // server stops. /* @@ -155,18 +168,28 @@ func (s *Server) ListenAndServe() error { return err } - // TODO(miek): going out on a limb here, let's assume that listening - // on the part for tcp and udp results in the same error. We can only - // return the error from the udp listener, disregarding whatever - // happenend to the tcp one. + l, err := net.Listen("tcp", s.Addr) + if err != nil { + return err + } + pc, err := net.ListenPacket("udp", s.Addr) + if err != nil { + return err + } + + s.listenerMu.Lock() + s.server[0] = &dns.Server{Listener: l, Net: "tcp", Handler: s.mux} + s.tcp = l + s.server[1] = &dns.Server{PacketConn: pc, Net: "udp", Handler: s.mux} + s.udp = pc + s.listenerMu.Unlock() + go func() { - s.server[0] = &dns.Server{Addr: s.Addr, Net: "tcp", Handler: s.mux} - s.server[0].ListenAndServe() + s.server[0].ActivateAndServe() }() close(s.startChan) // unblock anyone waiting for this to start listening - s.server[1] = &dns.Server{Addr: s.Addr, Net: "udp", Handler: s.mux} - return s.server[1].ListenAndServe() + return s.server[1].ActivateAndServe() } // setup prepares the server s to begin listening; it should be @@ -244,14 +267,11 @@ func (s *Server) Stop() (err error) { if s.listener != nil { err = s.listener.Close() } - s.listenerMu.Unlock() - // Don't know if the above is still valid. for _, s1 := range s.server { - if err := s1.Shutdown(); err != nil { - return err - } + err = s1.Shutdown() } + s.listenerMu.Unlock() return } diff --git a/test/etcd_test.go b/test/etcd_test.go new file mode 100644 index 000000000..96be524e4 --- /dev/null +++ b/test/etcd_test.go @@ -0,0 +1,11 @@ +// +build etcd + +package test + +import "testing" + +// This test starts two coredns servers (and needs etcd). Configure a stubzones in both (that will loop) and +// will then test if we detect this loop. +func TestEtcdStubForwarding(t *testing.T) { + // TODO(miek) +} diff --git a/test/server_test.go b/test/server_test.go new file mode 100644 index 000000000..6020e76d3 --- /dev/null +++ b/test/server_test.go @@ -0,0 +1,53 @@ +package test + +import ( + "testing" + + "github.com/miekg/dns" +) + +// Start 2 tests server, server A will proxy to B, server B is an CH server. +func TestProxyToChaosServer(t *testing.T) { + corefile := `.:0 { + chaos CoreDNS-001 miek@miek.nl +} +` + chaos, tcpCH, udpCH, err := testServer(t, corefile) + if err != nil { + t.Fatalf("Could get server: %s", err) + } + defer chaos.Stop() + + corefileProxy := `.:0 { + proxy . ` + udpCH + ` +} +` + proxy, _, udp, err := testServer(t, corefileProxy) + if err != nil { + t.Fatalf("Could get server: %s", err) + } + defer proxy.Stop() + + chaosTest(t, udpCH, "udp") + chaosTest(t, tcpCH, "tcp") + + chaosTest(t, udp, "udp") + // chaosTest(t, tcp, "tcp"), commented out because we use the original transport to reach the + // proxy and we only forward to the udp port. +} + +func chaosTest(t *testing.T, server, net string) { + m := testMsg("version.bind.", dns.TypeTXT, nil) + m.Question[0].Qclass = dns.ClassCHAOS + + r, err := testExchange(m, server, net) + if err != nil { + t.Fatalf("Could not send message: %s", err) + } + if r.Rcode != dns.RcodeSuccess || len(r.Answer) == 0 { + t.Fatalf("Expected successful reply on %s, got %s", net, dns.RcodeToString[r.Rcode]) + } + if r.Answer[0].String() != `version.bind. 0 CH TXT "CoreDNS-001"` { + t.Fatalf("Expected version.bind. reply, got %s", r.Answer[0].String()) + } +} diff --git a/test/tests.go b/test/tests.go new file mode 100644 index 000000000..2b11a5ac9 --- /dev/null +++ b/test/tests.go @@ -0,0 +1,40 @@ +package test + +import ( + "testing" + "time" + + "github.com/miekg/coredns/core" + "github.com/miekg/coredns/middleware" + "github.com/miekg/coredns/server" + + "github.com/miekg/dns" +) + +func testMsg(zone string, typ uint16, o *dns.OPT) *dns.Msg { + m := new(dns.Msg) + m.SetQuestion(zone, typ) + if o != nil { + m.Extra = []dns.RR{o} + } + return m +} + +func testExchange(m *dns.Msg, server, net string) (*dns.Msg, error) { + c := new(dns.Client) + c.Net = net + return middleware.Exchange(c, m, server) +} + +// testServer returns a test server and the tcp and udp listeners addresses. +func testServer(t *testing.T, corefile string) (*server.Server, string, string, error) { + srv, err := core.TestServer(t, corefile) + if err != nil { + return nil, "", "", err + } + go srv.ListenAndServe() + + time.Sleep(1 * time.Second) + tcp, udp := srv.LocalAddr() + return srv, tcp.String(), udp.String(), nil +}