Line data Source code
1 : #include "source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.h"
2 :
3 : #include "envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.h"
4 : #include "envoy/network/dns_resolver.h"
5 :
6 : #include "source/common/api/os_sys_calls_impl.h"
7 : #include "source/common/network/address_impl.h"
8 :
9 : namespace Envoy {
10 : namespace Network {
11 :
12 : // This resolver uses getaddrinfo() on a dedicated resolution thread. Thus, it is only suitable
13 : // currently for relatively low rate resolutions. In the future a thread pool could be added if
14 : // desired.
15 : class GetAddrInfoDnsResolver : public DnsResolver, public Logger::Loggable<Logger::Id::dns> {
16 : public:
17 : GetAddrInfoDnsResolver(Event::Dispatcher& dispatcher, Api::Api& api)
18 : : dispatcher_(dispatcher),
19 0 : resolver_thread_(api.threadFactory().createThread([this] { resolveThreadRoutine(); })) {}
20 :
21 0 : ~GetAddrInfoDnsResolver() override {
22 0 : {
23 0 : absl::MutexLock guard(&mutex_);
24 0 : shutting_down_ = true;
25 0 : }
26 :
27 0 : resolver_thread_->join();
28 0 : }
29 :
30 : // DnsResolver
31 : ActiveDnsQuery* resolve(const std::string& dns_name, DnsLookupFamily dns_lookup_family,
32 0 : ResolveCb callback) override {
33 0 : ENVOY_LOG(debug, "adding new query [{}] to pending queries", dns_name);
34 0 : auto new_query = std::make_unique<PendingQuery>(dns_name, dns_lookup_family, callback);
35 0 : absl::MutexLock guard(&mutex_);
36 0 : pending_queries_.emplace_back(std::move(new_query));
37 0 : return pending_queries_.back().get();
38 0 : }
39 :
40 0 : void resetNetworking() override {}
41 :
42 : private:
43 : class PendingQuery : public ActiveDnsQuery {
44 : public:
45 : PendingQuery(const std::string& dns_name, DnsLookupFamily dns_lookup_family, ResolveCb callback)
46 0 : : dns_name_(dns_name), dns_lookup_family_(dns_lookup_family), callback_(callback) {}
47 :
48 0 : void cancel(CancelReason) override {
49 0 : ENVOY_LOG(debug, "cancelling query [{}]", dns_name_);
50 0 : cancelled_ = true;
51 0 : }
52 :
53 : const std::string dns_name_;
54 : const DnsLookupFamily dns_lookup_family_;
55 : ResolveCb callback_;
56 : bool cancelled_{false};
57 : };
58 : // Must be a shared_ptr for passing around via post.
59 : using PendingQuerySharedPtr = std::shared_ptr<PendingQuery>;
60 :
61 : // RAII wrapper to free the `addrinfo`.
62 : class AddrInfoWrapper : NonCopyable {
63 : public:
64 0 : AddrInfoWrapper(addrinfo* info) : info_(info) {}
65 0 : ~AddrInfoWrapper() {
66 0 : if (info_ != nullptr) {
67 0 : Api::OsSysCallsSingleton::get().freeaddrinfo(info_);
68 0 : }
69 0 : }
70 0 : const addrinfo* get() { return info_; }
71 :
72 : private:
73 : addrinfo* info_;
74 : };
75 :
76 : // Parse a getaddrinfo() response and determine the final address list. We could potentially avoid
77 : // adding v4 or v6 addresses if we know they will never be used. Right now the final filtering is
78 : // done below and this code is kept simple.
79 : std::pair<ResolutionStatus, std::list<DnsResponse>>
80 0 : processResponse(const PendingQuery& query, const addrinfo* addrinfo_result) {
81 0 : std::list<DnsResponse> v4_results;
82 0 : std::list<DnsResponse> v6_results;
83 0 : for (auto ai = addrinfo_result; ai != nullptr; ai = ai->ai_next) {
84 0 : if (ai->ai_family == AF_INET) {
85 0 : sockaddr_in address;
86 0 : memset(&address, 0, sizeof(address));
87 0 : address.sin_family = AF_INET;
88 0 : address.sin_port = 0;
89 0 : address.sin_addr = reinterpret_cast<sockaddr_in*>(ai->ai_addr)->sin_addr;
90 :
91 0 : v4_results.emplace_back(
92 0 : DnsResponse(std::make_shared<const Address::Ipv4Instance>(&address), DEFAULT_TTL));
93 0 : } else if (ai->ai_family == AF_INET6) {
94 0 : sockaddr_in6 address;
95 0 : memset(&address, 0, sizeof(address));
96 0 : address.sin6_family = AF_INET6;
97 0 : address.sin6_port = 0;
98 0 : address.sin6_addr = reinterpret_cast<sockaddr_in6*>(ai->ai_addr)->sin6_addr;
99 0 : v6_results.emplace_back(
100 0 : DnsResponse(std::make_shared<const Address::Ipv6Instance>(address), DEFAULT_TTL));
101 0 : }
102 0 : }
103 :
104 0 : std::list<DnsResponse> final_results;
105 0 : switch (query.dns_lookup_family_)
106 0 : case DnsLookupFamily::All: {
107 0 : final_results = std::move(v4_results);
108 0 : final_results.splice(final_results.begin(), v6_results);
109 0 : break;
110 0 : case DnsLookupFamily::V4Only:
111 0 : final_results = std::move(v4_results);
112 0 : break;
113 0 : case DnsLookupFamily::V6Only:
114 0 : final_results = std::move(v6_results);
115 0 : break;
116 0 : case DnsLookupFamily::V4Preferred:
117 0 : if (!v4_results.empty()) {
118 0 : final_results = std::move(v4_results);
119 0 : } else {
120 0 : final_results = std::move(v6_results);
121 0 : }
122 0 : break;
123 0 : case DnsLookupFamily::Auto:
124 : // This is effectively V6Preferred.
125 0 : if (!v6_results.empty()) {
126 0 : final_results = std::move(v6_results);
127 0 : } else {
128 0 : final_results = std::move(v4_results);
129 0 : }
130 0 : break;
131 0 : }
132 :
133 0 : ENVOY_LOG(
134 0 : debug, "getaddrinfo resolution complete for host '{}': {}", query.dns_name_,
135 0 : accumulateToString<Network::DnsResponse>(final_results, [](const auto& dns_response) {
136 0 : return dns_response.addrInfo().address_->asString();
137 0 : }));
138 :
139 0 : return std::make_pair(ResolutionStatus::Success, final_results);
140 0 : }
141 :
142 : // Background thread which wakes up and does resolutions.
143 0 : void resolveThreadRoutine() {
144 0 : ENVOY_LOG(debug, "starting getaddrinfo resolver thread");
145 :
146 0 : while (true) {
147 0 : PendingQuerySharedPtr next_query;
148 0 : {
149 0 : absl::MutexLock guard(&mutex_);
150 0 : auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
151 0 : return shutting_down_ || !pending_queries_.empty();
152 0 : };
153 0 : mutex_.Await(absl::Condition(&condition));
154 0 : if (shutting_down_) {
155 0 : break;
156 0 : }
157 :
158 0 : next_query = std::move(pending_queries_.front());
159 0 : pending_queries_.pop_front();
160 0 : }
161 :
162 0 : ENVOY_LOG(debug, "popped pending query [{}]", next_query->dns_name_);
163 :
164 : // For mock testing make sure the getaddrinfo() response is freed prior to the post.
165 0 : std::pair<ResolutionStatus, std::list<DnsResponse>> response;
166 0 : {
167 0 : addrinfo hints;
168 0 : memset(&hints, 0, sizeof(hints));
169 0 : hints.ai_flags = AI_ADDRCONFIG;
170 0 : hints.ai_family = AF_UNSPEC;
171 : // If we don't specify a socket type, every address will appear twice, once
172 : // for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
173 : // anyway, just pick one.
174 0 : hints.ai_socktype = SOCK_STREAM;
175 0 : addrinfo* addrinfo_result_do_not_use = nullptr;
176 0 : auto rc = Api::OsSysCallsSingleton::get().getaddrinfo(
177 0 : next_query->dns_name_.c_str(), nullptr, &hints, &addrinfo_result_do_not_use);
178 0 : auto addrinfo_wrapper = AddrInfoWrapper(addrinfo_result_do_not_use);
179 0 : if (rc.return_value_ == 0) {
180 0 : response = processResponse(*next_query, addrinfo_wrapper.get());
181 0 : } else {
182 : // TODO(mattklein123): Handle some errors differently such as `EAI_NODATA`.
183 0 : ENVOY_LOG(debug, "getaddrinfo failed with rc={} errno={}", gai_strerror(rc.return_value_),
184 0 : errorDetails(rc.errno_));
185 0 : response = std::make_pair(ResolutionStatus::Failure, std::list<DnsResponse>());
186 0 : }
187 0 : }
188 :
189 0 : dispatcher_.post(
190 0 : [finished_query = std::move(next_query), response = std::move(response)]() mutable {
191 0 : if (finished_query->cancelled_) {
192 0 : ENVOY_LOG(debug, "dropping cancelled query [{}]", finished_query->dns_name_);
193 0 : } else {
194 0 : finished_query->callback_(response.first, std::move(response.second));
195 0 : }
196 0 : });
197 0 : }
198 :
199 0 : ENVOY_LOG(debug, "getaddrinfo resolver thread exiting");
200 0 : }
201 :
202 : // getaddrinfo() doesn't provide TTL so use a hard coded default. This can be made configurable
203 : // later if needed.
204 : static constexpr std::chrono::seconds DEFAULT_TTL = std::chrono::seconds(60);
205 :
206 : Event::Dispatcher& dispatcher_;
207 : absl::Mutex mutex_;
208 : std::list<PendingQuerySharedPtr> pending_queries_ ABSL_GUARDED_BY(mutex_);
209 : bool shutting_down_ ABSL_GUARDED_BY(mutex_){};
210 : // The resolver thread must be initialized last so that the above members are already fully
211 : // initialized.
212 : const Thread::ThreadPtr resolver_thread_;
213 : };
214 :
215 : // getaddrinfo DNS resolver factory
216 : class GetAddrInfoDnsResolverFactory : public DnsResolverFactory,
217 : public Logger::Loggable<Logger::Id::dns> {
218 : public:
219 38 : std::string name() const override { return {"envoy.network.dns_resolver.getaddrinfo"}; }
220 :
221 1 : ProtobufTypes::MessagePtr createEmptyConfigProto() override {
222 1 : return ProtobufTypes::MessagePtr{new envoy::extensions::network::dns_resolver::getaddrinfo::v3::
223 1 : GetAddrInfoDnsResolverConfig()};
224 1 : }
225 :
226 : DnsResolverSharedPtr
227 : createDnsResolver(Event::Dispatcher& dispatcher, Api::Api& api,
228 0 : const envoy::config::core::v3::TypedExtensionConfig&) const override {
229 0 : return std::make_shared<GetAddrInfoDnsResolver>(dispatcher, api);
230 0 : }
231 : };
232 :
233 : // Register the CaresDnsResolverFactory
234 : REGISTER_FACTORY(GetAddrInfoDnsResolverFactory, DnsResolverFactory);
235 :
236 : } // namespace Network
237 : } // namespace Envoy
|