mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
6.6 KiB
221 lines
6.6 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package dns |
|
|
|
import ( |
|
"net" |
|
"strings" |
|
|
|
"github.com/miekg/dns" |
|
|
|
"github.com/hashicorp/consul/agent/discovery" |
|
) |
|
|
|
// buildQueryFromDNSMessage returns a discovery.Query from a DNS message. |
|
func buildQueryFromDNSMessage(req *dns.Msg, reqCtx Context, domain, altDomain string, |
|
remoteAddress net.Addr) (*discovery.Query, error) { |
|
queryType, queryParts, querySuffixes := getQueryTypePartsAndSuffixesFromDNSMessage(req, domain, altDomain) |
|
|
|
queryTenancy, err := getQueryTenancy(reqCtx, queryType, querySuffixes) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
name, tag := getQueryNameAndTagFromParts(queryType, queryParts) |
|
|
|
portName := parsePort(queryParts) |
|
|
|
switch { |
|
case queryType == discovery.QueryTypeWorkload && req.Question[0].Qtype == dns.TypeSRV: |
|
// Currently we do not support SRV records for workloads |
|
return nil, errNotImplemented |
|
case queryType == discovery.QueryTypeInvalid, name == "": |
|
return nil, errInvalidQuestion |
|
} |
|
|
|
return &discovery.Query{ |
|
QueryType: queryType, |
|
QueryPayload: discovery.QueryPayload{ |
|
Name: name, |
|
Tenancy: queryTenancy, |
|
Tag: tag, |
|
PortName: portName, |
|
SourceIP: getSourceIP(req, queryType, remoteAddress), |
|
}, |
|
}, nil |
|
} |
|
|
|
// getQueryNameAndTagFromParts returns the query name and tag from the query parts that are taken from the original dns question. |
|
func getQueryNameAndTagFromParts(queryType discovery.QueryType, queryParts []string) (string, string) { |
|
n := len(queryParts) |
|
if n == 0 { |
|
return "", "" |
|
} |
|
|
|
switch queryType { |
|
case discovery.QueryTypeService: |
|
// Support RFC 2782 style syntax |
|
if n == 2 && strings.HasPrefix(queryParts[1], "_") && strings.HasPrefix(queryParts[0], "_") { |
|
// Grab the tag since we make nuke it if it's tcp |
|
tag := queryParts[1][1:] |
|
|
|
// Treat _name._tcp.service.consul as a default, no need to filter on that tag |
|
if tag == "tcp" { |
|
tag = "" |
|
} |
|
|
|
name := queryParts[0][1:] |
|
// _name._tag.service.consul |
|
return name, tag |
|
} |
|
return queryParts[n-1], "" |
|
} |
|
return queryParts[n-1], "" |
|
} |
|
|
|
// getQueryTenancy returns a discovery.QueryTenancy from a DNS message. |
|
func getQueryTenancy(reqCtx Context, queryType discovery.QueryType, querySuffixes []string) (discovery.QueryTenancy, error) { |
|
labels, ok := parseLabels(querySuffixes) |
|
if !ok { |
|
return discovery.QueryTenancy{}, errNameNotFound |
|
} |
|
|
|
// If we don't have an explicit partition in the request, try the first fallback |
|
// which was supplied in the request context. The agent's partition will be used as the last fallback |
|
// later in the query processor. |
|
if labels.Partition == "" { |
|
labels.Partition = reqCtx.DefaultPartition |
|
} |
|
|
|
// If we have a sameness group, we can return early without further data massage. |
|
if labels.SamenessGroup != "" { |
|
return discovery.QueryTenancy{ |
|
Namespace: labels.Namespace, |
|
Partition: labels.Partition, |
|
SamenessGroup: labels.SamenessGroup, |
|
Datacenter: reqCtx.DefaultDatacenter, |
|
}, nil |
|
} |
|
|
|
if queryType == discovery.QueryTypeVirtual { |
|
if labels.Peer == "" { |
|
// If the peer name was not explicitly defined, fall back to the ambiguously-parsed version. |
|
labels.Peer = labels.PeerOrDatacenter |
|
} |
|
} |
|
|
|
return discovery.QueryTenancy{ |
|
Namespace: labels.Namespace, |
|
Partition: labels.Partition, |
|
Peer: labels.Peer, |
|
Datacenter: getEffectiveDatacenter(labels, reqCtx.DefaultDatacenter), |
|
}, nil |
|
} |
|
|
|
// getEffectiveDatacenter returns the effective datacenter from the parsed labels. |
|
func getEffectiveDatacenter(labels *parsedLabels, defaultDC string) string { |
|
switch { |
|
case labels.Datacenter != "": |
|
return labels.Datacenter |
|
case labels.PeerOrDatacenter != "" && labels.Peer != labels.PeerOrDatacenter: |
|
return labels.PeerOrDatacenter |
|
} |
|
return defaultDC |
|
} |
|
|
|
// getQueryTypePartsAndSuffixesFromDNSMessage returns the query type, the parts, and suffixes of the query name. |
|
func getQueryTypePartsAndSuffixesFromDNSMessage(req *dns.Msg, domain, altDomain string) (queryType discovery.QueryType, parts []string, suffixes []string) { |
|
// Get the QName without the domain suffix |
|
// TODO (v2-dns): we will also need to handle the "failover" and "no-failover" suffixes here. |
|
// They come AFTER the domain. See `stripSuffix` in router.go |
|
qName := trimDomainFromQuestionName(req.Question[0].Name, domain, altDomain) |
|
|
|
// Split into the label parts |
|
labels := dns.SplitDomainName(qName) |
|
|
|
done := false |
|
for i := len(labels) - 1; i >= 0 && !done; i-- { |
|
queryType = getQueryTypeFromLabels(labels[i]) |
|
switch queryType { |
|
case discovery.QueryTypeService, discovery.QueryTypeWorkload, |
|
discovery.QueryTypeConnect, discovery.QueryTypeVirtual, discovery.QueryTypeIngress, |
|
discovery.QueryTypeNode, discovery.QueryTypePreparedQuery: |
|
parts = labels[:i] |
|
suffixes = labels[i+1:] |
|
done = true |
|
case discovery.QueryTypeInvalid: |
|
fallthrough |
|
default: |
|
// If this is a SRV query the "service" label is optional, we add it back to use the |
|
// existing code-path. |
|
if req.Question[0].Qtype == dns.TypeSRV && strings.HasPrefix(labels[i], "_") { |
|
queryType = discovery.QueryTypeService |
|
parts = labels[:i+1] |
|
suffixes = labels[i+1:] |
|
done = true |
|
} |
|
} |
|
} |
|
|
|
return queryType, parts, suffixes |
|
} |
|
|
|
// trimDomainFromQuestionName returns the question name without the domain suffix. |
|
func trimDomainFromQuestionName(questionName, domain, altDomain string) string { |
|
qName := dns.CanonicalName(questionName) |
|
longer := domain |
|
shorter := altDomain |
|
|
|
if len(shorter) > len(longer) { |
|
longer, shorter = shorter, longer |
|
} |
|
|
|
if strings.HasSuffix(qName, "."+strings.TrimLeft(longer, ".")) { |
|
return strings.TrimSuffix(qName, longer) |
|
} |
|
return strings.TrimSuffix(qName, shorter) |
|
} |
|
|
|
// getQueryTypeFromLabels returns the query type from the labels. |
|
func getQueryTypeFromLabels(label string) discovery.QueryType { |
|
switch label { |
|
case "service": |
|
return discovery.QueryTypeService |
|
case "connect": |
|
return discovery.QueryTypeConnect |
|
case "virtual": |
|
return discovery.QueryTypeVirtual |
|
case "ingress": |
|
return discovery.QueryTypeIngress |
|
case "node": |
|
return discovery.QueryTypeNode |
|
case "query": |
|
return discovery.QueryTypePreparedQuery |
|
case "workload": |
|
return discovery.QueryTypeWorkload |
|
default: |
|
return discovery.QueryTypeInvalid |
|
} |
|
} |
|
|
|
// getSourceIP returns the source IP from the dns request. |
|
func getSourceIP(req *dns.Msg, queryType discovery.QueryType, remoteAddr net.Addr) (sourceIP net.IP) { |
|
if queryType == discovery.QueryTypePreparedQuery { |
|
subnet := ednsSubnetForRequest(req) |
|
|
|
if subnet != nil { |
|
sourceIP = subnet.Address |
|
} else { |
|
switch v := remoteAddr.(type) { |
|
case *net.UDPAddr: |
|
sourceIP = v.IP |
|
case *net.TCPAddr: |
|
sourceIP = v.IP |
|
case *net.IPAddr: |
|
sourceIP = v.IP |
|
} |
|
} |
|
} |
|
return sourceIP |
|
}
|
|
|