Support RFC 2782 for prepared query DNS lookups (#14465)

Format:
	_<query id or name>._tcp.query[.<datacenter>].<domain>
This commit is contained in:
Jared Kirschner 2022-11-20 17:21:24 -05:00 committed by GitHub
parent fb46ae870c
commit b97acfb107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 9 deletions

3
.changelog/14465.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
dns: support RFC 2782 SRV lookups for prepared queries using format `_<query id or name>._tcp.query[.<datacenter>].<domain>`.
```

View File

@ -908,10 +908,11 @@ func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, maxRecursi
return d.nodeLookup(cfg, lookup, req, resp) return d.nodeLookup(cfg, lookup, req, resp)
case "query": case "query":
n := len(queryParts)
datacenter := d.agent.config.Datacenter datacenter := d.agent.config.Datacenter
// ensure we have a query name // ensure we have a query name
if len(queryParts) < 1 { if n < 1 {
return invalid() return invalid()
} }
@ -919,8 +920,23 @@ func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, maxRecursi
return invalid() return invalid()
} }
// Allow a "." in the query name, just join all the parts. query := ""
query := strings.Join(queryParts, ".")
// If the first and last DNS query parts begin with _, this is an RFC 2782 style SRV lookup.
// This allows for prepared query names to include "." (for backwards compatibility).
// Otherwise, this is a standard prepared query lookup.
if n >= 2 && strings.HasPrefix(queryParts[0], "_") && strings.HasPrefix(queryParts[n-1], "_") {
// The last DNS query part is the protocol field (ignored).
// All prior parts are the prepared query name or ID.
query = strings.Join(queryParts[:n-1], ".")
// Strip leading underscore
query = query[1:]
} else {
// Allow a "." in the query name, just join all the parts.
query = strings.Join(queryParts, ".")
}
err := d.preparedQueryLookup(cfg, datacenter, query, remoteAddr, req, resp, maxRecursionLevel) err := d.preparedQueryLookup(cfg, datacenter, query, remoteAddr, req, resp, maxRecursionLevel)
return ecsNotGlobalError{error: err} return ecsNotGlobalError{error: err}

View File

@ -2743,13 +2743,16 @@ func TestDNS_ServiceLookup_ServiceAddress_SRV(t *testing.T) {
} }
// Register an equivalent prepared query. // Register an equivalent prepared query.
// Specify prepared query name containing "." to test
// since that is technically supported (though atypical).
var id string var id string
preparedQueryName := "query.name.with.dots"
{ {
args := &structs.PreparedQueryRequest{ args := &structs.PreparedQueryRequest{
Datacenter: "dc1", Datacenter: "dc1",
Op: structs.PreparedQueryCreate, Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{ Query: &structs.PreparedQuery{
Name: "test", Name: preparedQueryName,
Service: structs.ServiceQuery{ Service: structs.ServiceQuery{
Service: "db", Service: "db",
}, },
@ -2764,6 +2767,9 @@ func TestDNS_ServiceLookup_ServiceAddress_SRV(t *testing.T) {
questions := []string{ questions := []string{
"db.service.consul.", "db.service.consul.",
id + ".query.consul.", id + ".query.consul.",
preparedQueryName + ".query.consul.",
fmt.Sprintf("_%s._tcp.query.consul.", id),
fmt.Sprintf("_%s._tcp.query.consul.", preparedQueryName),
} }
for _, question := range questions { for _, question := range questions {
m := new(dns.Msg) m := new(dns.Msg)

View File

@ -396,16 +396,24 @@ you can use the following query formats specify namespace but not partition:
### Prepared Query Lookups ### Prepared Query Lookups
The format of a prepared query lookup is: The following formats are valid for prepared query lookups:
```text - Standard lookup
<query or name>.query[.<datacenter>].<domain>
``` ```text
<query name or id>.query[.<datacenter>].<domain>
```
- [RFC 2782](https://tools.ietf.org/html/rfc2782) SRV lookup
```text
_<query name or id>._tcp.query[.<datacenter>].<domain>
```
The `datacenter` is optional, and if not provided, the datacenter of this Consul The `datacenter` is optional, and if not provided, the datacenter of this Consul
agent is assumed. agent is assumed.
The `query or name` is the ID or given name of an existing The `query name or id` is the given name or ID of an existing
[Prepared Query](/api-docs/query). These behave like standard service [Prepared Query](/api-docs/query). These behave like standard service
queries but provide a much richer set of features, such as filtering by multiple queries but provide a much richer set of features, such as filtering by multiple
tags and automatically failing over to look for services in remote datacenters if tags and automatically failing over to look for services in remote datacenters if