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 f0837a2cd0
commit 3e7e8ae9c5
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)
case "query":
n := len(queryParts)
datacenter := d.agent.config.Datacenter
// ensure we have a query name
if len(queryParts) < 1 {
if n < 1 {
return invalid()
}
@ -919,8 +920,23 @@ func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, maxRecursi
return invalid()
}
// Allow a "." in the query name, just join all the parts.
query := strings.Join(queryParts, ".")
query := ""
// 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)
return ecsNotGlobalError{error: err}

View File

@ -2743,13 +2743,16 @@ func TestDNS_ServiceLookup_ServiceAddress_SRV(t *testing.T) {
}
// Register an equivalent prepared query.
// Specify prepared query name containing "." to test
// since that is technically supported (though atypical).
var id string
preparedQueryName := "query.name.with.dots"
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "test",
Name: preparedQueryName,
Service: structs.ServiceQuery{
Service: "db",
},
@ -2764,6 +2767,9 @@ func TestDNS_ServiceLookup_ServiceAddress_SRV(t *testing.T) {
questions := []string{
"db.service.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 {
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
The format of a prepared query lookup is:
The following formats are valid for prepared query lookups:
```text
<query or name>.query[.<datacenter>].<domain>
```
- Standard lookup
```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
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
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