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
155
  AddrInfoWrapper(addrinfo* info) : info_(info) {}
11
155
  ~AddrInfoWrapper() {
12
155
    if (info_ != nullptr) {
13
118
      Api::OsSysCallsSingleton::get().freeaddrinfo(info_);
14
118
    }
15
155
  }
16
120
  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
120
                                        const addrinfo* addrinfo_result) {
77
120
  std::list<DnsResponse> v4_results;
78
120
  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
255
  for (auto ai = addrinfo_result; ai != nullptr; ai = ai->ai_next) {
82
135
    if (ai->ai_family == AF_INET) {
83
116
      sockaddr_in address;
84
116
      memset(&address, 0, sizeof(address));
85
116
      address.sin_family = AF_INET;
86
116
      address.sin_port = 0;
87
116
      address.sin_addr = reinterpret_cast<sockaddr_in*>(ai->ai_addr)->sin_addr;
88

            
89
116
      v4_results.emplace_back(
90
116
          DnsResponse(std::make_shared<const Address::Ipv4Instance>(&address), DEFAULT_TTL));
91
116
    } 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
135
  }
101

            
102
120
  std::list<DnsResponse> final_results;
103
120
  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
80
  case DnsLookupFamily::V4Only:
109
80
    final_results = std::move(v4_results);
110
80
    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
120
  }
130

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

            
136
120
  return std::make_pair(ResolutionStatus::Completed, final_results);
137
120
}
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
1110
      auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
149
1110
        return shutting_down_ || !pending_queries_.empty();
150
1110
      };
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
2
        continue;
162
2
      }
163
157
    }
164

            
165
155
    ENVOY_LOG(trace, "Thread ({}) popped pending query [{}]",
166
155
              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
155
    std::pair<ResolutionStatus, std::list<DnsResponse>> response;
170
155
    std::string details;
171
155
    {
172
155
      next_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Starting));
173
155
      addrinfo hints;
174
155
      memset(&hints, 0, sizeof(hints));
175
155
      if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.getaddrinfo_no_ai_flags")) {
176
155
        hints.ai_flags = AI_ADDRCONFIG;
177
155
      }
178
155
      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
155
      hints.ai_socktype = SOCK_STREAM;
183
155
      addrinfo* addrinfo_result_do_not_use = nullptr;
184
155
      auto rc = Api::OsSysCallsSingleton::get().getaddrinfo(next_query->dns_name_.c_str(), nullptr,
185
155
                                                            &hints, &addrinfo_result_do_not_use);
186
155
      auto addrinfo_wrapper = AddrInfoWrapper(addrinfo_result_do_not_use);
187
155
      if (rc.return_value_ == 0) {
188
120
        next_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Success));
189
120
        response = processResponse(*next_query, addrinfo_wrapper.get());
190
125
      } 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
147
      details = gai_strerror(rc.return_value_);
232
147
    }
233

            
234
    dispatcher_.post([finished_query = std::move(next_query), response = std::move(response),
235
147
                      details = std::string(details)]() mutable {
236
145
      if (finished_query->isCancelled()) {
237
1
        finished_query->addTrace(static_cast<uint8_t>(GetAddrInfoTrace::Cancelled));
238
1
        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
145
    });
244
147
  }
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