diff --git a/common/errors.go b/common/errors.go new file mode 100644 index 00000000..23072055 --- /dev/null +++ b/common/errors.go @@ -0,0 +1,13 @@ +package common + +func CheckFatal(e error) { + if e != nil { + Error.Fatal(e) + } +} + +func CheckWarn(e error) { + if e != nil { + Warning.Println(e) + } +} diff --git a/nameserver/mdns_client_test.go b/nameserver/mdns_client_test.go index aa9ad6a5..c21c1f18 100644 --- a/nameserver/mdns_client_test.go +++ b/nameserver/mdns_client_test.go @@ -2,6 +2,7 @@ package nameserver import ( "github.com/miekg/dns" + . "github.com/zettio/weave/common" wt "github.com/zettio/weave/testing" "log" "net" @@ -20,18 +21,18 @@ func minimalServer(w dns.ResponseWriter, req *dns.Msg) { sendAnswer := func(m *dns.Msg) { buf, err := m.Pack() - checkFatal(err) + CheckFatal(err) if buf == nil { log.Fatal("Nil buffer") } //log.Println("minimalServer sending:", buf) // This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work sendconn, err := net.DialUDP("udp", nil, ipv4Addr) - checkFatal(err) + CheckFatal(err) _, err = sendconn.Write(buf) sendconn.Close() - checkFatal(err) + CheckFatal(err) } //log.Println("minimalServer received:", req) diff --git a/nameserver/server.go b/nameserver/server.go index 3975053c..c2a680e8 100644 --- a/nameserver/server.go +++ b/nameserver/server.go @@ -5,6 +5,7 @@ import ( "github.com/miekg/dns" . "github.com/zettio/weave/common" "net" + "sync" ) const ( @@ -12,18 +13,6 @@ const ( UDPBufSize = 4096 // bigger than the default 512 ) -func checkFatal(e error) { - if e != nil { - Error.Fatal(e) - } -} - -func checkWarn(e error) { - if e != nil { - Warning.Println(e) - } -} - func makeDNSFailResponse(r *dns.Msg) *dns.Msg { m := new(dns.Msg) m.SetReply(r) @@ -32,7 +21,100 @@ func makeDNSFailResponse(r *dns.Msg) *dns.Msg { return m } -func queryHandler(lookups []Lookup) dns.HandlerFunc { +type DNSServer struct { + config *dns.ClientConfig + zone Zone + iface *net.Interface + port int + udpSrv *dns.Server + tcpSrv *dns.Server +} + +// Creates a new DNS server with a given config +func NewDNSServer(config *dns.ClientConfig, zone Zone, iface *net.Interface, port int) *DNSServer { + return &DNSServer{ + config: config, + zone: zone, + iface: iface, + port: port, + } +} + +// Start the DNS server +func (s *DNSServer) Start() error { + Info.Printf("WeaveDNS server starting...") + + mdnsClient, err := NewMDNSClient() + CheckFatal(err) + + ifaceName := "default interface" + if s.iface != nil { + ifaceName = s.iface.Name + } + Info.Printf("Using mDNS on %s", ifaceName) + err = mdnsClient.Start(s.iface) + CheckFatal(err) + + // create two DNS request multiplexerers, depending on the protocol used by clients + // (we use the same protocol for asking upstream servers) + mux := func(client *dns.Client) *dns.ServeMux { + m := dns.NewServeMux() + m.HandleFunc(LOCAL_DOMAIN, s.queryHandler([]Lookup{s.zone, mdnsClient})) + m.HandleFunc(RDNS_DOMAIN, s.rdnsHandler([]Lookup{s.zone, mdnsClient}, client)) + m.HandleFunc(".", s.notUsHandler(client)) + return m + } + + mdnsServer, err := NewMDNSServer(s.zone) + CheckFatal(err) + + err = mdnsServer.Start(s.iface) + CheckFatal(err) + + address := fmt.Sprintf(":%d", s.port) + s.udpSrv = &dns.Server{Addr: address, Net: "udp", Handler: mux(&dns.Client{Net: "udp", UDPSize: UDPBufSize})} + s.tcpSrv = &dns.Server{Addr: address, Net: "tcp", Handler: mux(&dns.Client{Net: "tcp"})} + + wg := new(sync.WaitGroup) + wg.Add(2) + + go func() { + defer wg.Done() + + Debug.Printf("Listening for DNS on %s (UDP)", address) + err = s.udpSrv.ListenAndServe() + CheckFatal(err) + Debug.Printf("DNS UDP server exiting...") + }() + + go func() { + defer wg.Done() + + Debug.Printf("Listening for DNS on %s (TCP)", address) + err = s.tcpSrv.ListenAndServe() + CheckFatal(err) + Debug.Printf("DNS TCP server exiting...") + }() + + // Waiting for all goroutines to finish (otherwise they die as main routine dies) + wg.Wait() + + Info.Printf("WeaveDNS server exiting...") + return nil +} + +// Perform a graceful shutdown +func (s *DNSServer) Stop() error { + if err := s.tcpSrv.Shutdown(); err != nil { + return err + } + if err := s.udpSrv.Shutdown(); err != nil { + return err + } + return nil +} + +func (s *DNSServer) queryHandler(lookups []Lookup) dns.HandlerFunc { return func(w dns.ResponseWriter, r *dns.Msg) { q := r.Question[0] Debug.Printf("Query: %+v", q) @@ -51,8 +133,8 @@ func queryHandler(lookups []Lookup) dns.HandlerFunc { } } -func rdnsHandler(config *dns.ClientConfig, lookups []Lookup) dns.HandlerFunc { - fallback := notUsHandler(config) +func (s *DNSServer) rdnsHandler(lookups []Lookup, client *dns.Client) dns.HandlerFunc { + fallback := s.notUsHandler(client) return func(w dns.ResponseWriter, r *dns.Msg) { q := r.Question[0] Debug.Printf("Reverse query: %+v", q) @@ -72,17 +154,14 @@ func rdnsHandler(config *dns.ClientConfig, lookups []Lookup) dns.HandlerFunc { } } -/* When we receive a request for a name outside of our '.weave.local.' - domain, ask the configured DNS server as a fallback. -*/ -func notUsHandler(config *dns.ClientConfig) dns.HandlerFunc { - dnsClient := new(dns.Client) - dnsClient.UDPSize = UDPBufSize +// When we receive a request for a name outside of our '.weave.local.' +// domain, ask the configured DNS server as a fallback. +func (s *DNSServer) notUsHandler(dnsClient *dns.Client) dns.HandlerFunc { return func(w dns.ResponseWriter, r *dns.Msg) { q := r.Question[0] Debug.Printf("[dns msgid %d] Fallback query: %+v", r.MsgHdr.Id, q) - for _, server := range config.Servers { - reply, _, err := dnsClient.Exchange(r, fmt.Sprintf("%s:%s", server, config.Port)) + for _, server := range s.config.Servers { + reply, _, err := dnsClient.Exchange(r, fmt.Sprintf("%s:%s", server, s.config.Port)) if err != nil { Debug.Printf("[dns msgid %d] Network error trying %s (%s)", r.MsgHdr.Id, server, err) @@ -103,40 +182,3 @@ func notUsHandler(config *dns.ClientConfig) dns.HandlerFunc { w.WriteMsg(makeDNSFailResponse(r)) } } - -func StartServer(zone Zone, iface *net.Interface, dnsPort int, wait int) error { - config, err := dns.ClientConfigFromFile("/etc/resolv.conf") - checkFatal(err) - return startServerWithConfig(config, zone, iface, dnsPort, wait) -} - -func startServerWithConfig(config *dns.ClientConfig, zone Zone, iface *net.Interface, dnsPort int, wait int) error { - mdnsClient, err := NewMDNSClient() - checkFatal(err) - - ifaceName := "default interface" - if iface != nil { - ifaceName = iface.Name - } - Info.Printf("Using mDNS on %s", ifaceName) - err = mdnsClient.Start(iface) - checkFatal(err) - - LocalServeMux := dns.NewServeMux() - LocalServeMux.HandleFunc(LOCAL_DOMAIN, queryHandler([]Lookup{zone, mdnsClient})) - LocalServeMux.HandleFunc(RDNS_DOMAIN, rdnsHandler(config, []Lookup{zone, mdnsClient})) - LocalServeMux.HandleFunc(".", notUsHandler(config)) - - mdnsServer, err := NewMDNSServer(zone) - checkFatal(err) - - err = mdnsServer.Start(iface) - checkFatal(err) - - address := fmt.Sprintf(":%d", dnsPort) - Info.Printf("Listening for DNS on %s", address) - err = dns.ListenAndServe(address, "udp", LocalServeMux) - checkFatal(err) - - return nil -} diff --git a/nameserver/server_test.go b/nameserver/server_test.go index c42fc4d7..bde9d722 100644 --- a/nameserver/server_test.go +++ b/nameserver/server_test.go @@ -1,9 +1,10 @@ package nameserver import ( + "encoding/binary" "fmt" "github.com/miekg/dns" - "github.com/zettio/weave/common" + . "github.com/zettio/weave/common" wt "github.com/zettio/weave/testing" "net" "testing" @@ -16,7 +17,7 @@ const ( testRDNSnonlocal = "8.8.8.8.in-addr.arpa." ) -func TestDNSServer(t *testing.T) { +func TestUDPDNSServer(t *testing.T) { const ( port = 17625 successTestName = "test1.weave.local." @@ -27,13 +28,37 @@ func TestDNSServer(t *testing.T) { dnsAddr := fmt.Sprintf("localhost:%d", port) testCIDR1 := testAddr1 + "/24" - common.InitDefaultLogging(true) + InitDefaultLogging(true) var zone = new(ZoneDb) ip, _, _ := net.ParseCIDR(testCIDR1) zone.AddRecord(containerID, successTestName, ip) + fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetReply(req) + if len(req.Question) == 1 { + q := req.Question[0] + if q.Name == "weave.works." && q.Qtype == dns.TypeMX { + m.Answer = make([]dns.RR, 1) + m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail.weave.works."} + } else if q.Name == "weave.works." && q.Qtype == dns.TypeANY { + const N = 10 + m.Extra = make([]dns.RR, N) + for i, _ := range m.Extra { + m.Extra[i] = &dns.TXT{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"Lots and lots and lots and lots and lots and lots and lots and lots and lots of data"}} + } + } else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR { + m.Answer = make([]dns.RR, 1) + m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."} + } else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR { + m.Rcode = dns.RcodeNameError + } + } + w.WriteMsg(m) + } + // Run another DNS server for fallback - s, fallbackAddr, err := RunLocalUDPServer("127.0.0.1:0") + s, fallbackAddr, err := RunLocalUDPServer(t, "127.0.0.1:0", fallbackHandler) wt.AssertNoErr(t, err) defer s.Shutdown() @@ -41,7 +66,9 @@ func TestDNSServer(t *testing.T) { wt.AssertNoErr(t, err) config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort} - go startServerWithConfig(config, zone, nil, port, 0) + srv := NewDNSServer(config, zone, nil, port) + defer srv.Stop() + go srv.Start() time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start c := new(dns.Client) @@ -49,6 +76,7 @@ func TestDNSServer(t *testing.T) { m := new(dns.Msg) m.SetQuestion(successTestName, dns.TypeA) m.RecursionDesired = true + r, _, err := c.Exchange(m, dnsAddr) wt.AssertNoErr(t, err) wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code") @@ -112,41 +140,131 @@ func TestDNSServer(t *testing.T) { // needs two servers, each listening on its own address } -func fallbackHandler(w dns.ResponseWriter, req *dns.Msg) { - m := new(dns.Msg) - m.SetReply(req) - if len(req.Question) == 1 { - q := req.Question[0] - if q.Name == "weave.works." && q.Qtype == dns.TypeMX { - m.Answer = make([]dns.RR, 1) - m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail.weave.works."} - } else if q.Name == "weave.works." && q.Qtype == dns.TypeANY { - const N = 10 - m.Extra = make([]dns.RR, N) - for i, _ := range m.Extra { - m.Extra[i] = &dns.TXT{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"Lots and lots and lots and lots and lots and lots and lots and lots and lots of data"}} - } - } else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR { - m.Answer = make([]dns.RR, 1) - m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."} - } else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR { - m.Rcode = dns.RcodeNameError - } +func TestTCPDNSServer(t *testing.T) { + const ( + port = 17625 + numAnswers = 512 + nonLocalName = "weave.works." + ) + dnsAddr := fmt.Sprintf("localhost:%d", port) + + InitDefaultLogging(true) + var zone = new(ZoneDb) + + var addrs []net.IP + bs := make([]byte, 4) + for i := 0; i < numAnswers; i++ { + binary.LittleEndian.PutUint32(bs, uint32(i)) + addrs = append(addrs, net.IPv4(bs[0], bs[1], bs[2], bs[3])) } - w.WriteMsg(m) + + // handler for the fallback server: it will just return a very long response + fallbackUDPHandler := func(w dns.ResponseWriter, req *dns.Msg) { + t.Logf("Fallback UDP server got asked: returning %d answers", numAnswers) + q := req.Question[0] + m := makeAddressReply(req, &q, addrs) + m.Truncated = true + w.WriteMsg(m) + } + fallbackTCPHandler := func(w dns.ResponseWriter, req *dns.Msg) { + t.Logf("Fallback TCP server got asked: returning %d answers", numAnswers) + q := req.Question[0] + m := makeAddressReply(req, &q, addrs) + w.WriteMsg(m) + } + + // Run another DNS server for fallback + us, fallbackUdpAddr, err := RunLocalUDPServer(t, "127.0.0.1:0", fallbackUDPHandler) + wt.AssertNoErr(t, err) + defer us.Shutdown() + + _, fallbackPort, err := net.SplitHostPort(fallbackUdpAddr) + wt.AssertNoErr(t, err) + + // start the TCP server on the same port as the UDP server + fallbackTcpAddr := fmt.Sprintf("127.0.0.1:%s", fallbackPort) + ts, fallbackTcpAddr, err := RunLocalTCPServer(t, fallbackTcpAddr, fallbackTCPHandler) + wt.AssertNoErr(t, err) + defer ts.Shutdown() + + t.Logf("Creating a WeaveDNS server instance, falling back to 127.0.0.1:%s", fallbackPort) + config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort} + srv := NewDNSServer(config, zone, nil, port) + defer srv.Stop() + go srv.Start() + time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start + + t.Logf("Creating a UDP and a TCP client") + uc := new(dns.Client) + uc.UDPSize = UDPBufSize + tc := new(dns.Client) + tc.Net = "tcp" + + t.Logf("Creating DNS query message") + m := new(dns.Msg) + m.RecursionDesired = true + m.SetQuestion(nonLocalName, dns.TypeA) + + t.Logf("Checking the fallback server at %s returns a truncated response with UDP", fallbackUdpAddr) + r, _, err := uc.Exchange(m, fallbackUdpAddr) + t.Logf("Got response from fallback server (UDP) with %d answers", len(r.Answer)) + t.Logf("Response:\n%+v\n", r) + wt.AssertNoErr(t, err) + wt.AssertTrue(t, r.MsgHdr.Truncated, "DNS truncated reponse flag") + wt.AssertNotEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP)") + + t.Logf("Checking the WeaveDNS server at %s returns a truncated reponse with UDP", dnsAddr) + r, _, err = uc.Exchange(m, dnsAddr) + t.Logf("Got response from WeaveDNS (UDP) with %d answers", len(r.Answer)) + t.Logf("Response:\n%+v\n", r) + wt.AssertNoErr(t, err) + wt.AssertTrue(t, r.MsgHdr.Truncated, "DNS truncated reponse flag") + wt.AssertNotEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP)") + + t.Logf("Checking the WeaveDNS server at %s does not return a truncated reponse with TCP", dnsAddr) + r, _, err = tc.Exchange(m, dnsAddr) + t.Logf("Got response from WeaveDNS (TCP) with %d answers", len(r.Answer)) + t.Logf("Response:\n%+v\n", r) + wt.AssertNoErr(t, err) + wt.AssertFalse(t, r.MsgHdr.Truncated, "DNS truncated response flag") + wt.AssertEqualInt(t, len(r.Answer), numAnswers, "number of answers (TCP)") } -func RunLocalUDPServer(laddr string) (*dns.Server, string, error) { +func RunLocalUDPServer(t *testing.T, laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) { + t.Logf("Starting fallback UDP server at %s", laddr) pc, err := net.ListenPacket("udp", laddr) if err != nil { return nil, "", err } - server := &dns.Server{PacketConn: pc, Handler: dns.HandlerFunc(fallbackHandler)} + server := &dns.Server{PacketConn: pc, Handler: handler} go func() { server.ActivateAndServe() pc.Close() }() + t.Logf("Fallback UDP server listening at %s", pc.LocalAddr()) return server, pc.LocalAddr().String(), nil } + +func RunLocalTCPServer(t *testing.T, laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) { + t.Logf("Starting fallback TCP server at %s", laddr) + laddrTcp, err := net.ResolveTCPAddr("tcp", laddr) + if err != nil { + return nil, "", err + } + + l, err := net.ListenTCP("tcp", laddrTcp) + if err != nil { + return nil, "", err + } + server := &dns.Server{Listener: l, Handler: handler} + + go func() { + server.ActivateAndServe() + l.Close() + }() + + t.Logf("Fallback TCP server listening at %s", l.Addr().String()) + return server, l.Addr().String(), nil +} diff --git a/testing/util.go b/testing/util.go index 2222fd85..7517674e 100644 --- a/testing/util.go +++ b/testing/util.go @@ -7,6 +7,18 @@ import ( "time" ) +func AssertTrue(t *testing.T, cond bool, desc string) { + if !cond { + Fatalf(t, "Expected %s to be true", desc) + } +} + +func AssertFalse(t *testing.T, cond bool, desc string) { + if cond { + Fatalf(t, "Expected %s to be false", desc) + } +} + func AssertNoErr(t *testing.T, err error) { if err != nil { Fatalf(t, "Unexpected error: %s", err) @@ -25,6 +37,12 @@ func AssertEqualInt(t *testing.T, got, wanted int, desc string) { } } +func AssertNotEqualInt(t *testing.T, got, wanted int, desc string) { + if got == wanted { + Fatalf(t, "Expected %s %d to be different to %d", desc, wanted, got) + } +} + func AssertEqualString(t *testing.T, got, wanted string, desc string) { if got != wanted { Fatalf(t, "Expected %s '%s' but got '%s'", desc, wanted, got) diff --git a/weavedns/main.go b/weavedns/main.go index 2898b96e..1c788b77 100644 --- a/weavedns/main.go +++ b/weavedns/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "github.com/miekg/dns" . "github.com/zettio/weave/common" weavedns "github.com/zettio/weave/nameserver" weavenet "github.com/zettio/weave/net" @@ -18,19 +19,22 @@ func main() { justVersion bool ifaceName string apiPath string + fallback string dnsPort int httpPort int wait int watch bool debug bool + err error ) flag.BoolVar(&justVersion, "version", false, "print version and exit") flag.StringVar(&ifaceName, "iface", "", "name of interface to use for multicast") flag.StringVar(&apiPath, "api", "unix:///var/run/docker.sock", "Path to Docker API socket") flag.IntVar(&wait, "wait", 0, "number of seconds to wait for interface to be created and come up") - flag.IntVar(&dnsPort, "dnsport", 53, "port to listen to dns requests") + flag.IntVar(&dnsPort, "dnsport", 53, "port to listen to DNS requests") flag.IntVar(&httpPort, "httpport", 6785, "port to listen to HTTP requests") + flag.StringVar(&fallback, "fallback", "", "Fallback server and port (ie, '8.8.8.8:53')") flag.BoolVar(&watch, "watch", true, "watch the docker socket for container events") flag.BoolVar(&debug, "debug", false, "output debugging info to stderr") flag.Parse() @@ -63,8 +67,24 @@ func main() { } } + var config *dns.ClientConfig + if len(fallback) > 0 { + fallbackHost, fallbackPort, err := net.SplitHostPort(fallback) + if err != nil { + Error.Fatal("Fould not parse fallback host and port", err) + } + config = &dns.ClientConfig{Servers: []string{fallbackHost}, Port: fallbackPort} + Debug.Printf("DNS fallback at %s:%s", fallbackHost, fallbackPort) + } else { + config, err = dns.ClientConfigFromFile("/etc/resolv.conf") + if err != nil { + Error.Fatal("Failed obtain DNS client configuration", err) + } + } + go weavedns.ListenHttp(weavedns.LOCAL_DOMAIN, zone, httpPort) - err := weavedns.StartServer(zone, iface, dnsPort, wait) + srv := weavedns.NewDNSServer(config, zone, iface, dnsPort) + err = srv.Start() if err != nil { Error.Fatal("Failed to start server", err) }