Skip to content

Commit

Permalink
Merge pull request #288 from gmr/rfc-2782-srv-lookups
Browse files Browse the repository at this point in the history
Add RFC-2782 style SRV lookups
  • Loading branch information
armon committed Aug 18, 2014
2 parents 869d7aa + b030991 commit ca1aeb8
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 12 deletions.
33 changes: 24 additions & 9 deletions command/agent/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,17 +266,32 @@ 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)

// Handle lookup with and without tag
d.serviceLookup(network, datacenter, service, 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)
}

case "node":
if len(labels) == 1 {
Expand Down
134 changes: 134 additions & 0 deletions command/agent/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
}
50 changes: 47 additions & 3 deletions website/source/docs/agent/dns.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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).

0 comments on commit ca1aeb8

Please sign in to comment.