/proc/self/cwd/source/extensions/filters/udp/dns_filter/dns_filter.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/extensions/filters/udp/dns_filter/dns_filter.h" |
2 | | |
3 | | #include "envoy/network/listener.h" |
4 | | #include "envoy/type/matcher/v3/string.pb.h" |
5 | | |
6 | | #include "source/common/config/datasource.h" |
7 | | #include "source/common/network/address_impl.h" |
8 | | #include "source/common/network/dns_resolver/dns_factory_util.h" |
9 | | #include "source/common/protobuf/message_validator_impl.h" |
10 | | #include "source/extensions/filters/udp/dns_filter/dns_filter_utils.h" |
11 | | |
12 | | namespace Envoy { |
13 | | namespace Extensions { |
14 | | namespace UdpFilters { |
15 | | namespace DnsFilter { |
16 | | |
17 | | static constexpr std::chrono::milliseconds DEFAULT_RESOLVER_TIMEOUT{500}; |
18 | | static constexpr std::chrono::seconds DEFAULT_RESOLVER_TTL{300}; |
19 | | |
20 | | DnsFilterEnvoyConfig::DnsFilterEnvoyConfig( |
21 | | Server::Configuration::ListenerFactoryContext& context, |
22 | | const envoy::extensions::filters::udp::dns_filter::v3::DnsFilterConfig& config) |
23 | | : root_scope_(context.scope()), cluster_manager_(context.clusterManager()), api_(context.api()), |
24 | | stats_(generateStats(config.stat_prefix(), root_scope_)), |
25 | 0 | resolver_timeout_(DEFAULT_RESOLVER_TIMEOUT), random_(context.api().randomGenerator()) { |
26 | 0 | using envoy::extensions::filters::udp::dns_filter::v3::DnsFilterConfig; |
27 | |
|
28 | 0 | const auto& server_config = config.server_config(); |
29 | |
|
30 | 0 | envoy::data::dns::v3::DnsTable dns_table; |
31 | 0 | bool result = loadServerConfig(server_config, dns_table); |
32 | 0 | ENVOY_LOG(debug, "Loading DNS table from external file: {}", result ? "Success" : "Failure"); |
33 | |
|
34 | 0 | retry_count_ = dns_table.external_retry_count(); |
35 | |
|
36 | 0 | for (const auto& virtual_domain : dns_table.virtual_domains()) { |
37 | 0 | AddressConstPtrVec addrs{}; |
38 | |
|
39 | 0 | const absl::string_view domain_name = virtual_domain.name(); |
40 | 0 | const absl::string_view suffix = Utils::getDomainSuffix(domain_name); |
41 | 0 | ENVOY_LOG(trace, "Loading configuration for domain: {}. Suffix: {}", domain_name, suffix); |
42 | |
|
43 | 0 | if (virtual_domain.endpoint().has_address_list()) { |
44 | 0 | const auto& address_list = virtual_domain.endpoint().address_list().address(); |
45 | 0 | addrs.reserve(address_list.size()); |
46 | | |
47 | | // Shuffle the configured addresses. We store the addresses starting at a random |
48 | | // list index so that we do not always return answers in the same order as the IPs |
49 | | // are configured. |
50 | 0 | size_t i = random_.random(); |
51 | | |
52 | | // Creating the IP address will throw an exception if the address string is malformed |
53 | 0 | for (auto index = 0; index < address_list.size(); index++) { |
54 | 0 | const auto address_iter = std::next(address_list.begin(), (i++ % address_list.size())); |
55 | 0 | auto ipaddr = Network::Utility::parseInternetAddress(*address_iter, 0 /* port */); |
56 | 0 | addrs.push_back(std::move(ipaddr)); |
57 | 0 | } |
58 | |
|
59 | 0 | DnsEndpointConfig endpoint_config{}; |
60 | | |
61 | | // Check whether the trie contains an entry for this domain |
62 | 0 | auto virtual_domains = dns_lookup_trie_.find(suffix); |
63 | 0 | if (virtual_domains != nullptr) { |
64 | | // The suffix already has a node in the trie |
65 | |
|
66 | 0 | auto existing_endpoint_config = virtual_domains->find(domain_name); |
67 | 0 | if (existing_endpoint_config != virtual_domains->end()) { |
68 | | // Update the existing endpoint config with the new addresses |
69 | |
|
70 | 0 | auto& addr_vec = existing_endpoint_config->second.address_list.value(); |
71 | 0 | addr_vec.reserve(addr_vec.size() + addrs.size()); |
72 | 0 | std::move(addrs.begin(), addrs.end(), std::inserter(addr_vec, addr_vec.end())); |
73 | 0 | } else { |
74 | | // Add a new endpoint config for the new domain |
75 | 0 | endpoint_config.address_list = absl::make_optional<AddressConstPtrVec>(std::move(addrs)); |
76 | 0 | virtual_domains->emplace(std::string(domain_name), std::move(endpoint_config)); |
77 | 0 | } |
78 | 0 | } else { |
79 | 0 | endpoint_config.address_list = absl::make_optional<AddressConstPtrVec>(std::move(addrs)); |
80 | 0 | addEndpointToSuffix(suffix, domain_name, endpoint_config); |
81 | 0 | } |
82 | 0 | } |
83 | |
|
84 | 0 | if (virtual_domain.endpoint().has_service_list()) { |
85 | 0 | const auto& dns_service_list = virtual_domain.endpoint().service_list(); |
86 | 0 | for (const auto& dns_service : dns_service_list.services()) { |
87 | | |
88 | | // Each service should be its own domain in the stored config. The filter will see |
89 | | // the full service name in queries on the wire. The protocol string returned will be empty |
90 | | // if a numeric protocol is configured and we cannot resolve its name |
91 | 0 | const std::string proto = Utils::getProtoName(dns_service.protocol()); |
92 | 0 | if (proto.empty()) { |
93 | 0 | continue; |
94 | 0 | } |
95 | 0 | const std::chrono::seconds ttl = std::chrono::seconds(dns_service.ttl().seconds()); |
96 | | |
97 | | // Generate the full name for the DNS service. All input parameters are populated |
98 | | // strings enforced by the message definition |
99 | 0 | const std::string full_service_name = |
100 | 0 | Utils::buildServiceName(dns_service.service_name(), proto, virtual_domain.name()); |
101 | |
|
102 | 0 | DnsSrvRecordPtr service_record_ptr = |
103 | 0 | std::make_unique<DnsSrvRecord>(full_service_name, proto, ttl); |
104 | | |
105 | | // Store service targets. We require at least one target to be present. The target should |
106 | | // be a fully qualified domain name. If the target name is not a fully qualified name, we |
107 | | // will consider this name to be that of a cluster |
108 | 0 | for (const auto& target : dns_service.targets()) { |
109 | 0 | DnsSrvRecord::DnsTargetAttributes attributes{}; |
110 | 0 | attributes.priority = target.priority(); |
111 | 0 | attributes.weight = target.weight(); |
112 | 0 | attributes.port = target.port(); |
113 | |
|
114 | 0 | absl::string_view target_name = target.host_name(); |
115 | 0 | if (target_name.empty()) { |
116 | 0 | target_name = target.cluster_name(); |
117 | 0 | attributes.is_cluster = true; |
118 | 0 | } |
119 | |
|
120 | 0 | ENVOY_LOG(trace, "Storing service {} target {}", full_service_name, target_name); |
121 | 0 | service_record_ptr->addTarget(target_name, attributes); |
122 | 0 | } |
123 | |
|
124 | 0 | DnsEndpointConfig endpoint_config{}; |
125 | 0 | endpoint_config.service_list = |
126 | 0 | absl::make_optional<DnsSrvRecordPtr>(std::move(service_record_ptr)); |
127 | |
|
128 | 0 | auto virtual_domains = dns_lookup_trie_.find(suffix); |
129 | 0 | if (virtual_domains != nullptr) { |
130 | 0 | virtual_domains->emplace(full_service_name, std::move(endpoint_config)); |
131 | 0 | } |
132 | 0 | } |
133 | 0 | } |
134 | | |
135 | | // A DNS name can be redirected to only one cluster. |
136 | 0 | const absl::string_view cluster_name = virtual_domain.endpoint().cluster_name(); |
137 | 0 | if (!cluster_name.empty()) { |
138 | 0 | DnsEndpointConfig endpoint_config{}; |
139 | 0 | endpoint_config.cluster_name = absl::make_optional<std::string>(cluster_name); |
140 | | |
141 | | // See if there's a suffix already configured |
142 | 0 | auto virtual_domains = dns_lookup_trie_.find(suffix); |
143 | 0 | if (virtual_domains == nullptr) { |
144 | 0 | addEndpointToSuffix(suffix, domain_name, endpoint_config); |
145 | 0 | } else { |
146 | | // A domain can be redirected to one cluster. If it appears multiple times, the first |
147 | | // entry is the only one used |
148 | 0 | if (virtual_domains->find(domain_name) == virtual_domains->end()) { |
149 | 0 | virtual_domains->emplace(domain_name, std::move(endpoint_config)); |
150 | 0 | } |
151 | 0 | } |
152 | 0 | } |
153 | |
|
154 | 0 | std::chrono::seconds ttl = virtual_domain.has_answer_ttl() |
155 | 0 | ? std::chrono::seconds(virtual_domain.answer_ttl().seconds()) |
156 | 0 | : DEFAULT_RESOLVER_TTL; |
157 | 0 | domain_ttl_.emplace(virtual_domain.name(), ttl); |
158 | 0 | } |
159 | |
|
160 | 0 | forward_queries_ = config.has_client_config(); |
161 | 0 | if (forward_queries_) { |
162 | 0 | const auto& client_config = config.client_config(); |
163 | 0 | dns_resolver_factory_ = |
164 | 0 | &Network::createDnsResolverFactoryFromProto(client_config, typed_dns_resolver_config_); |
165 | | // Set additional resolving options from configuration |
166 | 0 | resolver_timeout_ = std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT( |
167 | 0 | client_config, resolver_timeout, DEFAULT_RESOLVER_TIMEOUT.count())); |
168 | 0 | max_pending_lookups_ = client_config.max_pending_lookups(); |
169 | 0 | } else { |
170 | | // In case client_config doesn't exist, create default DNS resolver factory and save it. |
171 | 0 | dns_resolver_factory_ = &Network::createDefaultDnsResolverFactory(typed_dns_resolver_config_); |
172 | 0 | max_pending_lookups_ = 0; |
173 | 0 | } |
174 | 0 | } |
175 | | |
176 | | void DnsFilterEnvoyConfig::addEndpointToSuffix(const absl::string_view suffix, |
177 | | const absl::string_view domain_name, |
178 | 0 | DnsEndpointConfig& endpoint_config) { |
179 | |
|
180 | 0 | DnsVirtualDomainConfigSharedPtr virtual_domains = std::make_shared<DnsVirtualDomainConfig>(); |
181 | 0 | virtual_domains->emplace(std::string(domain_name), std::move(endpoint_config)); |
182 | |
|
183 | 0 | auto success = dns_lookup_trie_.add(suffix, std::move(virtual_domains), false); |
184 | 0 | ASSERT(success, "Unable to overwrite existing suffix in dns_filter trie"); |
185 | 0 | } |
186 | | |
187 | | bool DnsFilterEnvoyConfig::loadServerConfig( |
188 | | const envoy::extensions::filters::udp::dns_filter::v3::DnsFilterConfig::ServerContextConfig& |
189 | | config, |
190 | 0 | envoy::data::dns::v3::DnsTable& table) { |
191 | 0 | using envoy::data::dns::v3::DnsTable; |
192 | |
|
193 | 0 | if (config.has_inline_dns_table()) { |
194 | 0 | table = config.inline_dns_table(); |
195 | 0 | return true; |
196 | 0 | } |
197 | | |
198 | 0 | const auto& datasource = config.external_dns_table(); |
199 | 0 | bool data_source_loaded = false; |
200 | 0 | TRY_NEEDS_AUDIT { |
201 | | // Data structure is deduced from the file extension. If the data is not read an exception |
202 | | // is thrown. If no table can be read, the filter will refer all queries to an external |
203 | | // DNS server, if configured, otherwise all queries will be responded to with Name Error. |
204 | 0 | MessageUtil::loadFromFile(datasource.filename(), table, |
205 | 0 | ProtobufMessage::getNullValidationVisitor(), api_); |
206 | 0 | data_source_loaded = true; |
207 | 0 | } |
208 | 0 | END_TRY catch (const ProtobufMessage::UnknownProtoFieldException& e) { |
209 | 0 | ENVOY_LOG(warn, "Invalid field in DNS Filter datasource configuration: {}", e.what()); |
210 | 0 | } |
211 | 0 | catch (const EnvoyException& e) { |
212 | 0 | ENVOY_LOG(warn, "Filesystem DNS Filter config update failure: {}", e.what()); |
213 | 0 | } |
214 | 0 | return data_source_loaded; |
215 | 0 | } |
216 | | |
217 | | DnsFilter::DnsFilter(Network::UdpReadFilterCallbacks& callbacks, |
218 | | const DnsFilterEnvoyConfigSharedPtr& config) |
219 | | : UdpListenerReadFilter(callbacks), config_(config), listener_(callbacks.udpListener()), |
220 | | cluster_manager_(config_->clusterManager()), |
221 | | message_parser_(config->forwardQueries(), listener_.dispatcher().timeSource(), |
222 | | config->retryCount(), config->random(), |
223 | 0 | config_->stats().downstream_rx_query_latency_) { |
224 | | // This callback is executed when the dns resolution completes. At that time of a response by |
225 | | // the resolver, we build an answer record from each IP returned then send a response to the |
226 | | // client |
227 | 0 | resolver_callback_ = [this](DnsQueryContextPtr context, const DnsQueryRecord* query, |
228 | 0 | AddressConstPtrVec& iplist) -> void { |
229 | | // We cannot retry the resolution if ares returns without a response. The ares context |
230 | | // is still dirty and will result in a segfault when it is freed during a subsequent resolve |
231 | | // call from here. We will retry resolutions for pending lookups only |
232 | 0 | if (context->resolution_status_ != Network::DnsResolver::ResolutionStatus::Success && |
233 | 0 | !context->in_callback_ && context->retry_ > 0) { |
234 | 0 | --context->retry_; |
235 | 0 | ENVOY_LOG(debug, "resolving name [{}] via external resolvers [retry {}]", query->name_, |
236 | 0 | context->retry_); |
237 | 0 | resolver_->resolveExternalQuery(std::move(context), query); |
238 | 0 | return; |
239 | 0 | } |
240 | | |
241 | 0 | config_->stats().externally_resolved_queries_.inc(); |
242 | 0 | if (iplist.empty()) { |
243 | 0 | config_->stats().unanswered_queries_.inc(); |
244 | 0 | } |
245 | |
|
246 | 0 | incrementExternalQueryTypeCount(query->type_); |
247 | 0 | for (const auto& ip : iplist) { |
248 | 0 | incrementExternalQueryTypeAnswerCount(query->type_); |
249 | 0 | const std::chrono::seconds ttl = getDomainTTL(query->name_); |
250 | 0 | message_parser_.storeDnsAnswerRecord(context, *query, ttl, std::move(ip)); |
251 | 0 | } |
252 | 0 | sendDnsResponse(std::move(context)); |
253 | 0 | }; |
254 | |
|
255 | 0 | resolver_ = std::make_unique<DnsFilterResolver>( |
256 | 0 | resolver_callback_, config->resolverTimeout(), listener_.dispatcher(), |
257 | 0 | config->maxPendingLookups(), config->typedDnsResolverConfig(), config->dnsResolverFactory(), |
258 | 0 | config->api()); |
259 | 0 | } |
260 | | |
261 | 0 | Network::FilterStatus DnsFilter::onData(Network::UdpRecvData& client_request) { |
262 | 0 | config_->stats().downstream_rx_bytes_.recordValue(client_request.buffer_->length()); |
263 | 0 | config_->stats().downstream_rx_queries_.inc(); |
264 | | |
265 | | // Setup counters for the parser |
266 | 0 | DnsParserCounters parser_counters( |
267 | 0 | config_->stats().query_buffer_underflow_, config_->stats().record_name_overflow_, |
268 | 0 | config_->stats().query_parsing_failure_, config_->stats().queries_with_additional_rrs_, |
269 | 0 | config_->stats().queries_with_ans_or_authority_rrs_); |
270 | | |
271 | | // Parse the query, if it fails return an response to the client |
272 | 0 | DnsQueryContextPtr query_context = |
273 | 0 | message_parser_.createQueryContext(client_request, parser_counters); |
274 | 0 | incrementQueryTypeCount(query_context->queries_); |
275 | 0 | if (!query_context->parse_status_) { |
276 | 0 | config_->stats().downstream_rx_invalid_queries_.inc(); |
277 | 0 | sendDnsResponse(std::move(query_context)); |
278 | 0 | return Network::FilterStatus::StopIteration; |
279 | 0 | } |
280 | | |
281 | | // Resolve the requested name and respond to the client. If the return code is |
282 | | // External, we will respond to the client when the upstream resolver returns |
283 | 0 | if (getResponseForQuery(query_context) == DnsLookupResponseCode::External) { |
284 | 0 | return Network::FilterStatus::StopIteration; |
285 | 0 | } |
286 | | |
287 | | // We have an answer, it might be "No Answer". Send it to the client |
288 | 0 | sendDnsResponse(std::move(query_context)); |
289 | |
|
290 | 0 | return Network::FilterStatus::StopIteration; |
291 | 0 | } |
292 | | |
293 | 0 | void DnsFilter::sendDnsResponse(DnsQueryContextPtr query_context) { |
294 | 0 | Buffer::OwnedImpl response; |
295 | | |
296 | | // Serializes the generated response to the parsed query from the client. If there is a |
297 | | // parsing error or the incoming query is invalid, we will still generate a valid DNS response |
298 | 0 | message_parser_.buildResponseBuffer(query_context, response); |
299 | 0 | config_->stats().downstream_tx_responses_.inc(); |
300 | 0 | config_->stats().downstream_tx_bytes_.recordValue(response.length()); |
301 | 0 | Network::UdpSendData response_data{query_context->local_->ip(), *(query_context->peer_), |
302 | 0 | response}; |
303 | 0 | listener_.send(response_data); |
304 | 0 | } |
305 | | |
306 | 0 | DnsLookupResponseCode DnsFilter::getResponseForQuery(DnsQueryContextPtr& context) { |
307 | | /* It appears to be a rare case where we would have more than one query in a single request. |
308 | | * It is allowed by the protocol but not widely supported: |
309 | | * |
310 | | * See: https://www.ietf.org/rfc/rfc1035.txt |
311 | | * |
312 | | * The question section is used to carry the "question" in most queries, |
313 | | * i.e., the parameters that define what is being asked. The section |
314 | | * contains QDCOUNT (usually 1) entries. |
315 | | */ |
316 | 0 | for (const auto& query : context->queries_) { |
317 | | // Try to resolve the query locally. If forwarding the query externally is disabled we will |
318 | | // always attempt to resolve with the configured domains |
319 | 0 | const bool forward_queries = config_->forwardQueries(); |
320 | 0 | if (isKnownDomain(query->name_) || !forward_queries) { |
321 | | // Determine whether the name is a cluster. Move on to the next query if successful |
322 | 0 | if (resolveViaClusters(context, *query)) { |
323 | 0 | continue; |
324 | 0 | } |
325 | | |
326 | | // Determine whether we an answer this query with the static configuration |
327 | 0 | if (resolveViaConfiguredHosts(context, *query)) { |
328 | 0 | continue; |
329 | 0 | } |
330 | 0 | } |
331 | | |
332 | | // Forwarding queries is enabled if the configuration contains a client configuration |
333 | | // for the dns_filter. |
334 | 0 | if (forward_queries) { |
335 | 0 | ENVOY_LOG(debug, "resolving name [{}] via external resolvers", query->name_); |
336 | 0 | resolver_->resolveExternalQuery(std::move(context), query.get()); |
337 | |
|
338 | 0 | return DnsLookupResponseCode::External; |
339 | 0 | } |
340 | 0 | } |
341 | | |
342 | 0 | if (context->answers_.empty()) { |
343 | 0 | config_->stats().unanswered_queries_.inc(); |
344 | 0 | return DnsLookupResponseCode::Failure; |
345 | 0 | } |
346 | 0 | return DnsLookupResponseCode::Success; |
347 | 0 | } |
348 | | |
349 | | bool DnsFilter::resolveViaConfiguredHosts(DnsQueryContextPtr& context, |
350 | 0 | const DnsQueryRecord& query) { |
351 | 0 | switch (query.type_) { |
352 | 0 | case DNS_RECORD_TYPE_A: |
353 | 0 | case DNS_RECORD_TYPE_AAAA: |
354 | 0 | return resolveConfiguredDomain(context, query); |
355 | 0 | case DNS_RECORD_TYPE_SRV: |
356 | 0 | return resolveConfiguredService(context, query); |
357 | 0 | default: |
358 | 0 | return false; |
359 | 0 | } |
360 | 0 | } |
361 | | |
362 | 0 | std::chrono::seconds DnsFilter::getDomainTTL(const absl::string_view domain) { |
363 | 0 | const auto& domain_ttl_config = config_->domainTtl(); |
364 | 0 | const auto& iter = domain_ttl_config.find(domain); |
365 | |
|
366 | 0 | if (iter == domain_ttl_config.end()) { |
367 | 0 | return DEFAULT_RESOLVER_TTL; |
368 | 0 | } |
369 | 0 | return iter->second; |
370 | 0 | } |
371 | | |
372 | 0 | bool DnsFilter::isKnownDomain(const absl::string_view domain_name) { |
373 | 0 | const absl::string_view suffix = Utils::getDomainSuffix(domain_name); |
374 | 0 | auto config = config_->getDnsTrie().find(suffix); |
375 | |
|
376 | 0 | if (config != nullptr) { |
377 | 0 | config_->stats().known_domain_queries_.inc(); |
378 | 0 | return true; |
379 | 0 | } |
380 | | |
381 | 0 | return false; |
382 | 0 | } |
383 | | |
384 | 0 | const DnsEndpointConfig* DnsFilter::getEndpointConfigForDomain(const absl::string_view domain) { |
385 | 0 | const absl::string_view suffix = Utils::getDomainSuffix(domain); |
386 | 0 | const auto virtual_domains = config_->getDnsTrie().find(suffix); |
387 | |
|
388 | 0 | if (virtual_domains == nullptr) { |
389 | 0 | ENVOY_LOG(debug, "No domain configuration exists for [{}]", domain); |
390 | 0 | return nullptr; |
391 | 0 | } |
392 | | |
393 | 0 | const auto iter = virtual_domains->find(domain); |
394 | 0 | if (iter == virtual_domains->end()) { |
395 | 0 | ENVOY_LOG(debug, "No endpoint configuration exists for [{}]", domain); |
396 | 0 | return nullptr; |
397 | 0 | } |
398 | 0 | return &(iter->second); |
399 | 0 | } |
400 | | |
401 | 0 | const DnsSrvRecord* DnsFilter::getServiceConfigForDomain(const absl::string_view domain) { |
402 | 0 | const DnsEndpointConfig* endpoint_config = getEndpointConfigForDomain(domain); |
403 | 0 | if (endpoint_config != nullptr && endpoint_config->service_list.has_value()) { |
404 | 0 | return endpoint_config->service_list.value().get(); |
405 | 0 | } |
406 | 0 | return nullptr; |
407 | 0 | } |
408 | | |
409 | 0 | const AddressConstPtrVec* DnsFilter::getAddressListForDomain(const absl::string_view domain) { |
410 | 0 | const DnsEndpointConfig* endpoint_config = getEndpointConfigForDomain(domain); |
411 | 0 | if (endpoint_config != nullptr && endpoint_config->address_list.has_value()) { |
412 | 0 | return &(endpoint_config->address_list.value()); |
413 | 0 | } |
414 | 0 | return nullptr; |
415 | 0 | } |
416 | | |
417 | 0 | const absl::string_view DnsFilter::getClusterNameForDomain(const absl::string_view domain) { |
418 | 0 | const DnsEndpointConfig* endpoint_config = getEndpointConfigForDomain(domain); |
419 | 0 | if (endpoint_config != nullptr && endpoint_config->cluster_name.has_value()) { |
420 | 0 | return endpoint_config->cluster_name.value(); |
421 | 0 | } |
422 | 0 | return {}; |
423 | 0 | } |
424 | | |
425 | 0 | bool DnsFilter::resolveClusterService(DnsQueryContextPtr& context, const DnsQueryRecord& query) { |
426 | 0 | size_t cluster_endpoints = 0; |
427 | | |
428 | | // Get the service_list config for the domain |
429 | 0 | const auto* service_config = getServiceConfigForDomain(query.name_); |
430 | 0 | if (service_config != nullptr) { |
431 | | // We can redirect to more than one cluster, but only one is supported |
432 | 0 | const auto& cluster_target = service_config->targets_.begin(); |
433 | 0 | const auto& target_name = cluster_target->first; |
434 | 0 | const auto& attributes = cluster_target->second; |
435 | |
|
436 | 0 | if (!attributes.is_cluster) { |
437 | 0 | ENVOY_LOG(trace, "Service target [{}] is not a cluster", target_name); |
438 | 0 | return false; |
439 | 0 | } |
440 | | |
441 | | // Determine if there is a cluster |
442 | 0 | Upstream::ThreadLocalCluster* cluster = cluster_manager_.getThreadLocalCluster(target_name); |
443 | 0 | if (cluster == nullptr) { |
444 | 0 | ENVOY_LOG(trace, "No cluster found for service target: {}", target_name); |
445 | 0 | return false; |
446 | 0 | } |
447 | | |
448 | | // Add a service record for each cluster endpoint using the cluster name |
449 | 0 | const std::chrono::seconds ttl = getDomainTTL(target_name); |
450 | 0 | for (const auto& hostsets : cluster->prioritySet().hostSetsPerPriority()) { |
451 | 0 | for (const auto& host : hostsets->hosts()) { |
452 | | |
453 | | // If the target port is zero, use the port from the cluster host. |
454 | | // If the cluster host port is zero also, then this is the value that will |
455 | | // appear in the service record. Zero is a permitted value in the record |
456 | 0 | DnsSrvRecord::DnsTargetAttributes new_attributes = attributes; |
457 | 0 | if (!new_attributes.port) { |
458 | 0 | new_attributes.port = host->address()->ip()->port(); |
459 | 0 | } |
460 | | |
461 | | // Create the service record element and increment the SRV record answer count |
462 | 0 | auto config = std::make_unique<DnsSrvRecord>(service_config->name_, service_config->proto_, |
463 | 0 | service_config->ttl_); |
464 | |
|
465 | 0 | config->addTarget(target_name, new_attributes); |
466 | 0 | message_parser_.storeDnsSrvAnswerRecord(context, query, std::move(config)); |
467 | 0 | incrementClusterQueryTypeAnswerCount(query.type_); |
468 | | |
469 | | // Return the address for all discovered endpoints |
470 | 0 | ENVOY_LOG(debug, "using host address {} for cluster [{}]", |
471 | 0 | host->address()->ip()->addressAsString(), target_name); |
472 | | |
473 | | // We have to determine the address type here so that we increment the correct counter |
474 | 0 | const auto type = Utils::getAddressRecordType(host->address()); |
475 | 0 | if (type.has_value() && |
476 | 0 | message_parser_.storeDnsAdditionalRecord(context, target_name, type.value(), |
477 | 0 | query.class_, ttl, host->address())) { |
478 | 0 | ++cluster_endpoints; |
479 | 0 | incrementClusterQueryTypeAnswerCount(type.value()); |
480 | 0 | } |
481 | 0 | } |
482 | 0 | } |
483 | 0 | } |
484 | 0 | return (cluster_endpoints != 0); |
485 | 0 | } |
486 | | |
487 | 0 | bool DnsFilter::resolveClusterHost(DnsQueryContextPtr& context, const DnsQueryRecord& query) { |
488 | | // Determine if the domain name is being redirected to a cluster |
489 | 0 | const auto cluster_name = getClusterNameForDomain(query.name_); |
490 | 0 | absl::string_view lookup_name; |
491 | 0 | if (!cluster_name.empty()) { |
492 | 0 | lookup_name = cluster_name; |
493 | 0 | } else { |
494 | 0 | lookup_name = query.name_; |
495 | 0 | } |
496 | | |
497 | | // Return an address for all discovered endpoints. The address and query type must match |
498 | | // for the host to be included in the response |
499 | 0 | size_t cluster_endpoints = 0; |
500 | 0 | Upstream::ThreadLocalCluster* cluster = cluster_manager_.getThreadLocalCluster(lookup_name); |
501 | 0 | if (cluster != nullptr) { |
502 | | // TODO(abaptiste): consider using host weights when returning answer addresses |
503 | 0 | const std::chrono::seconds ttl = getDomainTTL(lookup_name); |
504 | |
|
505 | 0 | for (const auto& hostsets : cluster->prioritySet().hostSetsPerPriority()) { |
506 | 0 | for (const auto& host : hostsets->hosts()) { |
507 | | // Return the address for all discovered endpoints |
508 | 0 | ENVOY_LOG(debug, "using cluster host address {} for domain [{}]", |
509 | 0 | host->address()->ip()->addressAsString(), lookup_name); |
510 | 0 | if (message_parser_.storeDnsAnswerRecord(context, query, ttl, host->address())) { |
511 | 0 | incrementClusterQueryTypeAnswerCount(query.type_); |
512 | 0 | ++cluster_endpoints; |
513 | 0 | } |
514 | 0 | } |
515 | 0 | } |
516 | 0 | } |
517 | 0 | return (cluster_endpoints != 0); |
518 | 0 | } |
519 | | |
520 | 0 | bool DnsFilter::resolveViaClusters(DnsQueryContextPtr& context, const DnsQueryRecord& query) { |
521 | 0 | switch (query.type_) { |
522 | 0 | case DNS_RECORD_TYPE_SRV: |
523 | 0 | return resolveClusterService(context, query); |
524 | 0 | case DNS_RECORD_TYPE_A: |
525 | 0 | case DNS_RECORD_TYPE_AAAA: |
526 | 0 | return resolveClusterHost(context, query); |
527 | 0 | default: |
528 | | // unsupported query type |
529 | 0 | return false; |
530 | 0 | } |
531 | 0 | } |
532 | | |
533 | 0 | bool DnsFilter::resolveConfiguredDomain(DnsQueryContextPtr& context, const DnsQueryRecord& query) { |
534 | 0 | const auto* configured_address_list = getAddressListForDomain(query.name_); |
535 | 0 | uint64_t hosts_found = 0; |
536 | 0 | if (configured_address_list != nullptr) { |
537 | | // Build an answer record from each configured IP address |
538 | 0 | for (const auto& configured_address : *configured_address_list) { |
539 | 0 | ASSERT(configured_address != nullptr); |
540 | 0 | ENVOY_LOG(trace, "using local address {} for domain [{}]", |
541 | 0 | configured_address->ip()->addressAsString(), query.name_); |
542 | 0 | ++hosts_found; |
543 | 0 | const std::chrono::seconds ttl = getDomainTTL(query.name_); |
544 | 0 | if (message_parser_.storeDnsAnswerRecord(context, query, ttl, configured_address)) { |
545 | 0 | incrementLocalQueryTypeAnswerCount(query.type_); |
546 | 0 | } |
547 | 0 | } |
548 | 0 | } |
549 | 0 | return (hosts_found != 0); |
550 | 0 | } |
551 | | |
552 | 0 | bool DnsFilter::resolveConfiguredService(DnsQueryContextPtr& context, const DnsQueryRecord& query) { |
553 | 0 | const auto* service_config = getServiceConfigForDomain(query.name_); |
554 | |
|
555 | 0 | size_t targets_discovered = 0; |
556 | 0 | if (service_config != nullptr) { |
557 | | // for each service target address, we must resolve the target's IP. The target record does not |
558 | | // specify the address type, so we must deduce it when building the record. It is possible that |
559 | | // the configured target's IP addresses are a mix of A and AAAA records. |
560 | 0 | for (const auto& [target_name, attributes] : service_config->targets_) { |
561 | 0 | const auto* configured_address_list = getAddressListForDomain(target_name); |
562 | |
|
563 | 0 | if (configured_address_list != nullptr) { |
564 | | // Build an SRV answer record for the service. We need a new SRV record for each target. |
565 | | // Although the same class is used, the target storage is different than the way the service |
566 | | // config is modeled. We store one SrvRecord per target so that we can enforce the response |
567 | | // size limit when serializing the answers to the client |
568 | 0 | ENVOY_LOG(trace, "Adding srv record for target [{}]", target_name); |
569 | |
|
570 | 0 | incrementLocalQueryTypeAnswerCount(query.type_); |
571 | 0 | auto config = std::make_unique<DnsSrvRecord>(service_config->name_, service_config->proto_, |
572 | 0 | service_config->ttl_); |
573 | 0 | config->addTarget(target_name, attributes); |
574 | 0 | message_parser_.storeDnsSrvAnswerRecord(context, query, std::move(config)); |
575 | |
|
576 | 0 | for (const auto& configured_address : *configured_address_list) { |
577 | 0 | ASSERT(configured_address != nullptr); |
578 | | |
579 | | // Since there is no type, only a name, we must determine the record type from its address |
580 | 0 | ENVOY_LOG(trace, "using address {} for target [{}] in SRV record", |
581 | 0 | configured_address->ip()->addressAsString(), target_name); |
582 | 0 | const std::chrono::seconds ttl = getDomainTTL(target_name); |
583 | |
|
584 | 0 | const auto type = Utils::getAddressRecordType(configured_address); |
585 | 0 | if (type.has_value()) { |
586 | 0 | incrementLocalQueryTypeAnswerCount(type.value()); |
587 | 0 | message_parser_.storeDnsAdditionalRecord(context, target_name, type.value(), |
588 | 0 | query.class_, ttl, configured_address); |
589 | 0 | ++targets_discovered; |
590 | 0 | } |
591 | 0 | } |
592 | 0 | } |
593 | 0 | } |
594 | 0 | } |
595 | 0 | return (targets_discovered != 0); |
596 | 0 | } |
597 | | |
598 | 0 | Network::FilterStatus DnsFilter::onReceiveError(Api::IoError::IoErrorCode error_code) { |
599 | 0 | config_->stats().downstream_rx_errors_.inc(); |
600 | 0 | UNREFERENCED_PARAMETER(error_code); |
601 | |
|
602 | 0 | return Network::FilterStatus::StopIteration; |
603 | 0 | } |
604 | | |
605 | | } // namespace DnsFilter |
606 | | } // namespace UdpFilters |
607 | | } // namespace Extensions |
608 | | } // namespace Envoy |