diff --git a/command/agent/dns.go b/command/agent/dns.go index 63810913c2..15378f58ce 100644 --- a/command/agent/dns.go +++ b/command/agent/dns.go @@ -66,6 +66,9 @@ func NewDNSServer(agent *Agent, config *DNSConfig, logOutput io.Writer, domain s logger: log.New(logOutput, "", log.LstdFlags), } + // Register mux handler, for reverse lookup + mux.HandleFunc("arpa.", srv.handlePtr) + // Register mux handlers, always handle "consul." mux.HandleFunc(domain, srv.handleQuery) if domain != consulDomain { @@ -162,6 +165,55 @@ START: return addr.String(), nil } +// handlePtr is used to handle "reverse" DNS queries +func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) { + q := req.Question[0] + defer func(s time.Time) { + d.logger.Printf("[DEBUG] dns: request for %v (%v)", q, time.Now().Sub(s)) + }(time.Now()) + + // Setup the message response + m := new(dns.Msg) + m.SetReply(req) + m.Authoritative = true + m.RecursionAvailable = (len(d.recursors) > 0) + + // Only add the SOA if requested + if req.Question[0].Qtype == dns.TypeSOA { + d.addSOA(d.domain, m) + } + + datacenter := d.agent.config.Datacenter + + // Get the QName without the domain suffix + qName := strings.ToLower(dns.Fqdn(req.Question[0].Name)) + + args := structs.DCSpecificRequest{ + Datacenter: datacenter, + QueryOptions: structs.QueryOptions{AllowStale: d.config.AllowStale}, + } + var out structs.IndexedNodes + + if err := d.agent.RPC("Catalog.ListNodes", &args, &out); err == nil { + for _, n := range out.Nodes { + arpa, _ := dns.ReverseAddr(n.Address) + if arpa == qName { + ptr := &dns.PTR{ + Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, + Ptr: fmt.Sprintf("%s.node.%s.consul.", n.Node, datacenter), + } + m.Answer = append(m.Answer, ptr) + break + } + } + } + + // Write out the complete response + if err := resp.WriteMsg(m); err != nil { + d.logger.Printf("[WARN] dns: failed to respond: %v", err) + } +} + // handleQUery is used to handle DNS queries in the configured domain func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) { q := req.Question[0] diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go index e253294973..6d1ca4f71c 100644 --- a/command/agent/dns_test.go +++ b/command/agent/dns_test.go @@ -304,6 +304,89 @@ func TestDNS_NodeLookup_CNAME(t *testing.T) { } } +func TestDNS_ReverseLookup(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + testutil.WaitForLeader(t, srv.agent.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo2", + Address: "127.0.0.2", + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) + + c := new(dns.Client) + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != "foo2.node.dc1.consul." { + t.Fatalf("Bad: %#v", ptrRec) + } +} + +func TestDNS_ReverseLookup_IPV6(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + testutil.WaitForLeader(t, srv.agent.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "::4242:4242", + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion("2.4.2.4.2.4.2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", dns.TypeANY) + + c := new(dns.Client) + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != "bar.node.dc1.consul." { + t.Fatalf("Bad: %#v", ptrRec) + } +} func TestDNS_ServiceLookup(t *testing.T) { dir, srv := makeDNSServer(t) defer os.RemoveAll(dir)