Merge pull request #288 from gmr/rfc-2782-srv-lookups

Add RFC-2782 style SRV lookups
This commit is contained in:
Armon Dadgar 2014-08-18 16:01:18 -07:00
commit 98b83d9c0d
3 changed files with 205 additions and 12 deletions

View file

@ -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

View file

@ -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])
}
}

View file

@ -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.
<tag>.<service>.service.<datacenter>.<domain>
### Standard Style Lookup
The format of a standard service lookup is like the following:
[tag.]<service>.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>._<protocol>.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).