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 }