From d292e08bb2efdda8f9245aa2ad400a12c9191b5a Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 2 Jan 2014 13:12:05 -0800 Subject: [PATCH] Adding basic DNS framework --- command/agent/agent_test.go | 11 ++-- command/agent/config.go | 4 ++ command/agent/dns.go | 106 ++++++++++++++++++++++++++++++++++++ command/agent/dns_test.go | 44 +++++++++++++++ 4 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 command/agent/dns.go create mode 100644 command/agent/dns_test.go diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 151b43c42..9b3cba657 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -20,13 +20,14 @@ func nextConfig() *Config { conf.Bootstrap = true conf.Datacenter = "dc1" conf.NodeName = fmt.Sprintf("Node %d", idx) - conf.HTTPAddr = fmt.Sprintf("127.0.0.1:%d", 8500+10*idx) - conf.RPCAddr = fmt.Sprintf("127.0.0.1:%d", 8400+10*idx) + conf.DNSAddr = fmt.Sprintf("127.0.0.1:%d", 18600+idx) + conf.HTTPAddr = fmt.Sprintf("127.0.0.1:%d", 18500+idx) + conf.RPCAddr = fmt.Sprintf("127.0.0.1:%d", 18400+idx) conf.SerfBindAddr = "127.0.0.1" - conf.SerfLanPort = int(8301 + 10*idx) - conf.SerfWanPort = int(8302 + 10*idx) + conf.SerfLanPort = int(18200 + idx) + conf.SerfWanPort = int(18300 + idx) conf.Server = true - conf.ServerAddr = fmt.Sprintf("127.0.0.1:%d", 8100+10*idx) + conf.ServerAddr = fmt.Sprintf("127.0.0.1:%d", 18100+idx) cons := consul.DefaultConfig() conf.ConsulConfig = cons diff --git a/command/agent/config.go b/command/agent/config.go index b4910157e..557ea1992 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -27,6 +27,9 @@ type Config struct { // DataDir is the directory to store our state in DataDir string + // DNSAddr is the address of the DNS server for the agent + DNSAddr string + // Encryption key to use for the Serf communication EncryptKey string @@ -87,6 +90,7 @@ type dirEnts []os.FileInfo func DefaultConfig() *Config { return &Config{ Datacenter: consul.DefaultDC, + DNSAddr: "127.0.0.1:8600", HTTPAddr: "127.0.0.1:8500", LogLevel: "INFO", RPCAddr: "127.0.0.1:8400", diff --git a/command/agent/dns.go b/command/agent/dns.go new file mode 100644 index 000000000..2cef1d8a8 --- /dev/null +++ b/command/agent/dns.go @@ -0,0 +1,106 @@ +package agent + +import ( + "fmt" + "github.com/miekg/dns" + "io" + "log" + "time" +) + +// DNSServer is used to wrap an Agent and expose various +// service discovery endpoints using a DNS interface. +type DNSServer struct { + agent *Agent + dnsHandler *dns.ServeMux + dnsServer *dns.Server + logger *log.Logger +} + +// NewDNSServer starts a new DNS server to provide an agent interface +func NewDNSServer(agent *Agent, logOutput io.Writer, bind string) (*DNSServer, error) { + // Construct the DNS components + mux := dns.NewServeMux() + + // Setup the server + server := &dns.Server{ + Addr: bind, + Net: "udp", + Handler: mux, + UDPSize: 65535, + } + + // Create the server + srv := &DNSServer{ + agent: agent, + dnsHandler: mux, + dnsServer: server, + logger: log.New(logOutput, "", log.LstdFlags), + } + + // Register mux handlers + mux.HandleFunc("consul.", srv.handleConsul) + + // Async start the DNS Server, handle a potential error + errCh := make(chan error, 1) + go func() { + srv.logger.Printf("[INFO] dns: starting server at %v", bind) + err := server.ListenAndServe() + srv.logger.Printf("[ERR] dns: error starting server: %v", err) + errCh <- err + }() + + // Check the server is running, do a test lookup + checkCh := make(chan error, 1) + go func() { + // This is jank, but we have no way to edge trigger on + // the start of our server, so we just wait and hope it is up. + time.Sleep(50 * time.Millisecond) + + m := new(dns.Msg) + m.SetQuestion("_test.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, bind) + if err != nil { + checkCh <- err + return + } + + srv.logger.Printf("resp %#v", in) + if len(in.Answer) == 0 { + checkCh <- fmt.Errorf("no response to test message") + return + } + close(checkCh) + }() + + // Wait for either the check, listen error, or timeout + select { + case e := <-errCh: + return srv, e + case e := <-checkCh: + return srv, e + case <-time.After(time.Second): + return srv, fmt.Errorf("timeout setting up DNS server") + } + return srv, nil +} + +// handleConsul is used to handle DNS queries in the ".consul." domain +func (d *DNSServer) handleConsul(resp dns.ResponseWriter, req *dns.Msg) { + q := req.Question[0] + d.logger.Printf("[DEBUG] dns: request for %v", q) + + if q.Qtype != dns.TypeANY && q.Qtype != dns.TypeTXT { + return + } + + // Always respond with TXT "ok" + m := new(dns.Msg) + m.SetReply(req) + header := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0} + txt := &dns.TXT{header, []string{"ok"}} + m.Answer = append(m.Answer, txt) + resp.WriteMsg(m) +} diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go new file mode 100644 index 000000000..6c66ba680 --- /dev/null +++ b/command/agent/dns_test.go @@ -0,0 +1,44 @@ +package agent + +import ( + "github.com/miekg/dns" + "os" + "testing" +) + +func makeDNSServer(t *testing.T) (string, *DNSServer) { + conf := nextConfig() + dir, agent := makeAgent(t, conf) + server, err := NewDNSServer(agent, agent.logOutput, conf.DNSAddr) + if err != nil { + t.Fatalf("err: %v", err) + } + return dir, server +} + +func TestDNS_IsAlive(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + m := new(dns.Msg) + m.SetQuestion("_test.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, srv.agent.config.DNSAddr) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + txt, ok := in.Answer[0].(*dns.TXT) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if txt.Txt[0] != "ok" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } +}