diff --git a/command/agent/dns.go b/command/agent/dns.go index b83243720..5e4480ac2 100644 --- a/command/agent/dns.go +++ b/command/agent/dns.go @@ -266,18 +266,33 @@ PARSE: goto INVALID } - // Extract the service - service := labels[n-2] + // Support RFC 2782 style syntax + if n == 3 && strings.HasPrefix(labels[n-2], "_") && strings.HasPrefix(labels[n-3], "_") { - // Support "." in the label, re-join all the parts - tag := "" - if n >= 3 { - tag = strings.Join(labels[:n-2], ".") + // Grab the tag since we make nuke it if it's tcp + tag := labels[n-2][1:] + + // Treat _name._tcp.service.consul as a default, no need to filter on that tag + if tag == "tcp" { + tag = "" + } + + // _name._tag.service.consul + d.serviceLookup(network, datacenter, labels[n-3][1:], tag, req, resp) + + // Consul 0.3 and prior format for SRV queries + } else { + + // Support "." in the label, re-join all the parts + tag := "" + if n >= 3 { + tag = strings.Join(labels[:n-2], ".") + } + + // tag[.tag].name.service.consul + d.serviceLookup(network, datacenter, labels[n-2], tag, req, resp) } - // Handle lookup with and without tag - d.serviceLookup(network, datacenter, service, tag, req, resp) - case "node": if len(labels) == 1 { goto INVALID diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go index 7d4226614..0107cca72 100644 --- a/command/agent/dns_test.go +++ b/command/agent/dns_test.go @@ -1120,3 +1120,137 @@ func TestDNS_ServiceLookup_TTL(t *testing.T) { t.Fatalf("Bad: %#v", in.Extra[0]) } } + +func TestDNS_ServiceLookup_SRV_RFC(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: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + } + + 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("_db._master.service.consul.", dns.TypeSRV) + + 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) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } +} + +func TestDNS_ServiceLookup_SRV_RFC_TCP_Default(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: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + } + + 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("_db._tcp.service.consul.", dns.TypeSRV) + + 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) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } +} diff --git a/website/source/docs/agent/dns.html.markdown b/website/source/docs/agent/dns.html.markdown index 938c989a6..ca4f06b5f 100644 --- a/website/source/docs/agent/dns.html.markdown +++ b/website/source/docs/agent/dns.html.markdown @@ -73,9 +73,14 @@ the node. ## Service Lookups A service lookup is the alternate type of query. It is used to query for service -providers. The format of a service lookup is like the following: +providers and supports two mode of lookup, a strict RCF style lookup and the +standard lookup. - ..service.. +### Standard Style Lookup + +The format of a standard service lookup is like the following: + + [tag.].service[.datacenter][.domain] As with node lookups, the `datacenter` is optional, as is the `tag`. If no tag is provided, then no filtering is done on tag. So, if we want to find any redis service @@ -114,6 +119,46 @@ SRV records. ;; ADDITIONAL SECTION: foobar.node.dc1.consul. 0 IN A 10.1.10.12 +### RFC-2782 Style Lookup + +The format for RFC style lookups uses the following format: + + _._.service[.datacenter][.domain] + +Per [RFC-2782](https://www.ietf.org/rfc/rfc2782.txt), SRV queries should use +underscores (_) as a prefix to the `service` and `protocol` values in a query to +prevent DNS collisions. The `protocol` value can be any of the tags for a +service or if the service has no tags, the value "tcp" should be used. If "tcp" +is specified as the protocol, the query will not perform any tag filtering. + +Other than the query format and default "tcp" protocol/tag value, the behavior +of the RFC style lookup is the same as the standard style of lookup. + +Using the RCF style lookup, If you registered the service "rabbitmq" on port +5672 and tagged it with "amqp" you would query the SRV record as +"_rabbitmq._amqp.service.consul" as illustrated in the example below: + + $ dig @127.0.0.1 -p 8600 consul.service.consul SRV + + ; <<>> DiG 9.8.3-P1 <<>> @127.0.0.1 -p 8600 _rabbitmq._amqp.service.consul ANY + ; (1 server found) + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52838 + ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 + ;; WARNING: recursion requested but not available + + ;; QUESTION SECTION: + ;_rabbitmq._amqp.service.consul. IN SRV + + ;; ANSWER SECTION: + _rabbitmq._amqp.service.consul. 0 IN SRV 1 1 5672 rabbitmq.node1.dc1.consul. + + ;; ADDITIONAL SECTION: + rabbitmq.node1.dc1.consul. 0 IN A 10.1.11.20 + +### UDP Based DNS Queries + When the DNS query is performed using UDP, Consul will truncate the results without setting the truncate bit. This is to prevent a redundant lookup over TCP which generate additional load. If the lookup is done over TCP, the results @@ -125,4 +170,3 @@ By default, all DNS results served by Consul set a 0 TTL value. This disables caching of DNS results. However, there are many situations in which caching is desirable for performance and scalability. This is discussed more in the guide for [DNS Caching](/docs/guides/dns-cache.html). -