libutil++  1.9.3
 All Classes Functions Variables
DNS.cpp
1 /*
2 ** libutil++
3 ** $Id: DNS.cpp 1884 2017-04-15 21:42:02Z sella $
4 ** Copyright (c) 2011-2016 Digital Genesis, LLC. All Rights Reserved.
5 ** Released under the LGPL Version 2.1 License.
6 ** http://www.digitalgenesis.com
7 */
8 
9 static const char rcsid[] __attribute__((used)) = "$Id: DNS.cpp 1884 2017-04-15 21:42:02Z sella $";
10 
11 #include "DNS.h"
12 #include "DNSException.h"
13 #include "DNSCallback.h"
14 #include "../util/ThreadPool.h"
15 #include "../util/Time.h"
16 
17 #include <string.h>
18 #include <fcntl.h>
19 #include <net/if.h>
20 #include <arpa/inet.h>
21 #include <sys/ioctl.h>
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <netinet/in.h>
25 #include <netdb.h>
26 #include <udns.h>
27 
29  struct sockaddr_storage *ss;
30  struct sockaddr *sa;
31  struct sockaddr_in *sa_in;
32  struct sockaddr_in6 *sa_in6;
33 };
34 
35 using namespace sella::net;
36 using namespace sella::util;
37 
38 const std::vector<std::string> DNS::empty;
39 bool walk(std::vector<IPAddress> &ips, struct addrinfo *result, union __sockaddr_union &u, int protocol);
40 
41 DNS::DNS(const std::vector<std::string> &nameservers, const std::vector<std::string> &searches) throw (DNSException) :
42  pool(NULL),
43  context(NULL),
44  flags(0)
45 {
46  init(nameservers, searches);
47 }
48 
49 DNS::DNS(sella::util::ThreadPool *pool, const std::vector<std::string> &nameservers, const std::vector<std::string> &searches) throw (DNSException) :
50  pool(pool),
51  context(NULL),
52  flags(0)
53 {
54  init(nameservers, searches);
55 
56  Task::callback function = std::bind(&DNS::receive, this, 0, 0);
57  task = Task::shared_ptr();
58  task->setCallback(function);
59 }
60 
61 DNS::~DNS() throw () {
62  if (pool && task) {
63  pool->unschedule(task, true);
64  }
65 
66  ::dns_free(this->context);
67 }
68 
69 std::vector<std::string> DNS::resolve(const std::string &name, int record, int protocol) throw (DNSException) {
71 
72  resolve(&c, name, record, protocol);
73 
74  while (getActive() > 0) {
75  receive(250000);
76  }
77 
78  return c.getResponses();
79 }
80 
81 sella::variant::Variant DNS::resolve(const std::vector<std::string> &names, int record, int protocol) throw (DNSException) {
82  sella::variant::Variant responses;
83  StoreDNSCallback c[names.size()];
84 
85  for (size_t i = 0; i < names.size(); i++) {
86  resolve(&(c[i]), names[i], record, protocol);
87  }
88 
89  while (getActive() > 0) {
90  receive(250000);
91  }
92 
93  for (size_t i = 0; i < names.size(); i++) {
94  const auto &r = c[i].getResponses();
95  auto &response = responses[c[i].qname]["response"];
96 
97  for (auto i = r.begin(); i != r.end(); ++i) {
98  response.push_back(*i);
99  }
100 
101  responses[c[i].qname]["qname"] = c[i].getQname();
102  responses[c[i].qname]["ttl"] = c[i].getTTL();
103  responses[c[i].qname]["error"] = c[i].getErrorName();
104  }
105 
106  return responses;
107 }
108 
109 std::vector<std::string> DNS::bulkResolve(const std::vector<std::string> &names, int record, int protocol) throw (DNSException) {
110  std::vector<std::string> responses;
111  StoreDNSCallback c[names.size()];
112 
113  for (size_t i = 0; i < names.size(); i++) {
114  resolve(&(c[i]), names[i], record, protocol);
115  }
116 
117  while (getActive() > 0) {
118  receive(250000);
119  }
120 
121  for (size_t i = 0; i < names.size(); i++) {
122  const auto &r = c[i].getResponses();
123 
124  responses.insert(responses.end(), r.begin(), r.end());
125  }
126 
127  return responses;
128 }
129 
130 std::vector<std::string> DNS::sresolve(const std::string &name, int protocol) throw (DNSException) {
131  const std::vector<IPAddress> &addresses = DNS::sresolve_ip(name, protocol);
132  std::vector<std::string> response;
133 
134  for (auto a = addresses.begin(); a != addresses.end(); ++a) {
135  response.push_back(a->str());
136  }
137 
138  return response;
139 }
140 
141 std::vector<IPAddress> DNS::sresolve_ip(const std::string &name, int protocol) throw (DNSException) {
142  int s;
143  union __sockaddr_union u;
144  struct sockaddr_storage addr;
145  struct addrinfo hints, *result;
146  std::vector<IPAddress> ips;
147 
148  memset(&hints, 0, sizeof(struct addrinfo));
149  hints.ai_family = AF_UNSPEC;
150  hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
151  hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
152  hints.ai_protocol = 0; /* Any protocol */
153  hints.ai_canonname = NULL;
154  hints.ai_addr = NULL;
155  hints.ai_next = NULL;
156 
157  if (protocol == IPv4Required) {
158  hints.ai_family = AF_INET;
159  } else if (protocol == IPv6Required) {
160  hints.ai_family = AF_INET6;
161  } else {
162  THROW(DNSException, "unknown protocol (%d)", protocol);
163  }
164 
165  if ((s = getaddrinfo(name.c_str(), NULL, &hints, &result)) != 0) {
166  if (s == EAI_NODATA) {
167  return ips;
168  }
169 
170  THROW_CODE(DNSException, s, "getaddrinfo() failed: %s", gai_strerror(s));
171  }
172 
173  u.ss = (struct sockaddr_storage*) &addr;
174  u.sa_in = (struct sockaddr_in*) &addr;
175  memset(&addr, 0, sizeof(addr));
176 
177  try {
178  if (protocol == AnyProtocol) {
179  walk(ips, result, u, AF_UNSPEC);
180  } else if (protocol == IPv4Preferred) {
181  if (!walk(ips, result, u, AF_INET)) {
182  walk(ips, result, u, AF_INET6);
183  }
184  } else if (protocol == IPv4Required) {
185  walk(ips, result, u, AF_INET);
186  } else if (protocol == IPv6Preferred) {
187  if (!walk(ips, result, u, AF_INET6)) {
188  walk(ips, result, u, AF_INET);
189  }
190  } else if (protocol == IPv6Required) {
191  walk(ips, result, u, AF_INET6);
192  }
193 
194  freeaddrinfo(result);
195  } catch (DNSException &e) {
196  freeaddrinfo(result);
197 
198  throw e;
199  }
200 
201  return ips;
202 }
203 
204 DNSCallback* DNS::resolve(DNSCallback* callback, const std::string &name, int record, int protocol) throw (DNSException) {
205  if (callback == NULL) {
206  THROW(DNSException, "invalid NULL callback supplied");
207  }
208 
209  callback->qname = name;
210  callback->record = record;
211  callback->protocol = protocol;
212  callback->flags = flags;
213 
214  switch (record) {
215  case A:
216  switch (protocol) {
217  case AnyProtocol:
218  case IPv4Preferred:
219  case IPv4Required:
220  ::dns_submit_p(this->context, name.c_str(), DNS_C_IN, DNS_T_A, flags, ::dns_parse_a4, DNS::callback_a, callback);
221  break;
222 
223  case IPv6Preferred:
224  case IPv6Required:
225  ::dns_submit_p(this->context, name.c_str(), DNS_C_IN, DNS_T_AAAA, flags, ::dns_parse_a6, DNS::callback_aaaa, callback);
226  break;
227 
228  default:
229  THROW(DNSException, "invalid protocol supplied (%d); must be AnyProtocol, IPv4Preferred, IPv4Required, IPv6Preferred, IPv6Required", protocol);
230  }
231  break;
232 
233  case PTR:
234  try {
235  IPAddress ip(name);
236 
237  if (ip.getFamily() == AF_INET) {
238  ::dns_submit_a4ptr(this->context, &(ip.getIPv4AddressStructure()), DNS::callback_ptr, callback);
239  } else {
240  ::dns_submit_a6ptr(this->context, &(ip.getIPv6AddressStructure()), DNS::callback_ptr, callback);
241  }
242 
243  break;
244  } catch (AddressException &e) {
245  RETHROW(DNSException, e);
246  }
247 
248  default:
249  THROW(DNSException, "record type %d has not been implemented yet", record);
250  }
251 
252 
253  if (pool) {
254  pool->reschedule(task, 0);
255  }
256 
257  return callback;
258 }
259 
260 bool DNS::receive(uint64_t usec, time_t now) throw () {
261  fd_set rfds;
262  struct timeval tv = { 0, 0 };
263 
264  if (usec > 0) {
265  tv = sella::util::Time::usectotv(usec);
266  }
267 
268  if (!now) {
269  now = time(NULL);
270  }
271 
272  std::lock_guard<std::mutex> lg(mutex);
273 
274  if (pool && getActive() > 0) {
275  pool->reschedule(task, 0, interval);
276  }
277 
278  ::dns_timeouts(this->context, -1, now);
279 
280  int s = dns_sock(context);
281 
282  FD_ZERO(&rfds);
283  FD_SET(s, &rfds);
284 
285  if (select(s + 1, &rfds, NULL, NULL, &tv) > 0) {
286  ::dns_ioevent(this->context, now);
287 
288  return true;
289  }
290 
291  return false;
292 }
293 
294 void DNS::setInterval(uint64_t usec) throw () {
295  this->interval = (usec > 0) ? usec : DefaultInterval;
296 }
297 
298 void DNS::setNoSearch(bool on) throw () {
299  flags = dns_set_opt(context, DNS_OPT_FLAGS, -1); /* -1 triggers get */
300 
301  if (on) {
302  flags |= DNS_NOSRCH;
303  } else {
304  flags &= ~DNS_NOSRCH;
305  }
306 
307  dns_set_opt(context, DNS_OPT_FLAGS, flags);
308 }
309 
310 void DNS::setNoRecursion(bool on) throw () {
311  flags = dns_set_opt(context, DNS_OPT_FLAGS, -1); /* -1 triggers get */
312 
313  if (on) {
314  flags |= DNS_NORD;
315  } else {
316  flags &= ~DNS_NORD;
317  }
318 
319  dns_set_opt(context, DNS_OPT_FLAGS, flags);
320 }
321 
322 void DNS::setAuthoritativeOnly(bool on) throw () {
323  flags = dns_set_opt(context, DNS_OPT_FLAGS, -1); /* -1 triggers get */
324 
325  if (on) {
326  flags |= DNS_AAONLY;
327  } else {
328  flags &= ~DNS_AAONLY;
329  }
330 
331  dns_set_opt(context, DNS_OPT_FLAGS, flags);
332 }
333 
334 void DNS::setTimeout(int seconds) throw () {
335  dns_set_opt(context, DNS_OPT_TIMEOUT, seconds);
336 }
337 
338 void DNS::setRetries(int retries) throw () {
339  dns_set_opt(context, DNS_OPT_NTRIES, retries);
340 }
341 
342 void DNS::setDots(int ndots) throw () {
343  dns_set_opt(context, DNS_OPT_NDOTS, ndots);
344 }
345 
346 void DNS::setUDPSize(int bytes) throw () {
347  dns_set_opt(context, DNS_OPT_UDPSIZE, bytes);
348 }
349 
350 void DNS::setUDPPort(short port) throw () {
351  dns_set_opt(context, DNS_OPT_PORT, (int) port);
352 }
353 
354 int DNS::getActive() throw () {
355  return dns_active(context);
356 }
357 
358 int DNS::getFD() throw () {
359  return dns_sock(context);
360 }
361 
362 const DNS::shared DNS::shared_ptr(const std::vector<std::string> &nameservers, const std::vector<std::string> &searches) throw (DNSException) {
363  return std::make_shared<DNS>(nameservers, searches);
364 }
365 
366 const DNS::shared DNS::shared_ptr(sella::util::ThreadPool *pool, const std::vector<std::string> &nameservers, const std::vector<std::string> &searches) throw (DNSException) {
367  return std::make_shared<DNS>(pool, nameservers, searches);
368 }
369 
370 const IPAddress DNS::convertArpa(const std::string &qname) throw (DNSException) {
371  if (qname.size() < 5 || qname.substr(qname.size() - 5).compare(".arpa") != 0) {
372  THROW(DNSException, "Input is not a valid .in-addr.arpa or .ip6.arapa record");
373  }
374 
375  try {
376  std::string buf;
377  const auto &tokens = String::split(qname, '.', false);
378 
379  if (tokens.size() == 6) {
380  buf.append(tokens[3]).append(".").append(tokens[2]).append(".").append(tokens[1]).append(".").append(tokens[0]);
381  } else if (tokens.size() == 34) {
382  for (ssize_t i = 31; i >= 0; i--) {
383  buf.append(tokens[i]);
384 
385  if (i % 4 == 0 && i > 0) {
386  buf.append(":");
387  }
388  }
389  } else {
390  THROW(DNSException, "Input is not a valid .in-addr.arpa or .ip6.arapa record");
391  }
392 
393  return IPAddress(buf);
394  } catch (AddressException &e) {
395  THROW(DNSException, "Input is not a valid .in-addr.arpa or .ip6.arapa record");
396  }
397 }
398 
399 void DNS::callback_a(struct dns_ctx *context, void *result, void *argument) {
400  std::vector<std::string> resolved;
401  DNSCallback *callback = reinterpret_cast<DNSCallback *>(argument);
402  struct dns_rr_a4 *response = reinterpret_cast<struct dns_rr_a4 *>(result);
403 
404  //printf("callback_a - %s\n", callback->qname.c_str());
405  //usleep(99000);
406 
407  int status = ::dns_status(context);
408 
409  if (status != DNS_E_NOERROR && status != DNS_E_NODATA) {
410 // printf("callback_a - failure\n");
411  callback->failure(callback->qname, static_cast<DNSCallback::Error>(::dns_status(context)));
412  } else if (!response || response->dnsa4_nrr == 0) {
413  //printf("callback_a - !response\n");
414  if (callback->protocol != IPv4Required) {
415  if (callback->protocol == IPv4Preferred) {
416  callback->protocol = IPv6Required;
417  }
418 
419  //printf("callback_a - go v6\n");
420  ::dns_submit_p(context, callback->qname.c_str(), DNS_C_IN, DNS_T_AAAA, callback->flags, ::dns_parse_a6, DNS::callback_aaaa, callback);
421  } else {
422  //printf("callback_a - success (final)\n");
423  callback->success(callback->qname, std::string(), -1, resolved);
424  }
425  } else {
426  //printf("callback_a - got: %d\n", response->dnsa4_nrr);
427  for (int i = 0; i < response->dnsa4_nrr; i++) {
428  try {
429  resolved.push_back(IPAddress(&response->dnsa4_addr[i]).str());
430  } catch (AddressException &e) { }
431  }
432 
433  //printf("callback_a - success\n");
434  if (strcmp(response->dnsa4_qname, response->dnsa4_cname) != 0) {
435  callback->success(response->dnsa4_qname, response->dnsa4_cname, response->dnsa4_ttl, resolved);
436  } else {
437  callback->success(response->dnsa4_qname, std::string(), response->dnsa4_ttl, resolved);
438  }
439 
440  if (callback->protocol != IPv4Required && callback->protocol != IPv4Preferred) {
441  //printf("callback_a - go v6: %d\n", callback->protocol);
442  ::dns_submit_p(context, response->dnsa4_qname, DNS_C_IN, DNS_T_AAAA, callback->flags, ::dns_parse_a6, DNS::callback_aaaa, callback);
443  }
444  }
445 }
446 
447 void DNS::callback_aaaa(struct dns_ctx *context, void *result, void *argument) {
448  std::vector<std::string> resolved;
449  DNSCallback *callback = reinterpret_cast<DNSCallback *>(argument);
450  struct dns_rr_a6 *response = reinterpret_cast<struct dns_rr_a6 *>(result);
451 
452  //printf("callback_aaaa - %s\n", callback->qname.c_str());
453  //usleep(99000);
454 
455  int status = ::dns_status(context);
456 
457  if (status != DNS_E_NOERROR && status != DNS_E_NODATA) {
458  //printf("callback_aaaa - failure\n");
459  callback->failure(callback->qname, static_cast<DNSCallback::Error>(::dns_status(context)));
460  } else if (!response || response->dnsa6_nrr == 0) {
461  //printf("callback_aaaa - !response\n");
462  if (callback->protocol != IPv6Required && callback->protocol != AnyProtocol) {
463  if (callback->protocol == IPv6Preferred) {
464  callback->protocol = IPv4Required;
465  }
466 
467  //printf("callback_aaaa - go v4\n");
468  ::dns_submit_p(context, callback->qname.c_str(), DNS_C_IN, DNS_T_A, callback->flags, ::dns_parse_a4, DNS::callback_a, callback);
469  } else {
470  //printf("callback_aaaa - success (final)\n");
471  callback->success(callback->qname, std::string(), -1, resolved);
472  }
473  } else {
474  //printf("callback_aaaa - got: %d\n", response->dnsa6_nrr);
475  for (int i = 0; i < response->dnsa6_nrr; i++) {
476  try {
477  resolved.push_back(IPAddress(&response->dnsa6_addr[i]).str());
478  } catch (AddressException &e) { }
479  }
480 
481  //printf("callback_aaaa - success\n");
482  if (strcmp(response->dnsa6_qname, response->dnsa6_cname) != 0) {
483  callback->success(response->dnsa6_qname, response->dnsa6_cname, response->dnsa6_ttl, resolved);
484  } else {
485  callback->success(response->dnsa6_qname, std::string(), response->dnsa6_ttl, resolved);
486  }
487 
488  if (callback->protocol != IPv6Required && callback->protocol != IPv6Preferred && callback->protocol != AnyProtocol) {
489  //printf("callback_aaaa - go v4\n");
490  ::dns_submit_p(context, response->dnsa6_qname, DNS_C_IN, DNS_T_A, callback->flags, ::dns_parse_a4, DNS::callback_a, callback);
491  }
492  }
493 }
494 
495 void DNS::callback_ptr(struct dns_ctx *context, dns_rr_ptr *result, void *argument) {
496  std::vector<std::string> reverses;
497  DNSCallback *callback = reinterpret_cast<DNSCallback*>(argument);
498  struct dns_rr_ptr *response = reinterpret_cast<struct dns_rr_ptr*>(result);
499 
500 // printf("callback_ptr - %s\n", callback->qname.c_str());
501 // usleep(99000);
502 
503  int status = ::dns_status(context);
504 
505  if (status != DNS_E_NOERROR && status != DNS_E_NODATA) {
506 // printf("callback_ptr - failure\n");
507  callback->failure(callback->qname, static_cast<DNSCallback::Error>(::dns_status(context)));
508  } else if (!response || response->dnsptr_nrr == 0) {
509  callback->success(callback->qname, std::string(), -1, reverses);
510  } else {
511 // printf("callback_ptr - got: %d\n", response->dnsptr_nrr);
512  for (int i = 0; i < response->dnsptr_nrr; i++) {
513  reverses.push_back(response->dnsptr_ptr[i]);
514  }
515 
516 // printf("callback_ptr - success\n");
517  if (strcmp(response->dnsptr_qname, response->dnsptr_cname) != 0) {
518  callback->success(response->dnsptr_qname, response->dnsptr_cname, response->dnsptr_ttl, reverses);
519  } else {
520  callback->success(response->dnsptr_qname, std::string(), response->dnsptr_ttl, reverses);
521  }
522  }
523 }
524 
525 void DNS::init(const std::vector<std::string> &nameservers, const std::vector<std::string> &searches) {
526  if (dns_init(NULL, 0) < 0) { /* Sets up internal default context */
527  THROW(DNSException, "failed to initialize default DNS context");
528  }
529 
530  if (!(this->context = dns_new(NULL))) { /* Copies internal default context */
531  THROW(DNSException, "failed to allocate DNS context");
532  }
533 
534  if (::dns_init(this->context, 0) < 0) {
535  THROW(DNSException, "failed to initialize DNS context");
536  }
537 
538  if (!nameservers.empty()) {
539  ::dns_add_serv(context, NULL);
540 
541  for (auto n = nameservers.begin(); n != nameservers.end(); ++n) {
542  if (::dns_add_serv(context, n->c_str()) < 0) {
543  THROW2(DNSException, "failed to set nameserver '%s'", n->c_str());
544  }
545  }
546  }
547 
548  if (!searches.empty()) {
549  ::dns_add_srch(context, NULL);
550 
551  for (auto s = searches.begin(); s != searches.end(); ++s) {
552  if (::dns_add_srch(context, s->c_str()) < 0) {
553  THROW2(DNSException, "failed to set search '%s'", s->c_str());
554  }
555  }
556  }
557 
558  this->flags = dns_set_opt(context, DNS_OPT_FLAGS, -1); /* -1 triggers get */
559 
560  if (::dns_open(this->context) < 1) {
561  THROW(DNSException, "failed to open DNS socket");
562  }
563 }
564 
565 bool walk(std::vector<IPAddress> &ips, struct addrinfo *result, union __sockaddr_union &u, int protocol) {
566  bool match = false;
567  struct addrinfo *rp;
568 
569  try {
570  for (rp = result; rp != NULL; rp = rp->ai_next) {
571  if ((protocol == AF_INET || protocol == AF_UNSPEC) && rp->ai_family == AF_INET) {
572  match = true;
573  memcpy(u.sa_in, rp->ai_addr, rp->ai_addrlen);
574  u.ss->ss_family = protocol;
575 
576  ips.push_back(IPAddress(u.ss));
577  } else if ((protocol == AF_INET6 || protocol == AF_UNSPEC) && rp->ai_family == AF_INET6) {
578  match = true;
579  memcpy(u.sa_in6, rp->ai_addr, rp->ai_addrlen);
580  u.ss->ss_family = protocol;
581 
582  ips.push_back(IPAddress(u.ss));
583  }
584  }
585  } catch (AddressException &e) {
586  RETHROW(DNSException, e);
587  }
588 
589  return match;
590 }
591 
592 /*
593 ** vim: noet ts=3 sw=3
594 */