1 module hio.resolver.ares;
2 
3 import core.sys.posix.sys.select;
4 import core.sys.posix.netdb;
5 
6 import std.socket;
7 import std.string;
8 import std.typecons;
9 import std.algorithm;
10 import std.traits;
11 import std.bitmanip;
12 
13 import std.experimental.logger;
14 
15 import core.thread;
16 
17 import hio.events;
18 import hio.loop;
19 import hio.common;
20 import hio.scheduler;
21 
22 struct ares_channeldata;
23 alias ares_channel = ares_channeldata*;
24 alias ares_socket_t = int;
25 
26 enum ARES_SOCKET_BAD = -1;
27 enum ARES_GETSOCK_MAXNUM = 16;
28 
29 int  ARES_SOCK_READABLE(uint bits,uint num) @safe @nogc nothrow
30 {
31     return (bits & (1<< (num)));
32 }
33 int ARES_SOCK_WRITABLE(uint bits, uint num) @safe @nogc nothrow
34 {
35     return (bits & (1 << ((num) + ARES_GETSOCK_MAXNUM)));
36 }
37 
38 enum ARES_SUCCESS = 0;
39 enum ARES_ENODATA = 1;
40 enum ARES_EFORMERR = 2;
41 enum ARES_ESERVFAIL = 3;
42 enum ARES_ENOTFOUND = 4;
43 enum ARES_ENOTIMP   = 5;
44 enum ARES_EREFUSED  = 6;
45 
46 /* Locally generated error codes */
47 enum ARES_EBADQUERY = 7;
48 enum ARES_EBADNAME  = 8;
49 enum ARES_EBADFAMILY  = 9;
50 enum ARES_EBADRESP    = 10;
51 enum ARES_ECONNREFUSED= 11;
52 enum ARES_ETIMEOUT    = 12;
53 enum ARES_EOF         = 13;
54 enum ARES_EFILE       = 14;
55 enum ARES_ENOMEM      = 15;
56 enum ARES_EDESTRUCTION= 16;
57 enum ARES_EBADSTR     = 17;
58 
59 /* ares_getnameinfo error codes */
60 enum ARES_EBADFLAGS   =  18;
61 
62 /* ares_getaddrinfo error codes */
63 enum ARES_ENONAME     =  19;
64 enum ARES_EBADHINTS   =  20;
65 
66 extern(C)
67 {
68     alias    ares_host_callback = void function(void *arg, int status, int timeouts, hostent *he);
69     int      ares_init(ares_channel*);
70     void     ares_destroy(ares_channel);
71     timeval* ares_timeout(ares_channel channel, timeval *maxtv, timeval *tv);
72     char*    ares_strerror(int);
73     void     ares_gethostbyname(ares_channel channel, const char *name, int family, ares_host_callback callback, void *arg);
74 
75     int      ares_fds(ares_channel, fd_set* reads_fds, fd_set* writes_fds);
76     int      ares_getsock(ares_channel channel, ares_socket_t* socks, int numsocks) @trusted @nogc nothrow;
77 
78     void     ares_process(ares_channel channel, fd_set *read_fds, fd_set *write_fds);
79     void     ares_process_fd(ares_channel channel, ares_socket_t read_fd, ares_socket_t write_fd) @trusted @nogc nothrow;
80     void     ares_library_init();
81     void     ares_library_cleanup();
82 }
83 
84 alias ResolverCallbackFunction = void function(int status, uint[] addresses);
85 alias ResolverCallbackDelegate = void delegate(int status, uint[] addresses);
86 alias ResolverCallbackFunction6 = void function(int status, ubyte[16][]* addresses);
87 alias ResolverCallbackDelegate6 = void delegate(int status, ubyte[16][]* addresses);
88 
89 alias ResolverResult4 = Tuple!(int, "status", InternetAddress[], "addresses");
90 alias ResolverResult6 = Tuple!(int, "status", Internet6Address[], "addresses");
91 alias ResolverResult = ResolverResult4;
92 
93 shared static this()
94 {
95     ares_library_init();
96 }
97 shared static ~this()
98 {
99     ares_library_cleanup();
100 }
101 
102 package static Resolver theResolver;
103 static this() {
104     theResolver = new Resolver();
105 }
106 
107 public auto hio_gethostbyname(string host, ushort port=InternetAddress.PORT_ANY)
108 {
109     return theResolver.gethostbyname(host, port);
110 }
111 
112 public auto hio_gethostbyname6(string host, ushort port=Internet6Address.PORT_ANY)
113 {
114     return theResolver.gethostbyname6(host, port);
115 }
116 ///
117 auto ares_statusString(int status)
118 {
119     return fromStringz(ares_strerror(status));
120 }
121 
122 package class Resolver: FileEventHandler
123 {
124     private
125     {
126         ares_channel                        _ares_channel;
127         hlEvLoop                            _loop;
128         bool[ARES_GETSOCK_MAXNUM]           _in_read;
129         bool[ARES_GETSOCK_MAXNUM]           _in_write;
130         ares_socket_t[ARES_GETSOCK_MAXNUM]  _sockets;
131         int                                 _id;
132         ResolverCallbackFunction[int]       _cbFunctions;
133         ResolverCallbackDelegate[int]       _cbDelegates;
134         ResolverCallbackFunction6[int]      _cb6Functions;
135         ResolverCallbackDelegate6[int]      _cb6Delegates;
136     }
137 
138     this()
139     {
140         immutable init_res = ares_init(&_ares_channel);
141         assert(init_res == ARES_SUCCESS, "Can't initialise ares.");
142     }
143     ~this()
144     {
145         close();
146     }
147     void close()
148     {
149         if (_ares_channel)
150         {
151             ares_destroy(_ares_channel);
152         }
153     }
154 
155     private ares_host_callback host_callback4 = (void *arg, int status, int timeouts, hostent* he)
156     {
157         int id = cast(int)arg;
158         uint[] result;
159         debug tracef("got callback from ares s:\"%s\" t:%d h:%s, id: %d", fromStringz(ares_strerror(status)), timeouts, he, id);
160 
161         Resolver resolver = theResolver;
162         if (status == 0 && he !is null && he.h_addrtype == AF_INET)
163         {
164             debug tracef("he=%s", fromStringz(he.h_name));
165             debug tracef("h_length=%X", he.h_length);
166             auto a = he.h_addr_list;
167             while( *a )
168             {
169                 uint addr;
170                 for (int i; i < he.h_length; i++)
171                 {
172                     addr = addr << 8;
173                     addr += (*a)[i];
174                 }
175                 result ~= addr;
176                 a++;
177             }
178         }
179         else
180         {
181             debug tracef("callback failed: %d", status);
182         }
183         auto cbf = id in resolver._cbFunctions;
184         auto cbd = id in resolver._cbDelegates;
185         debug tracef("cbf: %s, cbd: %s", cbd, cbf);
186         if (cbf !is null)
187         {
188             auto cb = *cbf;
189             resolver._cbFunctions.remove(id);
190             cb(status, result);
191         }
192         else if (cbd !is null)
193         {
194             auto cb = *cbd;
195             resolver._cbDelegates.remove(id);
196             cb(status, result);
197         }
198     };
199 
200     private ares_host_callback host_callback6 = (void *arg, int status, int timeouts, hostent* he)
201     {
202         int id = cast(int)arg;
203         ubyte[16][] result;
204         debug tracef("got callback from ares s:\"%s\" t:%d h:%s, id: %d", fromStringz(ares_strerror(status)), timeouts, he, id);
205 
206         Resolver resolver = theResolver;
207         if (status == 0 && he !is null && he.h_addrtype == AF_INET6)
208         {
209             if (he is null)
210             {
211                 return;
212             }
213             debug tracef("he=%s", fromStringz(he.h_name));
214             debug tracef("h_length=%X", he.h_length);
215             auto a = he.h_addr_list;
216             while(*a)
217             {
218                 //ubyte[16] *addr = cast(ubyte[16]*)*a;
219                 result ~= *(cast(ubyte[16]*)*a);//new Internet6Address(*addr, Internet6Address.PORT_ANY);
220                 a++;
221             }
222         }
223         else
224         {
225             debug tracef("callback failed: %d", status);
226         }
227         auto cbf = id in resolver._cb6Functions;
228         auto cbd = id in resolver._cb6Delegates;
229         debug tracef("cbf: %s, cbd: %s", cbd, cbf);
230         if (cbf !is null)
231         {
232             auto cb = *cbf;
233             resolver._cb6Functions.remove(id);
234             cb(status, &result);
235         }
236         else if (cbd !is null)
237         {
238             auto cb = *cbd;
239             resolver._cb6Delegates.remove(id);
240             cb(status, &result);
241         }
242     };
243     ///
244     /// gethostbyname which wait for result (can be called w/o eventloop or inside of task)
245     ///
246     ResolverResult4 gethostbyname(string hostname, ushort port=InternetAddress.PORT_ANY)
247     {
248         int                 status, id;
249         InternetAddress[]   adresses;
250         bool                done;
251 
252         _id++;
253 
254         debug tracef("start resolving %s", hostname);
255         auto fiber = Fiber.getThis();
256         if (fiber is null)
257         {
258             void cba(int s, uint[] a)
259             {
260                 status = s;
261                 foreach(ia;a)
262                 {
263                     adresses ~= new InternetAddress(ia, port);
264                 }
265                 done = true;
266                 _cbDelegates.remove(id);
267                 debug tracef("resolve for %s: %s, %s", hostname, ares_strerror(s), a);
268             }
269             id = _id;
270             _cbDelegates[_id] = &cba;
271             ares_gethostbyname(_ares_channel, toStringz(hostname), AF_INET, host_callback4, cast(void*)_id);
272             if ( done )
273             {
274                 // resolved from files
275                 debug tracef("return ready result");
276                 return ResolverResult(status, adresses);
277             }
278             // called without loop/callback, we can and have to block
279             int nfds, count;
280             fd_set readers, writers;
281             timeval tv;
282             timeval *tvp;
283 
284             while (!done) {
285                 FD_ZERO(&readers);
286                 FD_ZERO(&writers);
287                 nfds = ares_fds(_ares_channel, &readers, &writers);
288                 if (nfds == 0)
289                     break;
290                 tvp = ares_timeout(_ares_channel, null, &tv);
291                 count = select(nfds, &readers, &writers, null, tvp);
292                 ares_process(_ares_channel, &readers, &writers);
293             }
294             return ResolverResult(status, adresses);
295         }
296         else
297         {
298             bool yielded;
299             void cbb(int s, uint[] a)
300             {
301                 status = s;
302                 foreach (ia; a) {
303                     adresses ~= new InternetAddress(ia, port);
304                 }
305                 done = true;
306                 debug tracef("resolve for %s: %s, %s, yielded: %s", hostname, ares_strerror(s), a, yielded);
307                 if (yielded) fiber.call();
308                 debug tracef("done");
309             }
310             if (!_loop)
311             {
312                 _loop = getDefaultLoop();
313             }
314             id = _id;
315             _cbDelegates[_id] = &cbb;
316             ares_gethostbyname(_ares_channel, toStringz(hostname), AF_INET, host_callback4, cast(void*)_id);
317             if ( done )
318             {
319                 // resolved from files
320                 debug tracef("return ready result");
321                 return ResolverResult(status, adresses);
322             }
323             auto rc = ares_getsock(_ares_channel, &_sockets[0], ARES_GETSOCK_MAXNUM);
324             debug tracef("getsocks: 0x%04X, %s", rc, _sockets);
325             // prepare listening for socket events
326             handleGetSocks(rc, &_sockets);
327             yielded = true;
328             Fiber.yield();
329             return ResolverResult(status, adresses);
330         }
331     }
332     ///
333     /// gethostbyname which wait for result (can be called w/o eventloop or inside of task)
334     ///
335     ResolverResult6 gethostbyname6(string hostname, ushort port = Internet6Address.PORT_ANY)
336     {
337         int                 status, id;
338         Internet6Address[]  addresses;
339         bool                done;
340 
341         _id++;
342 
343         debug tracef("start resolving %s", hostname);
344         auto fiber = Fiber.getThis();
345         if (fiber is null)
346         {
347             void cba(int s, ubyte[16][] *a)
348             {
349                 status = s;
350                 foreach(ia;*a)
351                 {
352                     addresses ~= new Internet6Address(ia, port);
353                 }
354                 done = true;
355                 _cb6Delegates.remove(id);
356                 debug tracef("resolve for %s: %s, %s", hostname, ares_strerror(s), a);
357             }
358             id = _id;
359             _cb6Delegates[_id] = &cba;
360             ares_gethostbyname(_ares_channel, toStringz(hostname), AF_INET6, host_callback6, cast(void*)_id);
361             if ( done )
362             {
363                 // resolved from files
364                 debug tracef("return ready result");
365                 return ResolverResult6(status, addresses);
366             }
367             // called without loop/callback, we can and have to block
368             int nfds, count;
369             fd_set readers, writers;
370             timeval tv;
371             timeval *tvp;
372 
373             while (!done) {
374                 FD_ZERO(&readers);
375                 FD_ZERO(&writers);
376                 nfds = ares_fds(_ares_channel, &readers, &writers);
377                 if (nfds == 0)
378                     break;
379                 tvp = ares_timeout(_ares_channel, null, &tv);
380                 count = select(nfds, &readers, &writers, null, tvp);
381                 ares_process(_ares_channel, &readers, &writers);
382             }
383             return ResolverResult6(status, addresses);
384         }
385         else
386         {
387             bool yielded;
388             void cbb(int s, ubyte[16][] *a)
389             {
390                 status = s;
391                 foreach (ia; *a)
392                 {
393                     addresses ~= new Internet6Address(ia, port);
394                 }
395                 done = true;
396                 debug tracef("resolve for %s: %s, %s, yielded: %s", hostname, ares_strerror(s), a, yielded);
397                 if (yielded) fiber.call();
398                 debug tracef("done");
399             }
400             if (!_loop)
401             {
402                 _loop = getDefaultLoop();
403             }
404             id = _id;
405             _cb6Delegates[_id] = &cbb;
406             ares_gethostbyname(_ares_channel, toStringz(hostname), AF_INET6, host_callback6, cast(void*)_id);
407             if ( done )
408             {
409                 // resolved from files
410                 debug tracef("return ready result");
411                 return ResolverResult6(status, addresses);
412             }
413             auto rc = ares_getsock(_ares_channel, &_sockets[0], ARES_GETSOCK_MAXNUM);
414             debug tracef("getsocks: 0x%04X, %s", rc, _sockets);
415             // prepare listening for socket events
416             handleGetSocks(rc, &_sockets);
417             yielded = true;
418             Fiber.yield();
419             return ResolverResult6(status, addresses);
420         }
421     }
422 
423     private void handleGetSocks(int rc, ares_socket_t[ARES_GETSOCK_MAXNUM] *s) @safe
424     {
425         for(int i; i < ARES_GETSOCK_MAXNUM;i++)
426         {
427             if (ARES_SOCK_READABLE(rc, i) && !_in_read[i])
428             {
429                 debug tracef("add ares socket %s to IN events", (*s)[i]);
430                 _loop.startPoll((*s)[i], AppEvent.IN, this);
431                 _in_read[i] = true;
432             }
433             else if (!ARES_SOCK_READABLE(rc, i) && _in_read[i])
434             {
435                 debug tracef("detach ares socket %s from IN events", (*s)[i]);
436                 _loop.stopPoll((*s)[i], AppEvent.IN);
437                 _in_read[i] = false;
438             }
439             if (ARES_SOCK_WRITABLE(rc, i) && !_in_write[i])
440             {
441                 debug tracef("add ares socket %s to OUT events", (*s)[i]);
442                 _loop.startPoll((*s)[i], AppEvent.OUT, this);
443                 _in_write[i] = true;
444             }
445             else if (!ARES_SOCK_WRITABLE(rc, i) && _in_write[i])
446             {
447                 debug tracef("detach ares socket %s from OUT events", (*s)[i]);
448                 _loop.stopPoll((*s)[i], AppEvent.OUT);
449                 _in_write[i] = true;
450             }
451         }
452     }
453 
454     //
455     // call ares_process_fd for evented socket (may call completion callback),
456     // check againg for list of sockets to listen,
457     // prepare to listening.
458     //
459     override void eventHandler(int f, AppEvent ev)
460     {
461         debug tracef("handler: %d, %s", f, ev);
462         int socket_index;
463         ares_socket_t rs = ARES_SOCKET_BAD, ws = ARES_SOCKET_BAD;
464 
465         if (f==_sockets[0])
466         {
467             // 99.99 %% case
468             socket_index = 0;
469         }
470         else
471         {
472             for(socket_index=1;socket_index<ARES_GETSOCK_MAXNUM;socket_index++)
473             if (f==_sockets[socket_index])
474             {
475                 break;
476             }
477         }
478         // we'll got range violation in case we didn't find socket
479         // ares_process() can close socket for its own reasons
480         // so we have to detach descriptor.
481         // If we do not - we will have closed socket in polling system.
482         if (ev & AppEvent.OUT)
483         {
484             _loop.stopPoll(f, AppEvent.OUT);
485             _in_write[socket_index] = false;
486             ws = f;
487         }
488         if (ev & AppEvent.IN)
489         {
490             _loop.stopPoll(f, AppEvent.IN);
491             _in_read[socket_index] = false;
492             rs = f;
493         }
494         ares_process_fd(_ares_channel, rs, ws);
495         auto rc = ares_getsock(_ares_channel, &_sockets[0], ARES_GETSOCK_MAXNUM);
496         debug tracef("getsocks: 0x%04X, %s", rc, _sockets);
497         // prepare listening for socket events
498         handleGetSocks(rc, &_sockets);
499     }
500 
501     ///
502     /// increment request id,
503     /// register callbacks in resolver,
504     /// start listening on sockets.
505     ///
506     auto gethostbyname(F)(string hostname, hlEvLoop loop, F cb) if (isCallable!F)
507     {
508         assert(!_loop || _loop == loop);
509         if (_loop is null)
510         {
511             _loop = loop;
512         }
513         _id++;
514         static if (isDelegate!F)
515         {
516             _cbDelegates[_id] = cb;
517         }
518         else
519         {
520             _cbFunctions[_id] = cb;
521         }
522         ares_gethostbyname(_ares_channel, toStringz(hostname), AF_INET, host_callback4, cast(void*)_id);
523         auto rc = ares_getsock(_ares_channel, &_sockets[0], ARES_GETSOCK_MAXNUM);
524         debug tracef("getsocks: 0x%04X, %s", rc, _sockets);
525         // prepare listening for socket events
526         handleGetSocks(rc, &_sockets);
527     }
528     auto gethostbyname6(F)(string hostname, hlEvLoop loop, F cb) if (isCallable!F)
529     {
530         assert(!_loop || _loop == loop);
531         if (_loop is null)
532         {
533             _loop = loop;
534         }
535         _id++;
536         static if (isDelegate!F)
537         {
538             _cb6Delegates[_id] = cb;
539         }
540         else
541         {
542             _cb6Functions[_id] = cb;
543         }
544         ares_gethostbyname(_ares_channel, toStringz(hostname), AF_INET6, host_callback6, cast(void*)_id);
545         auto rc = ares_getsock(_ares_channel, &_sockets[0], ARES_GETSOCK_MAXNUM);
546         debug tracef("getsocks: 0x%04X, %s", rc, _sockets);
547         // prepare listening for socket events
548         handleGetSocks(rc, &_sockets);
549     }
550 }
551 
552 unittest
553 {
554     globalLogLevel = LogLevel.info;
555     info("=== Testing resolver ares/sync  INET4 ===");
556     auto resolver = theResolver;
557     auto r = resolver.gethostbyname("localhost");
558     assert(r.status == 0);
559     debug tracef("%s", r);
560     r = resolver.gethostbyname("8.8.8.8");
561     assert(r.status == 0);
562     debug tracef("%s", r);
563     r = resolver.gethostbyname("dlang.org");
564     assert(r.status == 0);
565     debug tracef("%s", r);
566     r = resolver.gethostbyname(".......");
567     assert(r.status != 0);
568     debug tracef("%s", r);
569     if (r.status != 0) 
570     {
571         tracef("status: %s", ares_statusString(r.status));
572     }
573     r = resolver.gethostbyname("iuytkjhcxbvkjhgfaksdjf");
574     assert(r.status != 0);
575     debug tracef("%s", r);
576     if (r.status != 0) 
577     {
578         tracef("status: %s", ares_statusString(r.status));
579     }
580 }
581 
582 unittest
583 {
584     globalLogLevel = LogLevel.info;
585     info("=== Testing resolver ares/sync  INET6 ===");
586     auto resolver = theResolver;
587     auto r = resolver.gethostbyname6("localhost");
588     assert(r.status == 0);
589     debug tracef("%s", r);
590     r = resolver.gethostbyname6("8.8.8.8");
591     assert(r.status == 0);
592     debug tracef("%s", r);
593     r = resolver.gethostbyname6("dlang.org");
594     assert(r.status == 0);
595     debug tracef("%s", r);
596     r = resolver.gethostbyname6(".......");
597     assert(r.status != 0);
598     debug tracef("%s", r);
599     if (r.status != 0) 
600     {
601         tracef("status: %s", ares_statusString(r.status));
602     }
603 }
604 
605 unittest
606 {
607     import std.array: array;
608     globalLogLevel = LogLevel.info;
609     info("=== Testing resolver ares/async INET4 ===");
610     auto resolver = theResolver;
611     auto app(string hostname)
612     {
613         int status;
614         InternetAddress[] adresses;
615         Fiber fiber = Fiber.getThis();
616         bool done;
617         bool yielded;
618 
619         void cb(int s, uint[] a)
620         {
621             status = s;
622             foreach (ia; a) {
623                 adresses ~= new InternetAddress(ia, InternetAddress.PORT_ANY);
624             }
625             done = true;
626             debug tracef("resolve for %s: %s, %s", hostname, fromStringz(ares_strerror(s)), a);
627             if (yielded)
628             {
629                 fiber.call();
630             }
631         }
632         auto loop = getDefaultLoop();
633         resolver.gethostbyname(hostname, loop, &cb);
634         if (!done)
635         {
636             yielded = true;
637             Fiber.yield();
638         }
639         return adresses;
640     }
641     auto names = ["dlang.org", "google.com", ".."];
642     auto tasks = names.map!(n => task(&app, n)).array;
643     try
644     {
645         tasks.each!(t => t.start);
646         getDefaultLoop.run(2.seconds);
647         assert(tasks.all!(t => t.ready));
648     }
649     catch (Throwable e)
650     {
651         errorf("%s", e);
652     }
653 }
654 unittest
655 {
656     import std.array: array;
657     globalLogLevel = LogLevel.info;
658     info("=== Testing resolver ares/async INET6 ===");
659     auto resolver = theResolver;
660     auto app(string hostname)
661     {
662         int status;
663         Internet6Address[] addresses;
664         Fiber fiber = Fiber.getThis();
665         bool done;
666         bool yielded;
667 
668         void cb(int s, ubyte[16][] *a)
669         {
670             status = s;
671             foreach (ia; *a)
672             {
673                 addresses ~= new Internet6Address(ia, Internet6Address.PORT_ANY);
674             }
675             done = true;
676             debug tracef("resolve for %s: %s, %s", hostname, fromStringz(ares_strerror(s)), a);
677             if (yielded)
678             {
679                 fiber.call();
680             }
681         }
682         auto loop = getDefaultLoop();
683         resolver.gethostbyname6(hostname, loop, &cb);
684         if (!done)
685         {
686             yielded = true;
687             Fiber.yield();
688         }
689         return addresses;
690     }
691     auto names = ["dlang.org", "google.com", "cloudflare.com", ".."];
692     auto tasks = names.map!(n => task(&app, n)).array;
693     try
694     {
695         tasks.each!(t => t.start);
696         getDefaultLoop.run(2.seconds);
697         assert(tasks.all!(t => t.ready));
698     }
699     catch (Throwable e)
700     {
701         errorf("%s", e);
702     }
703 }
704 
705 unittest
706 {
707     globalLogLevel = LogLevel.info;
708     info("=== Testing resolver ares/App   INET4 ===");
709     App({
710         import std.array: array;
711         auto resolve(string name)
712         {
713             auto r = theResolver.gethostbyname(name);
714             debug tracef("app resolved %s=%s", name, r);
715             return r;
716         }
717         auto names = [
718             "dlang.org",
719             "google.com",
720             "a.root-servers.net.",
721             "b.root-servers.net.",
722             "c.root-servers.net.",
723             "d.root-servers.net.",
724             "e.root-servers.net.",
725             "...",
726         ];
727         auto tasks = names.map!(n => task(&resolve, n)).array;
728         tasks.each!(t => t.start);
729         tasks.each!(t => t.wait);
730     });
731 }
732 unittest
733 {
734     globalLogLevel = LogLevel.info;
735     info("=== Testing resolver ares/App   INET6 ===");
736     App({
737         import std.array: array;
738         auto resolve(string name)
739         {
740             auto r = theResolver.gethostbyname6(name);
741             debug tracef("app resolved %s=%s", name, r);
742             return r;
743         }
744         auto names = [
745             "dlang.org",
746             "google.com",
747             "a.root-servers.net.",
748             "b.root-servers.net.",
749             "c.root-servers.net.",
750             "d.root-servers.net.",
751             "e.root-servers.net.",
752             ".....",
753         ];
754         auto tasks = names.map!(n => task(&resolve, n)).array;
755         tasks.each!(t => t.start);
756         tasks.each!(t => t.wait);
757         //tasks.each!(t => writeln(t.result.status));
758     });
759 }