• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2016 Tatsuhiro Tsujikawa
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25 #include "shrpx_dns_tracker.h"
26 #include "shrpx_config.h"
27 #include "shrpx_log.h"
28 #include "util.h"
29 
30 namespace shrpx {
31 
32 namespace {
gccb(struct ev_loop * loop,ev_timer * w,int revents)33 void gccb(struct ev_loop *loop, ev_timer *w, int revents) {
34   auto dns_tracker = static_cast<DNSTracker *>(w->data);
35   dns_tracker->gc();
36 }
37 } // namespace
38 
DNSTracker(struct ev_loop * loop,int family)39 DNSTracker::DNSTracker(struct ev_loop *loop, int family)
40     : loop_(loop), family_(family) {
41   ev_timer_init(&gc_timer_, gccb, 0., 12_h);
42   gc_timer_.data = this;
43 }
44 
~DNSTracker()45 DNSTracker::~DNSTracker() {
46   ev_timer_stop(loop_, &gc_timer_);
47 
48   for (auto &p : ents_) {
49     auto &qlist = p.second.qlist;
50     while (!qlist.empty()) {
51       auto head = qlist.head;
52       qlist.remove(head);
53       head->status = DNSResolverStatus::ERROR;
54       head->in_qlist = false;
55       // TODO Not sure we should call callback here, or it is even be
56       // safe to do that.
57     }
58   }
59 }
60 
make_entry(std::unique_ptr<DualDNSResolver> resolv,ImmutableString host,DNSResolverStatus status,const Address * result)61 ResolverEntry DNSTracker::make_entry(std::unique_ptr<DualDNSResolver> resolv,
62                                      ImmutableString host,
63                                      DNSResolverStatus status,
64                                      const Address *result) {
65   auto &dnsconf = get_config()->dns;
66 
67   auto ent = ResolverEntry{};
68   ent.resolv = std::move(resolv);
69   ent.host = std::move(host);
70   ent.status = status;
71   switch (status) {
72   case DNSResolverStatus::ERROR:
73   case DNSResolverStatus::OK:
74     ent.expiry = std::chrono::steady_clock::now() +
75                  util::duration_from(dnsconf.timeout.cache);
76     break;
77   default:
78     break;
79   }
80   if (result) {
81     ent.result = *result;
82   }
83   return ent;
84 }
85 
update_entry(ResolverEntry & ent,std::unique_ptr<DualDNSResolver> resolv,DNSResolverStatus status,const Address * result)86 void DNSTracker::update_entry(ResolverEntry &ent,
87                               std::unique_ptr<DualDNSResolver> resolv,
88                               DNSResolverStatus status, const Address *result) {
89   auto &dnsconf = get_config()->dns;
90 
91   ent.resolv = std::move(resolv);
92   ent.status = status;
93   switch (status) {
94   case DNSResolverStatus::ERROR:
95   case DNSResolverStatus::OK:
96     ent.expiry = std::chrono::steady_clock::now() +
97                  util::duration_from(dnsconf.timeout.cache);
98     break;
99   default:
100     break;
101   }
102   if (result) {
103     ent.result = *result;
104   }
105 }
106 
resolve(Address * result,DNSQuery * dnsq)107 DNSResolverStatus DNSTracker::resolve(Address *result, DNSQuery *dnsq) {
108   int rv;
109 
110   auto it = ents_.find(dnsq->host);
111 
112   if (it == std::end(ents_)) {
113     if (LOG_ENABLED(INFO)) {
114       LOG(INFO) << "DNS entry not found for " << dnsq->host;
115     }
116 
117     auto resolv = std::make_unique<DualDNSResolver>(loop_, family_);
118     auto host_copy =
119         ImmutableString{std::begin(dnsq->host), std::end(dnsq->host)};
120     auto host = StringRef{host_copy};
121 
122     rv = resolv->resolve(host);
123     if (rv != 0) {
124       if (LOG_ENABLED(INFO)) {
125         LOG(INFO) << "Name lookup failed for " << host;
126       }
127 
128       ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
129                                      DNSResolverStatus::ERROR, nullptr));
130 
131       start_gc_timer();
132 
133       return DNSResolverStatus::ERROR;
134     }
135 
136     switch (resolv->get_status(result)) {
137     case DNSResolverStatus::ERROR:
138       if (LOG_ENABLED(INFO)) {
139         LOG(INFO) << "Name lookup failed for " << host;
140       }
141 
142       ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
143                                      DNSResolverStatus::ERROR, nullptr));
144 
145       start_gc_timer();
146 
147       return DNSResolverStatus::ERROR;
148     case DNSResolverStatus::OK:
149       if (LOG_ENABLED(INFO)) {
150         LOG(INFO) << "Name lookup succeeded: " << host << " -> "
151                   << util::numeric_name(&result->su.sa, result->len);
152       }
153 
154       ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
155                                      DNSResolverStatus::OK, result));
156 
157       start_gc_timer();
158 
159       return DNSResolverStatus::OK;
160     case DNSResolverStatus::RUNNING: {
161       auto p = ents_.emplace(host,
162                              make_entry(std::move(resolv), std::move(host_copy),
163                                         DNSResolverStatus::RUNNING, nullptr));
164 
165       start_gc_timer();
166 
167       auto &ent = (*p.first).second;
168 
169       add_to_qlist(ent, dnsq);
170 
171       return DNSResolverStatus::RUNNING;
172     }
173     default:
174       assert(0);
175     }
176   }
177 
178   auto &ent = (*it).second;
179 
180   if (ent.status != DNSResolverStatus::RUNNING &&
181       ent.expiry < std::chrono::steady_clock::now()) {
182     if (LOG_ENABLED(INFO)) {
183       LOG(INFO) << "DNS entry found for " << dnsq->host
184                 << ", but it has been expired";
185     }
186 
187     auto resolv = std::make_unique<DualDNSResolver>(loop_, family_);
188     auto host = StringRef{ent.host};
189 
190     rv = resolv->resolve(host);
191     if (rv != 0) {
192       if (LOG_ENABLED(INFO)) {
193         LOG(INFO) << "Name lookup failed for " << host;
194       }
195 
196       update_entry(ent, nullptr, DNSResolverStatus::ERROR, nullptr);
197 
198       return DNSResolverStatus::ERROR;
199     }
200 
201     switch (resolv->get_status(result)) {
202     case DNSResolverStatus::ERROR:
203       if (LOG_ENABLED(INFO)) {
204         LOG(INFO) << "Name lookup failed for " << host;
205       }
206 
207       update_entry(ent, nullptr, DNSResolverStatus::ERROR, nullptr);
208 
209       return DNSResolverStatus::ERROR;
210     case DNSResolverStatus::OK:
211       if (LOG_ENABLED(INFO)) {
212         LOG(INFO) << "Name lookup succeeded: " << host << " -> "
213                   << util::numeric_name(&result->su.sa, result->len);
214       }
215 
216       update_entry(ent, nullptr, DNSResolverStatus::OK, result);
217 
218       return DNSResolverStatus::OK;
219     case DNSResolverStatus::RUNNING:
220       update_entry(ent, std::move(resolv), DNSResolverStatus::RUNNING, nullptr);
221       add_to_qlist(ent, dnsq);
222 
223       return DNSResolverStatus::RUNNING;
224     default:
225       assert(0);
226     }
227   }
228 
229   switch (ent.status) {
230   case DNSResolverStatus::RUNNING:
231     if (LOG_ENABLED(INFO)) {
232       LOG(INFO) << "Waiting for name lookup complete for " << dnsq->host;
233     }
234     ent.qlist.append(dnsq);
235     dnsq->in_qlist = true;
236     return DNSResolverStatus::RUNNING;
237   case DNSResolverStatus::ERROR:
238     if (LOG_ENABLED(INFO)) {
239       LOG(INFO) << "Name lookup failed for " << dnsq->host << " (cached)";
240     }
241     return DNSResolverStatus::ERROR;
242   case DNSResolverStatus::OK:
243     if (LOG_ENABLED(INFO)) {
244       LOG(INFO) << "Name lookup succeeded (cached): " << dnsq->host << " -> "
245                 << util::numeric_name(&ent.result.su.sa, ent.result.len);
246     }
247     if (result) {
248       memcpy(result, &ent.result, sizeof(*result));
249     }
250     return DNSResolverStatus::OK;
251   default:
252     assert(0);
253     abort();
254   }
255 }
256 
add_to_qlist(ResolverEntry & ent,DNSQuery * dnsq)257 void DNSTracker::add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq) {
258   ent.resolv->set_complete_cb(
259       [&ent](DNSResolverStatus status, const Address *result) {
260         auto &qlist = ent.qlist;
261         while (!qlist.empty()) {
262           auto head = qlist.head;
263           qlist.remove(head);
264           head->status = status;
265           head->in_qlist = false;
266           auto cb = head->cb;
267           cb(status, result);
268         }
269 
270         auto &dnsconf = get_config()->dns;
271 
272         ent.resolv.reset();
273         ent.status = status;
274         ent.expiry = std::chrono::steady_clock::now() +
275                      util::duration_from(dnsconf.timeout.cache);
276         if (ent.status == DNSResolverStatus::OK) {
277           ent.result = *result;
278         }
279       });
280   ent.qlist.append(dnsq);
281   dnsq->in_qlist = true;
282 }
283 
cancel(DNSQuery * dnsq)284 void DNSTracker::cancel(DNSQuery *dnsq) {
285   if (!dnsq->in_qlist) {
286     return;
287   }
288 
289   auto it = ents_.find(dnsq->host);
290   if (it == std::end(ents_)) {
291     return;
292   }
293 
294   auto &ent = (*it).second;
295   ent.qlist.remove(dnsq);
296   dnsq->in_qlist = false;
297 }
298 
start_gc_timer()299 void DNSTracker::start_gc_timer() {
300   if (ev_is_active(&gc_timer_)) {
301     return;
302   }
303 
304   ev_timer_again(loop_, &gc_timer_);
305 }
306 
gc()307 void DNSTracker::gc() {
308   if (LOG_ENABLED(INFO)) {
309     LOG(INFO) << "Starting removing expired DNS cache entries";
310   }
311 
312   auto now = std::chrono::steady_clock::now();
313   for (auto it = std::begin(ents_); it != std::end(ents_);) {
314     auto &ent = (*it).second;
315     if (ent.expiry >= now) {
316       ++it;
317       continue;
318     }
319 
320     it = ents_.erase(it);
321   }
322 
323   if (ents_.empty()) {
324     ev_timer_stop(loop_, &gc_timer_);
325   }
326 }
327 
328 } // namespace shrpx
329