1 module hio.resolver.ares; 2 3 import std.socket; 4 import std.datetime; 5 import std.format; 6 import std.array; 7 import std.algorithm; 8 import std.typecons; 9 import std.string; 10 11 import std.experimental.logger; 12 13 import core.thread; 14 15 import core.sys.posix.sys.select; 16 import core.sys.posix.netdb; 17 18 import hio.events; 19 import hio.loop; 20 import hio.common; 21 import hio.scheduler; 22 23 import ikod.containers.hashmap; 24 25 public: 26 enum ARES_EMPTY = -1; 27 enum ARES_SUCCESS = 0; 28 enum ARES_ENODATA = 1; 29 enum ARES_EFORMERR = 2; 30 enum ARES_ESERVFAIL = 3; 31 enum ARES_ENOTFOUND = 4; 32 enum ARES_ENOTIMP = 5; 33 enum ARES_EREFUSED = 6; 34 /* Locally generated error codes */ 35 enum ARES_EBADQUERY = 7; 36 enum ARES_EBADNAME = 8; 37 enum ARES_EBADFAMILY = 9; 38 enum ARES_EBADRESP = 10; 39 enum ARES_ECONNREFUSED= 11; 40 enum ARES_ETIMEOUT = 12; 41 enum ARES_EOF = 13; 42 enum ARES_EFILE = 14; 43 enum ARES_ENOMEM = 15; 44 enum ARES_EDESTRUCTION= 16; 45 enum ARES_EBADSTR = 17; 46 47 /* ares_getnameinfo error codes */ 48 enum ARES_EBADFLAGS = 18; 49 50 /* ares_getaddrinfo error codes */ 51 enum ARES_ENONAME = 19; 52 enum ARES_EBADHINTS = 20; 53 54 55 package: 56 57 enum MaxFilesTTL = 60; // 60 secs 58 enum MaxDNSTTL = 24*3600; // 86400 secs 59 enum MaxNegTTL = 30; // negative resolving TTL 60 enum ResolverCacheSize = 512; // cache that number of entries 61 62 struct ares_channeldata; 63 alias ares_channel = ares_channeldata*; 64 alias ares_socket_t = int; 65 66 enum ARES_OPT_FLAGS = (1 << 0); 67 enum ARES_FLAG_STAYOPEN = (1 << 4); 68 enum ARES_SOCKET_BAD = -1; 69 enum ARES_GETSOCK_MAXNUM = 16; 70 71 int ARES_SOCK_READABLE(uint bits,uint num) @safe @nogc nothrow 72 { 73 return (bits & (1<< (num))); 74 } 75 int ARES_SOCK_WRITABLE(uint bits, uint num) @safe @nogc nothrow 76 { 77 return (bits & (1 << ((num) + ARES_GETSOCK_MAXNUM))); 78 } 79 80 struct ares_addrttl { 81 in_addr ipaddr; 82 int ttl; 83 } 84 85 alias ares_in6_addr = ubyte[16]; 86 struct ares_addr6ttl { 87 ares_in6_addr ip6addr; 88 int ttl; 89 } 90 91 enum sec2hnsec = 10_000_000; 92 enum ns_c_in = 1; 93 enum ns_t_a = 1; 94 enum ns_t_aaaa = 28; 95 struct ares_options { 96 int flags; 97 }; 98 static ares_options opts = {flags:ARES_FLAG_STAYOPEN}; 99 private extern(C) 100 { 101 alias ares_host_callback = void function(void *arg, int status, int timeouts, hostent *he); 102 alias ares_callback = void function(void *arg, int status, int timeouts, ubyte *abuf, int alen); 103 int ares_init(ares_channel*) @trusted; 104 int ares_init_options(ares_channel* channel, 105 ares_options *options, 106 int optmask) @trusted; 107 108 void ares_destroy(ares_channel); 109 timeval* ares_timeout(ares_channel channel, timeval *maxtv, timeval *tv); 110 char* ares_strerror(int); 111 void ares_gethostbyname(ares_channel channel, const char *name, int family, ares_host_callback callback, void *arg); 112 int ares_gethostbyname_file(ares_channel channel, const char *name, int family, hostent **host); 113 void ares_free_hostent(hostent *host) @trusted; 114 void ares_query(ares_channel channel, const char *name, int dnsclass, int type, ares_callback callback, void *arg); 115 int ares_parse_a_reply(ubyte *abuf, int alen, hostent **host, ares_addrttl *addrttls, int *naddrttls); 116 int ares_parse_aaaa_reply(ubyte *abuf, int alen, hostent **host, ares_addr6ttl *addrttls, int *naddrttls); 117 int ares_fds(ares_channel, fd_set* reads_fds, fd_set* writes_fds); 118 int ares_getsock(ares_channel channel, ares_socket_t* socks, int numsocks) @trusted @nogc nothrow; 119 120 void ares_process(ares_channel channel, fd_set *read_fds, fd_set *write_fds); 121 void ares_process_fd(ares_channel channel, ares_socket_t read_fd, ares_socket_t write_fd) @trusted @nogc nothrow; 122 void ares_library_init(); 123 void ares_library_cleanup(); 124 } 125 126 private struct DNS4CacheEntry 127 { 128 int _status = ARES_ENODATA; 129 long _timestamp; 130 long _ttl; // in hnsecs 131 uint[] _addresses; // in host order 132 // string toString() 133 // { 134 // return "status: '%s', ttl: %ssec addr: [%(%s,%)]".format(ares_statusString(_status), _ttl/10_000_000, 135 // _addresses.map!(i => fromStringz(inet_ntoa(cast(in_addr)htonl(i))))); 136 // } 137 } 138 139 private struct DNS6CacheEntry 140 { 141 int _status = ARES_ENODATA; 142 long _timestamp; 143 long _ttl; // in hnsecs 144 ubyte[16][] _addresses; 145 } 146 147 private class ResolverCache 148 { 149 private: 150 HashMap!(string, DNS4CacheEntry) cache4; 151 HashMap!(string, DNS6CacheEntry) cache6; 152 package: 153 synchronized auto get4(string host) @trusted 154 { 155 auto f = (cast(HashMap!(string, DNS4CacheEntry))cache4).fetch(host); 156 if ( !f.ok ) 157 { 158 return f; 159 } 160 // check if it is expired 161 immutable now = Clock.currStdTime; 162 auto v = f.value; 163 if ( v._timestamp + v._ttl >= now ) 164 { 165 // it's fresh 166 return f; 167 } 168 // clear expired entry, return empty response 169 (cast(HashMap!(string, DNS4CacheEntry))cache4).remove(host); 170 f.ok = false; 171 f.value = DNS4CacheEntry(); 172 return f; 173 } 174 synchronized void put4(string host, DNS4CacheEntry e) @trusted 175 { 176 (cast(HashMap!(string, DNS4CacheEntry))cache4)[host] = e; 177 } 178 synchronized auto get6(string host) @trusted 179 { 180 auto f = (cast(HashMap!(string, DNS6CacheEntry))cache6).fetch(host); 181 if ( !f.ok ) 182 { 183 return f; 184 } 185 // check if it is expired 186 immutable now = Clock.currStdTime; 187 auto v = f.value; 188 if ( v._timestamp + v._ttl >= now ) 189 { 190 // it's fresh 191 return f; 192 } 193 // clear expired entry, return empty response 194 (cast(HashMap!(string, DNS6CacheEntry))cache4).remove(host); 195 f.ok = false; 196 f.value = DNS6CacheEntry(); 197 return f; 198 } 199 synchronized void put6(string host, DNS6CacheEntry e) @trusted 200 { 201 (cast(HashMap!(string, DNS6CacheEntry))cache6)[host] = e; 202 } 203 synchronized void clear() @trusted 204 { 205 (cast(HashMap!(string, DNS4CacheEntry))cache4).clear; 206 (cast(HashMap!(string, DNS4CacheEntry))cache6).clear; 207 } 208 // XXX add cache cleanup 209 } 210 211 private shared ResolverCache resolverCache; 212 private static Resolver theResolver; 213 214 void _check_init_resolverCache() @safe 215 { 216 if ( resolverCache is null ) 217 { 218 resolverCache = new shared ResolverCache(); 219 } 220 } 221 shared static ~this() 222 { 223 if ( resolverCache ) 224 { 225 resolverCache.clear(); 226 } 227 } 228 void _check_init_theResolver() @safe 229 { 230 if ( theResolver is null ) 231 { 232 theResolver = new Resolver(); 233 } 234 } 235 private static bool exiting; 236 static ~this() 237 { 238 exiting = true; 239 if ( theResolver ) 240 { 241 if (theResolver._ares_channel ) 242 { 243 ares_destroy(theResolver._ares_channel); 244 } 245 theResolver._dns4QueryInprogress.clear(); 246 theResolver._dns6QueryInprogress.clear(); 247 } 248 } 249 // 250 // This is called inside from c-ares when we received server response. 251 // 1. check that we have this quiery id in queryInProgress dictionary 252 // 2. convert server response to dns cache entry and store it in cache 253 // 3. convert cache entrty to resolve result and pass it to callback 254 // 255 private extern(C) void ares_callback4(void *arg, int status, int timeouts, ubyte *abuf, int alen) 256 { 257 if ( exiting ) 258 { 259 return; 260 } 261 int id = cast(int)arg; 262 int naddrttls = 32; 263 ares_addrttl[32] addrttls; 264 hostent* he; 265 long min_ttl = long.max; 266 267 DNS4CacheEntry cache_entry; 268 cache_entry._status = status; 269 cache_entry._ttl = MaxNegTTL * sec2hnsec; 270 271 assert(theResolver); 272 273 auto f = theResolver._dns4QueryInprogress.fetch(id); 274 if ( !f.ok ) 275 { 276 debug(hioresolve) tracef("dns4queryinprogr[%d] not found", id); 277 return; 278 } 279 280 281 theResolver._dns4QueryInprogress.remove(id); 282 283 auto port = f.value._port; 284 auto callback = f.value._callback; 285 if ( f.value._timer ) 286 { 287 debug(hioresolve) tracef("stop timer %s", f.value._timer); 288 getDefaultLoop.stopTimer(f.value._timer); 289 } 290 291 debug(hioresolve) tracef("got callback from ares for INET query id %d, hostname %s", 292 id, f.value._hostname); 293 294 if ( status != ARES_SUCCESS) 295 { 296 callback(ResolverResult4(cache_entry, port)); 297 return; 298 } 299 immutable parse_status = ares_parse_a_reply(abuf, alen, &he, addrttls.ptr, &naddrttls); 300 scope(exit) 301 { 302 if ( he ) ares_free_hostent(he); 303 } 304 cache_entry._status = parse_status; 305 if (parse_status != ARES_SUCCESS) 306 { 307 callback(ResolverResult4(cache_entry, port)); 308 return; 309 } 310 uint[] result; 311 foreach(ref a; addrttls[0..naddrttls]) 312 { 313 //debug(hioresolve) tracef("record %s ttl: %d", a.ipaddr.s_addr, a.ttl); 314 min_ttl = min(a.ttl, min_ttl); 315 auto addr = a.ipaddr.s_addr; 316 result ~= ntohl(addr); 317 } 318 cache_entry._ttl = max(min_ttl, 1) * sec2hnsec; 319 cache_entry._addresses = result; 320 callback(ResolverResult4(cache_entry, port)); 321 } 322 private extern(C) void ares_callback6(void *arg, int status, int timeouts, ubyte *abuf, int alen) 323 { 324 if ( exiting ) 325 { 326 return; 327 } 328 int id = cast(int)arg; 329 int naddrttls = 32; 330 hostent* he; 331 int naddr6ttls = 32; 332 ares_addr6ttl[32] addr6ttls; 333 long min_ttl = long.max; 334 ubyte[16][] result; 335 336 DNS6CacheEntry cache_entry; 337 cache_entry._status = status; 338 cache_entry._ttl = MaxNegTTL * sec2hnsec; 339 340 assert(theResolver); 341 342 auto f = theResolver._dns6QueryInprogress.fetch(id); 343 if ( !f.ok ) 344 { 345 debug(hioresolve) tracef("dns6queryinprogr[%d] not found", id); 346 return; 347 } 348 349 auto port = f.value._port; 350 auto callback = f.value._callback; 351 theResolver._dns6QueryInprogress.remove(id); 352 353 if ( f.value._timer ) getDefaultLoop.stopTimer(f.value._timer); 354 355 debug(hioresolve) tracef("got callback from ares for INET query id %d, hostname %s, status: %s", 356 id, f.value._hostname, resolver_errno(status)); 357 358 if ( status != ARES_SUCCESS) 359 { 360 callback(ResolverResult6(cache_entry, port)); 361 return; 362 } 363 immutable parse_status = ares_parse_aaaa_reply(abuf, alen, &he, addr6ttls.ptr, &naddr6ttls); 364 scope(exit) 365 { 366 if ( he ) ares_free_hostent(he); 367 } 368 cache_entry._status = parse_status; 369 if (parse_status == ARES_SUCCESS) 370 { 371 foreach(ref a; addr6ttls[0..naddr6ttls]) 372 { 373 min_ttl = min(a.ttl, min_ttl); 374 auto addr = a.ip6addr; 375 result ~= addr; 376 } 377 cache_entry._ttl = max(min_ttl,1) * sec2hnsec; 378 cache_entry._addresses = result; 379 } 380 cache_entry._ttl = max(min_ttl, 1) * sec2hnsec; 381 cache_entry._addresses = result; 382 debug(hioresolve) tracef("result: %s", cache_entry); 383 callback(ResolverResult6(cache_entry, port)); 384 } 385 386 struct DNS4QueryInProgress 387 { 388 string _hostname; 389 ushort _port; 390 int _query_id; 391 Timer _timer; 392 void delegate(ResolverResult4) _callback; 393 } 394 395 struct DNS6QueryInProgress 396 { 397 string _hostname; 398 ushort _port; 399 int _query_id; 400 Timer _timer; 401 void delegate(ResolverResult6) _callback; 402 } 403 404 private class Resolver : FileEventHandler 405 { 406 private: 407 408 //hlEvLoop _loop; 409 ares_channel _ares_channel; 410 int _query_id; 411 bool[ARES_GETSOCK_MAXNUM] _in_read; 412 bool[ARES_GETSOCK_MAXNUM] _in_write; 413 ares_socket_t[ARES_GETSOCK_MAXNUM] _sockets; 414 HashMap!(int, DNS4QueryInProgress) _dns4QueryInprogress; 415 HashMap!(int, DNS6QueryInProgress) _dns6QueryInprogress; 416 417 package: 418 419 this() @safe 420 { 421 immutable init_opts = ares_init_options(&_ares_channel, &opts, ARES_OPT_FLAGS); 422 assert(init_opts == ARES_SUCCESS); 423 } 424 void close() 425 { 426 foreach(p;_dns4QueryInprogress.byPair) 427 { 428 Timer t = p.value._timer; 429 if ( t ) 430 { 431 getDefaultLoop.stopTimer(t); 432 } 433 } 434 foreach(p;_dns6QueryInprogress.byPair) 435 { 436 Timer t = p.value._timer; 437 if ( t ) 438 { 439 getDefaultLoop.stopTimer(t); 440 } 441 } 442 if (_ares_channel) 443 { 444 ares_destroy(_ares_channel); 445 } 446 } 447 DNS4CacheEntry resolve4FromFile(string hostname) @safe 448 { 449 // try to resolve from files 450 DNS4CacheEntry dnsInfo; 451 uint[] result; 452 hostent* he; 453 immutable status = () @trusted { 454 return ares_gethostbyname_file(_ares_channel, toStringz(hostname), AF_INET, &he); 455 }(); 456 if ( status == ARES_SUCCESS ) 457 { 458 debug(hioresolve) tracef("he=%s", fromStringz(he.h_name)); 459 debug(hioresolve) tracef("h_length=%X", he.h_length); 460 auto a = he.h_addr_list; 461 () @trusted { 462 while( *a ) 463 { 464 uint addr; 465 for (int i; i < he.h_length; i++) 466 { 467 addr = addr << 8; 468 addr += (*a)[i]; 469 } 470 result ~= addr; 471 a++; 472 } 473 }(); 474 dnsInfo._status = ARES_SUCCESS; 475 dnsInfo._timestamp = Clock.currStdTime; 476 dnsInfo._ttl = MaxFilesTTL * sec2hnsec; // -> hnsecs 477 dnsInfo._addresses = result; 478 } 479 if ( he ) 480 { 481 ares_free_hostent(he); 482 } 483 return dnsInfo; 484 } 485 DNS6CacheEntry resolve6FromFile(string hostname) @safe 486 { 487 // try to resolve from files 488 DNS6CacheEntry dnsInfo; 489 ubyte[16][] result; 490 hostent* he; 491 auto status = () @trusted { 492 return ares_gethostbyname_file(_ares_channel, toStringz(hostname), AF_INET6, &he); 493 }(); 494 if ( status == ARES_SUCCESS ) 495 { 496 debug(hioresolve) tracef("he=%s", fromStringz(he.h_name)); 497 debug(hioresolve) tracef("h_length=%X", he.h_length); 498 auto a = he.h_addr_list; 499 () @trusted 500 { 501 while( *a ) 502 { 503 result ~= *(cast(ubyte[16]*)*a); 504 a++; 505 } 506 }(); 507 dnsInfo._status = ARES_SUCCESS; 508 dnsInfo._timestamp = Clock.currStdTime; 509 dnsInfo._ttl = MaxFilesTTL * sec2hnsec; // -> hnsecs 510 dnsInfo._addresses = result; 511 } 512 if ( he ) 513 { 514 ares_free_hostent(he); 515 } 516 return dnsInfo; 517 } 518 519 private void handleGetSocks(int rc, ares_socket_t[ARES_GETSOCK_MAXNUM] *s) @safe 520 { 521 for(int i; i < ARES_GETSOCK_MAXNUM;i++) 522 { 523 if (ARES_SOCK_READABLE(rc, i) && !_in_read[i]) 524 { 525 debug(hioresolve) tracef("add ares socket %s to IN events", (*s)[i]); 526 getDefaultLoop().startPoll((*s)[i], AppEvent.IN, this); 527 _in_read[i] = true; 528 } 529 else if (!ARES_SOCK_READABLE(rc, i) && _in_read[i]) 530 { 531 debug(hioresolve) tracef("detach ares socket %s from IN events", (*s)[i]); 532 getDefaultLoop.stopPoll((*s)[i], AppEvent.IN); 533 _in_read[i] = false; 534 } 535 if (ARES_SOCK_WRITABLE(rc, i) && !_in_write[i]) 536 { 537 debug(hioresolve) tracef("add ares socket %s to OUT events", (*s)[i]); 538 getDefaultLoop.startPoll((*s)[i], AppEvent.OUT, this); 539 _in_write[i] = true; 540 } 541 else if (!ARES_SOCK_WRITABLE(rc, i) && _in_write[i]) 542 { 543 debug(hioresolve) tracef("detach ares socket %s from OUT events", (*s)[i]); 544 getDefaultLoop.stopPoll((*s)[i], AppEvent.OUT); 545 _in_write[i] = true; 546 } 547 } 548 } 549 private ResolverResult4 resolveSync4(string hostname, ushort port, Duration timeout) 550 { 551 ResolverResult4 result; 552 int id = _query_id++; 553 void callback(ResolverResult4 r) 554 { 555 result = r; 556 } 557 DNS4QueryInProgress qip = { 558 _hostname: hostname, 559 _port: port, 560 _query_id: id, 561 _timer: null, 562 _callback: &callback 563 }; 564 _dns4QueryInprogress[id] = qip; 565 debug(hioresolve) tracef("handle sync resolve for %s", hostname); 566 ares_query(_ares_channel, toStringz(hostname), ns_c_in, ns_t_a, &ares_callback4, cast(void*)id); 567 if ( !result.isEmpty ) 568 { 569 return result; 570 } 571 debug(hioresolve) tracef("wait on select for sync resolve for %s", hostname); 572 int nfds, count; 573 fd_set readers, writers; 574 timeval tv; 575 timeval *tvp; 576 auto started = Clock.currTime; 577 while ( Clock.currTime - started < timeout ) { 578 FD_ZERO(&readers); 579 FD_ZERO(&writers); 580 nfds = ares_fds(theResolver._ares_channel, &readers, &writers); 581 if (nfds == 0) 582 break; 583 tvp = ares_timeout(theResolver._ares_channel, null, &tv); 584 debug(hioresolve) tracef("select sleep for %s", tv); 585 count = select(nfds, &readers, &writers, null, tvp); 586 debug(hioresolve) tracef("select returned %d events %s, %s", count, readers, writers); 587 ares_process(theResolver._ares_channel, &readers, &writers); 588 } 589 auto rc = ares_getsock(_ares_channel, &_sockets[0], ARES_GETSOCK_MAXNUM); 590 debug(hioresolve) tracef("getsocks: 0x%04X, %s", rc, _sockets); 591 // prepare listening for socket events 592 handleGetSocks(rc, &_sockets); 593 return result; 594 } 595 596 private ResolverResult6 resolveSync6(string hostname, ushort port, Duration timeout) 597 { 598 ResolverResult6 result; 599 int id = _query_id++; 600 void callback(ResolverResult6 r) 601 { 602 result = r; 603 } 604 DNS6QueryInProgress qip = { 605 _hostname: hostname, 606 _port: port, 607 _query_id: id, 608 _timer: null, 609 _callback: &callback 610 }; 611 _dns6QueryInprogress[id] = qip; 612 debug(hioresolve) tracef("handle sync resolve for %s", hostname); 613 ares_query(_ares_channel, toStringz(hostname), ns_c_in, ns_t_aaaa, &ares_callback6, cast(void*)id); 614 if ( !result.isEmpty ) 615 { 616 return result; 617 } 618 debug(hioresolve) tracef("wait on select for sync resolve for %s", hostname); 619 int nfds, count; 620 fd_set readers, writers; 621 timeval tv; 622 timeval *tvp; 623 auto started = Clock.currTime; 624 while ( Clock.currTime - started < timeout ) { 625 FD_ZERO(&readers); 626 FD_ZERO(&writers); 627 nfds = ares_fds(theResolver._ares_channel, &readers, &writers); 628 if (nfds == 0) 629 break; 630 tvp = ares_timeout(theResolver._ares_channel, null, &tv); 631 debug(hioresolve) tracef("select sleep for %s", tv); 632 count = select(nfds, &readers, &writers, null, tvp); 633 debug(hioresolve) tracef("select returned %d events %s, %s", count, readers, writers); 634 ares_process(theResolver._ares_channel, &readers, &writers); 635 } 636 auto rc = ares_getsock(_ares_channel, &_sockets[0], ARES_GETSOCK_MAXNUM); 637 debug(hioresolve) tracef("getsocks: 0x%04X, %s", rc, _sockets); 638 // prepare listening for socket events 639 handleGetSocks(rc, &_sockets); 640 return result; 641 } 642 643 void send_IN_A_Query(string hostname, ushort port, 644 void delegate(ResolverResult4) @safe callback, 645 Duration timeout) @safe 646 { 647 int id = _query_id++; 648 Timer t = new Timer(timeout,(AppEvent e) @safe 649 { 650 // handle timeout 651 // remove this query from the InProgress and call calback 652 theResolver._dns4QueryInprogress.remove(id); 653 ResolverResult4 result = ResolverResult4(ARES_ETIMEOUT, new InternetAddress[](0)); 654 callback(result); 655 }); 656 DNS4QueryInProgress qip = { 657 _hostname: hostname, 658 _port: port, 659 _query_id: id, 660 _timer: t, 661 _callback: callback 662 }; 663 _dns4QueryInprogress[id] = qip; 664 getDefaultLoop.startTimer(t); 665 () @trusted 666 { 667 ares_query(_ares_channel, toStringz(hostname), ns_c_in, ns_t_a, &ares_callback4, cast(void*)id); 668 }(); 669 } 670 void send_IN_AAAA_Query(string hostname, ushort port, 671 void delegate(ResolverResult6) @safe callback, 672 Duration timeout) @safe 673 { 674 int id = _query_id++; 675 Timer t = new Timer(timeout,(AppEvent e) @safe 676 { 677 // handle timeout 678 // remove this query from the InProgress and call calback 679 theResolver._dns6QueryInprogress.remove(id); 680 ResolverResult6 result = ResolverResult6(ARES_ETIMEOUT, new Internet6Address[](0)); 681 callback(result); 682 }); 683 DNS6QueryInProgress qip = { 684 _hostname: hostname, 685 _port: port, 686 _query_id: id, 687 _timer: t, 688 _callback: callback 689 }; 690 _dns6QueryInprogress[id] = qip; 691 getDefaultLoop.startTimer(t); 692 () @trusted 693 { 694 ares_query(_ares_channel, toStringz(hostname), ns_c_in, ns_t_aaaa, &ares_callback6, cast(void*)id); 695 }(); 696 } 697 698 public: 699 700 override void eventHandler(int f, AppEvent ev) 701 { 702 debug(hioresolve) tracef("handler: %d, %s", f, ev); 703 int socket_index; 704 ares_socket_t rs = ARES_SOCKET_BAD, ws = ARES_SOCKET_BAD; 705 706 if (f==_sockets[0]) 707 { 708 // 99.99 %% case 709 socket_index = 0; 710 } 711 else 712 { 713 for(socket_index=1;socket_index<ARES_GETSOCK_MAXNUM;socket_index++) 714 if (f==_sockets[socket_index]) 715 { 716 break; 717 } 718 } 719 // we'll got range violation in case we didn't find socket 720 // ares_process() can close socket for its own reasons 721 // so we have to detach descriptor. 722 // If we do not - we will have closed socket in polling system. 723 auto loop = getDefaultLoop(); 724 if (ev & AppEvent.OUT) 725 { 726 loop.stopPoll(f, AppEvent.OUT); 727 loop.detach(f); 728 _in_write[socket_index] = false; 729 ws = f; 730 } 731 if (ev & AppEvent.IN) 732 { 733 loop.stopPoll(f, AppEvent.IN); 734 loop.detach(f); 735 _in_read[socket_index] = false; 736 rs = f; 737 } 738 debug(hioresolve) tracef("handle event on %s,%s", rs, ws); 739 ares_process_fd(_ares_channel, rs, ws); 740 auto rc = ares_getsock(_ares_channel, &_sockets[0], ARES_GETSOCK_MAXNUM); 741 debug(hioresolve) tracef("getsocks: 0x%04X, %s", rc, _sockets); 742 // prepare listening for socket events 743 handleGetSocks(rc, &_sockets); 744 } 745 override string describe() 746 { 747 return "newares"; 748 } 749 } 750 751 ResolverResult4 tryLocalResolve4(string host, short port) @safe 752 { 753 _check_init_resolverCache(); 754 _check_init_theResolver(); 755 ResolverResult4 result; 756 757 int addr; 758 int p = () @trusted 759 { 760 return inet_pton(AF_INET, toStringz(host), &addr); 761 }(); 762 763 if (p > 0) 764 { 765 debug(hioresolve) tracef("address converetd from %s", host, p); 766 return ResolverResult4(ARES_SUCCESS, [new InternetAddress(ntohl(addr), port)]); 767 } 768 // string is not addr, check cache 769 assert(resolverCache); 770 auto cache_entry = resolverCache.get4(host); 771 if ( cache_entry.ok ) 772 { 773 debug(hioresolve) tracef("resolved %s from cache", host); 774 return ResolverResult4(cache_entry.value, port); 775 } 776 // entry not in cache, try resolve from file 777 assert(theResolver); 778 auto file_entry = theResolver.resolve4FromFile(host); 779 if ( file_entry._status == ARES_SUCCESS ) 780 { 781 resolverCache.put4(host, file_entry); 782 return ResolverResult4(file_entry, port); 783 } 784 return result; 785 } 786 ResolverResult6 tryLocalResolve6(string host, short port) @safe 787 { 788 _check_init_resolverCache(); 789 _check_init_theResolver(); 790 ResolverResult6 result; 791 792 ubyte[16] addr; 793 int p = () @trusted 794 { 795 return inet_pton(AF_INET6, toStringz(host), addr.ptr); 796 }(); 797 798 if (p > 0) 799 { 800 debug(hioresolve) tracef("address converetd from %s", host, p); 801 return ResolverResult6(ARES_SUCCESS, [new Internet6Address(addr, port)]); 802 } 803 // string is not addr, check cache 804 assert(resolverCache); 805 auto cache_entry = resolverCache.get6(host); 806 if ( cache_entry.ok ) 807 { 808 debug(hioresolve) tracef("resolved %s from cache", host); 809 return ResolverResult6(cache_entry.value, port); 810 } 811 // entry not in cache, try resolve from file 812 assert(theResolver); 813 auto file_entry = theResolver.resolve6FromFile(host); 814 if ( file_entry._status == ARES_SUCCESS ) 815 { 816 resolverCache.put6(host, file_entry); 817 return ResolverResult6(file_entry, port); 818 } 819 return result; 820 } 821 822 public: 823 /// 824 string resolver_errno(int r) @trusted 825 { 826 import std.conv: to; 827 return to!string(ares_strerror(r)); 828 } 829 830 /// 831 struct ResolverResult4 832 { 833 private: 834 int _status = ARES_EMPTY; 835 Address[] _addresses; 836 837 public: 838 this(int s, Address[] a) @safe 839 { 840 _status = s; 841 _addresses = a; 842 } 843 this(DNS4CacheEntry entry, ushort port) @safe 844 { 845 _status = entry._status; 846 _addresses = entry._addresses 847 .map!(a => new InternetAddress(a, port)) 848 .map!(a => cast(Address)a).array; 849 } 850 bool isEmpty() @safe 851 { 852 return _status == ARES_EMPTY; 853 } 854 auto status() inout @safe @nogc 855 { 856 return _status; 857 } 858 auto addresses() inout @safe @nogc 859 { 860 return _addresses; 861 } 862 } 863 /// 864 struct ResolverResult6 865 { 866 private: 867 int _status = ARES_EMPTY; 868 Address[] _addresses; 869 870 public: 871 this(int s, Address[] a) @safe 872 { 873 _status = s; 874 _addresses = a; 875 } 876 this(DNS6CacheEntry entry, ushort port) @safe 877 { 878 _status = entry._status; 879 _addresses = entry._addresses 880 .map!(a => new Internet6Address(a, port)) 881 .map!(a => cast(Address)a).array; 882 } 883 bool isEmpty() @safe 884 { 885 return _status == ARES_EMPTY; 886 } 887 auto status() inout @safe @nogc 888 { 889 return _status; 890 } 891 auto addresses() inout @safe @nogc 892 { 893 return _addresses; 894 } 895 } 896 /// 897 ResolverResult4 hio_gethostbyname(string host, ushort port = InternetAddress.PORT_ANY, Duration timeout = 5.seconds) 898 { 899 bool yielded; 900 ResolverResult4 cb_result, result; 901 902 auto fiber = Fiber.getThis(); 903 if ( fiber is null ) 904 { 905 result = tryLocalResolve4(host, port); 906 if ( !result.isEmpty ) 907 { 908 return result; 909 } 910 return theResolver.resolveSync4(host, port, timeout); 911 } 912 913 // resolve using ares and/or loop 914 debug(hioresolve) tracef("resolve %s using ares", host); 915 void resolveCallback(ResolverResult4 r) @safe 916 { 917 debug(hioresolve) tracef("received result %s", r); 918 cb_result = r; 919 if ( yielded ) 920 { 921 debug(hioresolve) tracef("calling back with %s", cb_result); 922 () @trusted{fiber.call();}(); 923 } 924 } 925 result = hio_gethostbyname(host, &resolveCallback, port, timeout); 926 if ( !cb_result.isEmpty ) 927 { 928 return cb_result; 929 } 930 if ( !result.isEmpty ) 931 { 932 return result; 933 } 934 // wait for callback 935 debug(hioresolve) tracef("yeilding for resolving"); 936 yielded = true; 937 Fiber.yield(); 938 debug(hioresolve) tracef("returned from wait"); 939 return cb_result; 940 } 941 /// 942 ResolverResult4 hio_gethostbyname(C)(string hostname, C callback, ushort port = InternetAddress.PORT_ANY, Duration timeout = 5.seconds) @safe 943 { 944 ResolverResult4 result = tryLocalResolve4(hostname, port); 945 if ( !result.isEmpty ) 946 { 947 return result; 948 } 949 // local methods failed, 950 // prepare request and send it, wait for response 951 void handler(ResolverResult4 r) @safe 952 { 953 result = r; 954 callback(r); 955 } 956 theResolver.send_IN_A_Query(hostname, port, &handler, timeout); 957 auto rc = ares_getsock(theResolver._ares_channel, &theResolver._sockets[0], ARES_GETSOCK_MAXNUM); 958 debug(hioresolve) tracef("getsocks: 0x%04X, %s", rc, theResolver._sockets); 959 // prepare listening for socket events 960 theResolver.handleGetSocks(rc, &theResolver._sockets); 961 return result; 962 } 963 /// 964 ResolverResult6 hio_gethostbyname6(string host, ushort port = InternetAddress.PORT_ANY, Duration timeout = 5.seconds) 965 { 966 bool yielded; 967 ResolverResult6 result; 968 969 auto fiber = Fiber.getThis(); 970 if ( fiber is null ) 971 { 972 result = tryLocalResolve6(host, port); 973 if ( !result.isEmpty ) 974 { 975 return result; 976 } 977 return theResolver.resolveSync6(host, port, timeout); 978 } 979 980 // resolve using ares and/or loop 981 debug(hioresolve) tracef("resolve %s using ares", host); 982 void resolveCallback(ResolverResult6 r) @safe 983 { 984 debug(hioresolve) tracef("received result %s", r); 985 result = r; 986 if ( yielded ) 987 { 988 () @trusted{fiber.call();}(); 989 } 990 } 991 result = hio_gethostbyname6(host, &resolveCallback, port, timeout); 992 if ( result.isEmpty ) 993 { 994 // wait for callback 995 debug(hioresolve) tracef("yeilding for resolving"); 996 yielded = true; 997 Fiber.yield(); 998 debug(hioresolve) tracef("returned from wait"); 999 } 1000 return result; 1001 } 1002 /// 1003 ResolverResult6 hio_gethostbyname6(C)(string hostname, C callback, ushort port = InternetAddress.PORT_ANY, Duration timeout = 5.seconds) @safe 1004 { 1005 ResolverResult6 result = tryLocalResolve6(hostname, port); 1006 if ( !result.isEmpty ) 1007 { 1008 return result; 1009 } 1010 // local methods failed, 1011 // prepare request and send it, wait for response 1012 void handler(ResolverResult6 r) @safe 1013 { 1014 result = r; 1015 debug(hioresolve) tracef("callback 6, %s", r); 1016 callback(r); 1017 } 1018 debug(hioresolve) tracef("send AAAA query for %s", hostname); 1019 theResolver.send_IN_AAAA_Query(hostname, port, &handler, timeout); 1020 auto rc = ares_getsock(theResolver._ares_channel, &theResolver._sockets[0], ARES_GETSOCK_MAXNUM); 1021 debug(hioresolve) tracef("getsocks: 0x%04X, %s", rc, theResolver._sockets); 1022 // prepare listening for socket events 1023 theResolver.handleGetSocks(rc, &theResolver._sockets); 1024 return result; 1025 } 1026 1027 unittest 1028 { 1029 infof("=== Testing resolver shared cache"); 1030 _check_init_resolverCache(); 1031 assert(resolverCache ! is null); 1032 DNS4CacheEntry e; 1033 e._ttl = 100000; 1034 e._timestamp = Clock.currStdTime; 1035 resolverCache.put4("testhost", e); 1036 auto v = resolverCache.get4("testhost"); 1037 assert(v.ok); 1038 resolverCache.clear(); 1039 } 1040 1041 unittest 1042 { 1043 globalLogLevel = LogLevel.info; 1044 info("=== Testing resolver INET4 sync/no loop ==="); 1045 auto r = hio_gethostbyname("localhost"); 1046 assert(r.status == 0); 1047 debug(hioresolve) tracef("%s", r); 1048 r = hio_gethostbyname("8.8.8.8"); 1049 assert(r.status == 0); 1050 debug(hioresolve) tracef("%s", r); 1051 r = hio_gethostbyname("dlang.org"); 1052 assert(r.status == 0); 1053 debug(hioresolve) tracef("%s", r); 1054 r = hio_gethostbyname("......."); 1055 assert(r.status != 0); 1056 tracef("status: %s", resolver_errno(r.status)); 1057 r = hio_gethostbyname("......."); 1058 assert(r.status != 0); 1059 r = hio_gethostbyname("iuytkjhcxbvkjhgfaksdjf"); 1060 assert(r.status != 0); 1061 debug(hioresolve) tracef("%s", r); 1062 debug(hioresolve) tracef("status: %s", resolver_errno(r.status)); 1063 r = hio_gethostbyname("iuytkjhcxbvkjhgfaksdjf"); 1064 assert(r.status != 0); 1065 debug(hioresolve) tracef("%s", r); 1066 debug(hioresolve) tracef("status: %s", resolver_errno(r.status)); 1067 1068 DNS4CacheEntry e; 1069 e._status = ARES_SUCCESS; 1070 e._ttl = 1000000; 1071 e._timestamp = Clock.currStdTime; 1072 e._addresses = [0x7f_00_00_01]; 1073 resolverCache.put4("testhost", e); 1074 1075 // check resolve "127.0.0.1" 1076 r = hio_gethostbyname("127.0.0.1", InternetAddress.PORT_ANY, 5.seconds); 1077 assert(r._status == ARES_SUCCESS); 1078 assert(r._addresses.length >= 1); 1079 Address localhost = getAddress("127.0.0.1", InternetAddress.PORT_ANY)[0]; 1080 Address resolved = r._addresses[0]; 1081 assert(resolved.addressFamily == AF_INET); 1082 assert((cast(sockaddr_in*)resolved.name).sin_addr == (cast(sockaddr_in*)localhost.name).sin_addr); 1083 globalLogLevel = LogLevel.info; 1084 } 1085 1086 unittest 1087 { 1088 import std.array: array; 1089 globalLogLevel = LogLevel.info; 1090 info("=== Testing resolver INET4 async/callback ==="); 1091 auto app(string hostname) 1092 { 1093 int status; 1094 Address[] addresses; 1095 Fiber fiber = Fiber.getThis(); 1096 bool done; 1097 bool yielded; 1098 1099 void cb(ResolverResult4 r) @trusted 1100 { 1101 status = r.status; 1102 addresses = r.addresses; 1103 done = true; 1104 debug(hioresolve) tracef("resolve for %s: %s, %s", hostname, fromStringz(ares_strerror(status)), r); 1105 if (yielded) 1106 { 1107 fiber.call(); 1108 } 1109 } 1110 auto r = hio_gethostbyname(hostname, &cb); 1111 if (r.isEmpty) 1112 { 1113 yielded = true; 1114 Fiber.yield(); 1115 } 1116 return r.addresses; 1117 } 1118 auto names = [ 1119 "localhost", 1120 "localhost", // should get cached 1121 "dlang.org", 1122 "google.com", 1123 "..", 1124 "dlang.org", // should get cached 1125 "..", // should get cached negative 1126 ]; 1127 auto tasks = names.map!(n => task(&app, n)).array; 1128 try 1129 { 1130 tasks.each!(t => t.start); 1131 getDefaultLoop.run(2.seconds); 1132 assert(tasks.all!(t => t.ready)); 1133 } 1134 catch (Throwable e) 1135 { 1136 errorf("%s", e); 1137 } 1138 // theResolver.close(); 1139 // theResolver = null; 1140 } 1141 1142 unittest 1143 { 1144 info("=== Testing resolver INET4 sync/task ==="); 1145 globalLogLevel = LogLevel.info; 1146 DNS4CacheEntry e; 1147 e._status = ARES_SUCCESS; 1148 e._ttl = 1000000; 1149 e._timestamp = Clock.currStdTime; 1150 e._addresses = [0x7f_00_00_01]; 1151 resolverCache.put4("testhost", e); 1152 App({ 1153 // test cached resolve 1154 auto r = hio_gethostbyname("testhost", 12345); 1155 assert(r.status == ARES_SUCCESS); 1156 assert(r.addresses.length == 1); 1157 assert((cast(InternetAddress)r.addresses[0]).addr == 0x7f000001); 1158 assert((cast(InternetAddress)r.addresses[0]).port == 12345); 1159 // 1160 auto lh = hio_gethostbyname("localhost"); 1161 1162 auto resolve(string hostname) 1163 { 1164 auto r = hio_gethostbyname(hostname, 12345); 1165 debug(hioresolve) tracef("%s -> %s", hostname, r); 1166 return r; 1167 } 1168 auto names = [ 1169 "dlang.org", 1170 "google.com", 1171 "cloudflare.com", 1172 "a.root-servers.net.", 1173 "b.root-servers.net.", 1174 "c.root-servers.net.", 1175 "d.root-servers.net.", 1176 "e.root-servers.net.", 1177 "...", 1178 "127.0.0.1" 1179 ]; 1180 auto tasks = names.map!(n => task(&resolve, n)).array; 1181 tasks.each!(t => t.start); 1182 tasks.each!(t => t.wait); 1183 }); 1184 globalLogLevel = LogLevel.info; 1185 } 1186 1187 unittest 1188 { 1189 infof("=== Testing resolver timeouts ==="); 1190 globalLogLevel = LogLevel.info; 1191 auto v = App({ 1192 resolverCache.clear(); 1193 auto resolve(string hostname) 1194 { 1195 auto r = hio_gethostbyname(hostname, 12345, 3.msecs); 1196 debug(hioresolve) tracef("%s -> %s", hostname, r); 1197 return r; 1198 } 1199 auto names = [ 1200 "dlang.org", 1201 "google.com", 1202 "cloudflare.com", 1203 "a.root-servers.net.", 1204 "b.root-servers.net.", 1205 "c.root-servers.net.", 1206 "d.root-servers.net.", 1207 "e.root-servers.net.", 1208 "...", 1209 "127.0.0.1" 1210 ]; 1211 auto tasks = names.map!(n => task(&resolve, n)).array; 1212 tasks.each!(t => t.start); 1213 tasks.each!(t => t.wait); 1214 return tasks.map!"a.result".array; 1215 }); 1216 globalLogLevel = LogLevel.info; 1217 } 1218 1219 unittest 1220 { 1221 globalLogLevel = LogLevel.info; 1222 info("=== Testing resolver INET6 ares/sync ==="); 1223 // auto r = resolver.gethostbyname6("ip6-localhost"); 1224 // assert(r.status == 0); 1225 // debug(hioresolve) tracef("%s", r); 1226 // r = resolver.gethostbyname6("8.8.8.8"); 1227 // assert(r.status == 0); 1228 // debug(hioresolve) tracef("%s", r); 1229 auto r = hio_gethostbyname6("dlang.org"); 1230 assert(r.status == 0); 1231 tracef("%s", r); 1232 r = hio_gethostbyname6("......."); 1233 assert(r.status != 0); 1234 tracef("status: %s", resolver_errno(r.status)); 1235 r = hio_gethostbyname6("......."); 1236 assert(r.status != 0); 1237 globalLogLevel = LogLevel.info; 1238 } 1239 unittest 1240 { 1241 import std.array: array; 1242 globalLogLevel = LogLevel.info; 1243 info("=== Testing resolver INET6 ares/async ==="); 1244 auto app(string hostname) 1245 { 1246 int status; 1247 bool yielded; 1248 1249 Internet6Address[] addresses; 1250 Fiber fiber = Fiber.getThis(); 1251 1252 void cb(ResolverResult6 r) @trusted 1253 { 1254 status = r.status; 1255 addresses = r.addresses.map!(a => cast(Internet6Address)a).array; 1256 if (yielded) 1257 { 1258 fiber.call(); 1259 } 1260 } 1261 auto r = hio_gethostbyname6(hostname, &cb); 1262 if (r.isEmpty) 1263 { 1264 yielded = true; 1265 Fiber.yield(); 1266 } 1267 return addresses; 1268 } 1269 auto names = ["dlang.org", "google.com", "cloudflare.com", ".."]; 1270 auto tasks = names.map!(n => task(&app, n)).array; 1271 try 1272 { 1273 tasks.each!(t => t.start); 1274 getDefaultLoop.run(2.seconds); 1275 assert(tasks.all!(t => t.ready)); 1276 } 1277 catch (Throwable e) 1278 { 1279 errorf("%s", e); 1280 } 1281 }