1
#include "source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.h"
2

            
3
namespace Envoy {
4
namespace Network {
5
namespace {
6

            
7
// RAII wrapper to free the `addrinfo`.
8
class AddrInfoWrapper : NonCopyable {
9
public:
10
156
  AddrInfoWrapper(addrinfo* info) : info_(info) {}
11
156
  ~AddrInfoWrapper() {
12
156
    if (info_ != nullptr) {
13
119
      Api::OsSysCallsSingleton::get().freeaddrinfo(info_);
14
119
    }
15
156
  }
16
121
  const addrinfo* get() { return info_; }
17

            
18
private:
19
  addrinfo* info_;
20
};
21

            
22
} // namespace
23

            
24
constexpr uint32_t kThreadCap = 10;
25

            
26
GetAddrInfoDnsResolver::GetAddrInfoDnsResolver(
27
    const envoy::extensions::network::dns_resolver::getaddrinfo::v3::GetAddrInfoDnsResolverConfig&
28
        config,
29
    Event::Dispatcher& dispatcher, Api::Api& api)
30
200
    : config_(config), dispatcher_(dispatcher), api_(api) {
31
200
  uint32_t num_threads =
32
200
      config_.has_num_resolver_threads() ? config_.num_resolver_threads().value() : 1;
33
200
  num_threads = std::min(num_threads, kThreadCap);
34
200
  ENVOY_LOG(trace, "Starting getaddrinfo resolver with {} threads", num_threads);
35
200
  resolver_threads_.reserve(num_threads);
36
452
  for (uint32_t i = 0; i < num_threads; i++) {
37
252
    resolver_threads_.emplace_back(
38
252
        api_.threadFactory().createThread([this] { resolveThreadRoutine(); }));
39
252
  }
40
200
}
41

            
42
200
GetAddrInfoDnsResolver::~GetAddrInfoDnsResolver() {
43
200
  {
44
200
    absl::MutexLock guard(mutex_);
45
200
    shutting_down_ = true;
46
200
    pending_queries_.clear();
47
200
  }
48

            
49
252
  for (auto& thread : resolver_threads_) {
50
252
    thread->join();
51
252
  }
52
200
  ENVOY_LOG(trace, "All getaddrinfo resolver threads joined");
53
200
}
54

            
55
ActiveDnsQuery* GetAddrInfoDnsResolver::resolve(const std::string& dns_name,
56
                                                DnsLookupFamily dns_lookup_family,
57
148
                                                ResolveCb callback) {
58
148
  ENVOY_LOG(trace, "adding new query [{}] to pending queries", dns_name);
59
148
  auto new_query = std::make_unique<PendingQuery>(dns_name, dns_lookup_family, std::move(callback));
60
148
  new_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::NotStarted));
61
148
  ActiveDnsQuery* active_query = new_query.get();
62
148
  {
63
148
    absl::MutexLock guard(mutex_);
64
148
    if (config_.has_num_retries()) {
65
      // + 1 to include the initial query.
66
8
      pending_queries_.push_back({std::move(new_query), config_.num_retries().value() + 1});
67
142
    } else {
68
140
      pending_queries_.push_back({std::move(new_query), absl::nullopt});
69
140
    }
70
148
  }
71
148
  return active_query;
72
148
}
73

            
74
std::pair<DnsResolver::ResolutionStatus, std::list<DnsResponse>>
75
GetAddrInfoDnsResolver::processResponse(const PendingQuery& query,
76
121
                                        const addrinfo* addrinfo_result) {
77
121
  std::list<DnsResponse> v4_results;
78
121
  std::list<DnsResponse> v6_results;
79
  // We could potentially avoid adding v4 or v6 addresses if we know they will never be used.
80
  // Right now the final filtering is done below and this code is kept simple.
81
257
  for (auto ai = addrinfo_result; ai != nullptr; ai = ai->ai_next) {
82
136
    if (ai->ai_family == AF_INET) {
83
117
      sockaddr_in address;
84
117
      memset(&address, 0, sizeof(address));
85
117
      address.sin_family = AF_INET;
86
117
      address.sin_port = 0;
87
117
      address.sin_addr = reinterpret_cast<sockaddr_in*>(ai->ai_addr)->sin_addr;
88

            
89
117
      v4_results.emplace_back(
90
117
          DnsResponse(std::make_shared<const Address::Ipv4Instance>(&address), DEFAULT_TTL));
91
117
    } else if (ai->ai_family == AF_INET6) {
92
19
      sockaddr_in6 address;
93
19
      memset(&address, 0, sizeof(address));
94
19
      address.sin6_family = AF_INET6;
95
19
      address.sin6_port = 0;
96
19
      address.sin6_addr = reinterpret_cast<sockaddr_in6*>(ai->ai_addr)->sin6_addr;
97
19
      v6_results.emplace_back(
98
19
          DnsResponse(std::make_shared<const Address::Ipv6Instance>(address), DEFAULT_TTL));
99
19
    }
100
136
  }
101

            
102
121
  std::list<DnsResponse> final_results;
103
121
  switch (query.dns_lookup_family_) {
104
29
  case DnsLookupFamily::All:
105
29
    final_results = std::move(v4_results);
106
29
    final_results.splice(final_results.begin(), v6_results);
107
29
    break;
108
81
  case DnsLookupFamily::V4Only:
109
81
    final_results = std::move(v4_results);
110
81
    break;
111
2
  case DnsLookupFamily::V6Only:
112
2
    final_results = std::move(v6_results);
113
2
    break;
114
2
  case DnsLookupFamily::V4Preferred:
115
2
    if (!v4_results.empty()) {
116
1
      final_results = std::move(v4_results);
117
1
    } else {
118
1
      final_results = std::move(v6_results);
119
1
    }
120
2
    break;
121
7
  case DnsLookupFamily::Auto:
122
    // This is effectively V6Preferred.
123
7
    if (!v6_results.empty()) {
124
1
      final_results = std::move(v6_results);
125
6
    } else {
126
6
      final_results = std::move(v4_results);
127
6
    }
128
7
    break;
129
121
  }
130

            
131
121
  ENVOY_LOG(trace, "getaddrinfo resolution complete for host '{}': {}", query.dns_name_,
132
121
            accumulateToString<Network::DnsResponse>(final_results, [](const auto& dns_response) {
133
121
              return dns_response.addrInfo().address_->asString();
134
121
            }));
135

            
136
121
  return std::make_pair(ResolutionStatus::Completed, final_results);
137
121
}
138

            
139
// Background thread which wakes up and does resolutions.
140
252
void GetAddrInfoDnsResolver::resolveThreadRoutine() {
141
252
  ENVOY_LOG(trace, "starting getaddrinfo resolver thread");
142

            
143
409
  while (true) {
144
409
    std::unique_ptr<PendingQuery> next_query;
145
409
    absl::optional<uint32_t> num_retries;
146
409
    {
147
409
      absl::MutexLock guard(mutex_);
148
1106
      auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
149
1106
        return shutting_down_ || !pending_queries_.empty();
150
1106
      };
151
409
      mutex_.Await(absl::Condition(&condition));
152
409
      if (shutting_down_) {
153
252
        break;
154
252
      }
155

            
156
157
      PendingQueryInfo pending_query_info = std::move(pending_queries_.front());
157
157
      next_query = std::move(pending_query_info.pending_query_);
158
157
      num_retries = pending_query_info.num_retries_;
159
157
      pending_queries_.pop_front();
160
157
      if (next_query->isCancelled()) {
161
1
        continue;
162
1
      }
163
157
    }
164

            
165
156
    ENVOY_LOG(trace, "Thread ({}) popped pending query [{}]",
166
156
              api_.threadFactory().currentThreadId().getId(), next_query->dns_name_);
167

            
168
    // For mock testing make sure the getaddrinfo() response is freed prior to the post.
169
156
    std::pair<ResolutionStatus, std::list<DnsResponse>> response;
170
156
    std::string details;
171
156
    {
172
156
      next_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Starting));
173
156
      addrinfo hints;
174
156
      memset(&hints, 0, sizeof(hints));
175
156
      if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.getaddrinfo_no_ai_flags")) {
176
156
        hints.ai_flags = AI_ADDRCONFIG;
177
156
      }
178
156
      hints.ai_family = AF_UNSPEC;
179
      // If we don't specify a socket type, every address will appear twice, once
180
      // for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
181
      // anyway, just pick one.
182
156
      hints.ai_socktype = SOCK_STREAM;
183
156
      addrinfo* addrinfo_result_do_not_use = nullptr;
184
156
      auto rc = Api::OsSysCallsSingleton::get().getaddrinfo(next_query->dns_name_.c_str(), nullptr,
185
156
                                                            &hints, &addrinfo_result_do_not_use);
186
156
      auto addrinfo_wrapper = AddrInfoWrapper(addrinfo_result_do_not_use);
187
156
      if (rc.return_value_ == 0) {
188
121
        next_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Success));
189
121
        response = processResponse(*next_query, addrinfo_wrapper.get());
190
126
      } else if (rc.return_value_ == EAI_AGAIN) {
191
9
        if (!num_retries.has_value()) {
192
2
          ENVOY_LOG(trace, "retrying query [{}]", next_query->dns_name_);
193
2
          next_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Retrying));
194
2
          {
195
2
            absl::MutexLock guard(mutex_);
196
2
            pending_queries_.push_back({std::move(next_query), absl::nullopt});
197
2
          }
198
2
          continue;
199
2
        }
200
7
        (*num_retries)--;
201
7
        if (*num_retries > 0) {
202
6
          ENVOY_LOG(trace, "retrying query [{}], num_retries: {}", next_query->dns_name_,
203
6
                    *num_retries);
204
6
          next_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Retrying));
205
6
          {
206
6
            absl::MutexLock guard(mutex_);
207
6
            pending_queries_.push_back({std::move(next_query), *num_retries});
208
6
          }
209
6
          continue;
210
6
        }
211
1
        ENVOY_LOG(debug, "not retrying query [{}] because num_retries: {}", next_query->dns_name_,
212
1
                  *num_retries);
213
1
        next_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::DoneRetrying));
214
1
        response = std::make_pair(ResolutionStatus::Failure, std::list<DnsResponse>());
215
26
      } else if (rc.return_value_ == EAI_NONAME || rc.return_value_ == EAI_NODATA) {
216
        // Treat NONAME and NODATA as DNS records with no results and a failure.
217
        // Experiments on Android have shown that treating NONAME and NODATA as success leads to
218
        // many more net.dns and connection-level failures.
219
        // NOTE: this differs from how the c-ares resolver treats NONAME and NODATA:
220
        // https://github.com/envoyproxy/envoy/blob/099d85925b32ce8bf06e241ee433375a0a3d751b/source/extensions/network/dns_resolver/cares/dns_impl.h#L109-L111.
221
19
        ENVOY_LOG(debug, "getaddrinfo for host={} has no results rc={}", next_query->dns_name_,
222
19
                  gai_strerror(rc.return_value_));
223
19
        next_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::NoResult));
224
19
        response = std::make_pair(ResolutionStatus::Failure, std::list<DnsResponse>());
225
19
      } else {
226
7
        ENVOY_LOG(debug, "getaddrinfo failed for host={} with rc={} errno={}",
227
7
                  next_query->dns_name_, gai_strerror(rc.return_value_), errorDetails(rc.errno_));
228
7
        next_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Failed));
229
7
        response = std::make_pair(ResolutionStatus::Failure, std::list<DnsResponse>());
230
7
      }
231
148
      details = gai_strerror(rc.return_value_);
232
148
    }
233

            
234
    dispatcher_.post([finished_query = std::move(next_query), response = std::move(response),
235
148
                      details = std::string(details)]() mutable {
236
146
      if (finished_query->isCancelled()) {
237
2
        finished_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Cancelled));
238
2
        ENVOY_LOG(trace, "dropping cancelled query [{}]", finished_query->dns_name_);
239
144
      } else {
240
144
        finished_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Callback));
241
144
        finished_query->callback_(response.first, std::move(details), std::move(response.second));
242
144
      }
243
146
    });
244
148
  }
245

            
246
252
  ENVOY_LOG(trace, "getaddrinfo resolver thread exiting");
247
252
}
248

            
249
// Register the CaresDnsResolverFactory
250
REGISTER_FACTORY(GetAddrInfoDnsResolverFactory, DnsResolverFactory);
251

            
252
} // namespace Network
253
} // namespace Envoy