1 module eventcore.drivers.posix.dns; 2 @safe: 3 4 import eventcore.driver; 5 import eventcore.drivers.posix.driver; 6 import eventcore.internal.utils : ChoppedVector, print; 7 8 import std.socket : Address, AddressFamily, InternetAddress, Internet6Address, UnknownAddress; 9 version (Posix) { 10 import std.socket : UnixAddress; 11 import core.sys.posix.netdb : AI_ADDRCONFIG, AI_V4MAPPED, addrinfo, freeaddrinfo, getaddrinfo; 12 import core.sys.posix.netinet.in_; 13 import core.sys.posix.netinet.tcp; 14 import core.sys.posix.sys.un; 15 import core.stdc.errno : errno, EAGAIN, EINPROGRESS; 16 import core.sys.posix.fcntl; 17 } 18 version (Windows) { 19 import core.sys.windows.windows; 20 import core.sys.windows.winsock2; 21 alias sockaddr_storage = SOCKADDR_STORAGE; 22 alias EAGAIN = WSAEWOULDBLOCK; 23 } 24 25 26 /// getaddrinfo+thread based lookup - does not support true cancellation 27 version (Posix) 28 final class EventDriverDNS_GAI(Events : EventDriverEvents, Signals : EventDriverSignals) : EventDriverDNS { 29 import std.parallelism : task, taskPool; 30 import std.string : toStringz; 31 import core.atomic : atomicFence, atomicLoad, atomicStore; 32 import core.thread : Thread; 33 34 private { 35 static struct Lookup { 36 shared(bool) done; 37 DNSLookupCallback callback; 38 uint validationCounter; 39 addrinfo* result; 40 int retcode; 41 string name; 42 Thread thread; 43 } 44 ChoppedVector!Lookup m_lookups; 45 Events m_events; 46 EventID m_event = EventID.invalid; 47 size_t m_maxHandle; 48 uint m_validationCounter; 49 } 50 51 this(Events events, Signals signals) 52 @nogc { 53 m_events = events; 54 setupEvent(); 55 } 56 57 void dispose() 58 { 59 if (m_event != EventID.invalid) { 60 m_events.cancelWait(m_event, &onDNSSignal); 61 m_events.releaseRef(m_event); 62 m_event = EventID.invalid; 63 } 64 } 65 66 override DNSLookupID lookupHost(string name, DNSLookupCallback on_lookup_finished) 67 { 68 debug (EventCoreLogDNS) print("lookup %s", name); 69 auto handle = allocateHandle(); 70 if (handle > m_maxHandle) m_maxHandle = handle; 71 72 assert(on_lookup_finished !is null, "Null callback passed to lookupHost"); 73 74 setupEvent(); 75 76 assert(!m_lookups[handle].result); 77 Lookup* l = () @trusted { return &m_lookups[handle]; } (); 78 l.name = name; 79 l.callback = on_lookup_finished; 80 l.done = false; 81 auto events = () @trusted { return cast(shared)m_events; } (); 82 83 try { 84 auto thr = new class(l, AddressFamily.UNSPEC, events, m_event) Thread { 85 Lookup* m_lookup; 86 AddressFamily m_family; 87 shared(Events) m_events; 88 EventID m_event; 89 90 this(Lookup* l, AddressFamily af, shared(Events) events, EventID event) 91 { 92 m_lookup = l; 93 m_family = af; 94 m_events = events; 95 m_event = event; 96 super(&perform); 97 this.name = "Eventcore DNS Lookup"; 98 l.thread = this; 99 } 100 101 void perform() 102 nothrow { 103 debug (EventCoreLogDNS) print("lookup %s start", m_lookup.name); 104 addrinfo hints; 105 hints.ai_flags = AI_ADDRCONFIG; 106 version (linux) hints.ai_flags |= AI_V4MAPPED; 107 hints.ai_family = m_family; 108 () @trusted { m_lookup.retcode = getaddrinfo(m_lookup.name.toStringz(), null, m_family == AddressFamily.UNSPEC ? null : &hints, &m_lookup.result); } (); 109 if (m_lookup.retcode == -1) 110 version (CRuntime_Glibc) version (linux) __res_init(); 111 112 assert(m_lookup.retcode != 0 || m_lookup.result !is null); 113 114 atomicStore(m_lookup.done, true); 115 atomicFence(); // synchronize the other fields in m_lookup with the main thread 116 m_events.trigger(m_event, true); 117 debug (EventCoreLogDNS) print("lookup %s finished", m_lookup.name); 118 } 119 }; 120 121 () @trusted { thr.start(); } (); 122 } catch (Exception e) { 123 return DNSLookupID.invalid; 124 } 125 126 debug (EventCoreLogDNS) print("lookup handle: %s", handle); 127 m_events.loop.m_waiterCount++; 128 return DNSLookupID(handle, l.validationCounter); 129 } 130 131 override void cancelLookup(DNSLookupID handle) 132 { 133 if (!isValid(handle)) return; 134 m_lookups[handle].callback = null; 135 m_lookups[handle].result = null; 136 m_events.loop.m_waiterCount--; 137 } 138 139 override bool isValid(DNSLookupID handle) 140 const { 141 if (handle.value >= m_lookups.length) return false; 142 return m_lookups[handle.value].validationCounter == handle.validationCounter; 143 } 144 145 private void onDNSSignal(EventID event) 146 @trusted nothrow 147 { 148 debug (EventCoreLogDNS) print("DNS event triggered"); 149 m_events.wait(m_event, &onDNSSignal); 150 151 size_t lastmax; 152 foreach (i, ref l; m_lookups) { 153 if (i > m_maxHandle) break; 154 if (!atomicLoad(l.done)) { 155 lastmax = i; 156 continue; 157 } 158 // synchronize the other fields in m_lookup with the lookup thread 159 atomicFence(); 160 161 if (l.thread !is null) { 162 try { 163 l.thread.join(); 164 destroy(l.thread); 165 } catch (Exception e) { 166 debug (EventCoreLogDNS) print("Failed to join DNS thread: %s", e.msg); 167 } 168 l.thread = null; 169 } 170 171 if (l.callback) { 172 if (l.result || l.retcode) { 173 debug (EventCoreLogDNS) print("found finished lookup %s for %s", i, l.name); 174 auto cb = l.callback; 175 auto ai = l.result; 176 DNSStatus status; 177 switch (l.retcode) { 178 default: status = DNSStatus.error; break; 179 case 0: status = DNSStatus.ok; break; 180 } 181 l.callback = null; 182 l.result = null; 183 l.retcode = 0; 184 l.done = false; 185 if (i == m_maxHandle) m_maxHandle = lastmax; 186 m_events.loop.m_waiterCount--; 187 // An error happened, we have a return code 188 // We can directly call the delegate with it instead 189 // of calling `passToDNSCallback` (which doesn't support 190 // a `null` result on some platforms) 191 if (ai is null) 192 cb(DNSLookupID(i, l.validationCounter), status, null); 193 else 194 passToDNSCallback(DNSLookupID(i, l.validationCounter), cb, status, ai); 195 } else lastmax = i; 196 } 197 } 198 debug (EventCoreLogDNS) print("Max active DNS handle: %s", m_maxHandle); 199 } 200 201 private DNSLookupID allocateHandle() 202 @safe nothrow { 203 assert(m_lookups.length <= int.max); 204 int id = cast(int)m_lookups.length; 205 foreach (i, ref l; m_lookups) 206 if (!l.callback && !l.result) { 207 id = cast(int)i; 208 break; 209 } 210 211 auto vc = ++m_validationCounter; 212 m_lookups[id].validationCounter = vc; 213 return DNSLookupID(cast(int)id, vc); 214 } 215 216 private void setupEvent() 217 @nogc { 218 if (m_event == EventID.invalid) { 219 m_event = m_events.createInternal(); 220 m_events.wait(m_event, &onDNSSignal); 221 } 222 } 223 } 224 225 226 /// getaddrinfo_a based asynchronous lookups 227 final class EventDriverDNS_GAIA(Events : EventDriverEvents, Signals : EventDriverSignals) : EventDriverDNS { 228 import core.sys.posix.signal : SIGEV_SIGNAL, SIGRTMIN, sigevent; 229 230 private { 231 static struct Lookup { 232 gaicb ctx; 233 uint validationCounter; 234 DNSLookupCallback callback; 235 } 236 ChoppedVector!Lookup m_lookups; 237 Events m_events; 238 Signals m_signals; 239 int m_dnsSignal; 240 uint m_validationCounter; 241 SignalListenID m_sighandle; 242 } 243 244 @safe nothrow: 245 246 this(Events events, Signals signals) 247 { 248 m_events = events; 249 m_signals = signals; 250 m_dnsSignal = () @trusted { return SIGRTMIN; } (); 251 m_sighandle = signals.listenInternal(m_dnsSignal, &onDNSSignal); 252 } 253 254 void dispose() 255 { 256 m_signals.releaseRef(m_sighandle); 257 } 258 259 override DNSLookupID lookupHost(string name, DNSLookupCallback on_lookup_finished) 260 { 261 import std.string : toStringz; 262 263 auto handle = allocateHandle(); 264 265 sigevent evt; 266 evt.sigev_notify = SIGEV_SIGNAL; 267 evt.sigev_signo = m_dnsSignal; 268 gaicb* res = &m_lookups[handle].ctx; 269 res.ar_name = name.toStringz(); 270 auto ret = () @trusted { return getaddrinfo_a(GAI_NOWAIT, &res, 1, &evt); } (); 271 272 if (ret != 0) 273 { 274 version (CRuntime_Glibc) version (linux) __res_init(); 275 return DNSLookupID.invalid; 276 } 277 278 m_lookups[handle].callback = on_lookup_finished; 279 m_events.loop.m_waiterCount++; 280 281 return handle; 282 } 283 284 override void cancelLookup(DNSLookupID handle) 285 { 286 gai_cancel(&m_lookups[handle].ctx); 287 m_lookups[handle].callback = null; 288 m_events.loop.m_waiterCount--; 289 } 290 291 override bool isValid(DNSLookupID handle) 292 { 293 if (handle.value >= m_lookups.length) 294 return false; 295 return m_lookups[handle.value].validationCounter == handle.validationCounter; 296 } 297 298 private void onDNSSignal(SignalListenID, SignalStatus status, int signal) 299 @safe nothrow 300 { 301 assert(status == SignalStatus.ok); 302 foreach (i, ref l; m_lookups) { 303 scope (failure) assert(false); 304 305 if (!l.callback) continue; 306 auto err = gai_error(&l.ctx); 307 if (err == EAI_INPROGRESS) continue; 308 DNSStatus status; 309 switch (err) { 310 default: status = DNSStatus.error; break; 311 case 0: status = DNSStatus.ok; break; 312 } 313 auto cb = l.callback; 314 auto ai = l.ctx.ar_result; 315 l.callback = null; 316 l.ctx.ar_result = null; 317 m_events.loop.m_waiterCount--; 318 passToDNSCallback(cast(DNSLookupID)cast(int)i, cb, status, ai); 319 } 320 } 321 322 private DNSLookupID allocateHandle() 323 { 324 foreach (i, ref l; m_lookups) 325 if (!l.callback) { 326 m_lookups[i].validationCounter = ++m_validationCounter; 327 return cast(DNSLookupID)cast(int)i; 328 } 329 m_lookups[m_lookups.length].validationCounter = ++m_validationCounter; 330 return cast(DNSLookupID)cast(int)m_lookups.length; 331 } 332 } 333 334 version (linux) extern(C) { 335 import core.sys.posix.signal : sigevent; 336 337 nothrow @nogc: 338 339 struct gaicb { 340 const(char)* ar_name; 341 const(char)* ar_service; 342 const(addrinfo)* ar_request; 343 addrinfo* ar_result; 344 } 345 346 enum GAI_NOWAIT = 1; 347 348 enum EAI_INPROGRESS = -100; 349 350 int getaddrinfo_a(int mode, gaicb** list, int nitems, sigevent *sevp); 351 int gai_error(gaicb *req); 352 int gai_cancel(gaicb *req); 353 354 int __res_init(); 355 } 356 357 358 /// ghbn based lookup - does not support cancellation and blocks the thread! 359 final class EventDriverDNS_GHBN(Events : EventDriverEvents, Signals : EventDriverSignals) : EventDriverDNS { 360 import std.parallelism : task, taskPool; 361 import std.string : toStringz; 362 363 private { 364 static struct Lookup { 365 DNSLookupCallback callback; 366 bool success; 367 int retcode; 368 string name; 369 } 370 size_t m_maxHandle; 371 } 372 373 this(Events events, Signals signals) 374 { 375 } 376 377 void dispose() 378 { 379 } 380 381 override DNSLookupID lookupHost(string name, DNSLookupCallback on_lookup_finished) 382 { 383 import std.string : toStringz; 384 385 auto handle = DNSLookupID(m_maxHandle++, 0); 386 387 auto he = () @trusted { return gethostbyname(name.toStringz); } (); 388 if (he is null) { 389 on_lookup_finished(handle, DNSStatus.error, null); 390 return handle; 391 } 392 switch (he.h_addrtype) { 393 default: assert(false, "Invalid address family returned from host lookup."); 394 case AF_INET: { 395 sockaddr_in sa; 396 sa.sin_family = AF_INET; 397 sa.sin_addr = () @trusted { return *cast(in_addr*)he.h_addr_list[0]; } (); 398 scope addr = new RefAddress(() @trusted { return cast(sockaddr*)&sa; } (), sa.sizeof); 399 RefAddress[1] aa; 400 aa[0] = addr; 401 on_lookup_finished(handle, DNSStatus.ok, aa); 402 } break; 403 case AF_INET6: { 404 sockaddr_in6 sa; 405 sa.sin6_family = AF_INET6; 406 sa.sin6_addr = () @trusted { return *cast(in6_addr*)he.h_addr_list[0]; } (); 407 scope addr = new RefAddress(() @trusted { return cast(sockaddr*)&sa; } (), sa.sizeof); 408 RefAddress[1] aa; 409 aa[0] = addr; 410 on_lookup_finished(handle, DNSStatus.ok, aa); 411 } break; 412 } 413 414 return handle; 415 } 416 417 override void cancelLookup(DNSLookupID) {} 418 419 override bool isValid(DNSLookupID) 420 const { 421 return true; 422 } 423 } 424 425 package struct DNSSlot { 426 alias Handle = DNSLookupID; 427 DNSLookupCallback callback; 428 } 429 430 private void passToDNSCallback()(DNSLookupID id, scope DNSLookupCallback cb, DNSStatus status, addrinfo* ai_orig) 431 @trusted nothrow 432 { 433 import std.typecons : scoped; 434 435 try { 436 typeof(scoped!RefAddress())[16] addrs_prealloc = [ 437 scoped!RefAddress(), scoped!RefAddress(), scoped!RefAddress(), scoped!RefAddress(), 438 scoped!RefAddress(), scoped!RefAddress(), scoped!RefAddress(), scoped!RefAddress(), 439 scoped!RefAddress(), scoped!RefAddress(), scoped!RefAddress(), scoped!RefAddress(), 440 scoped!RefAddress(), scoped!RefAddress(), scoped!RefAddress(), scoped!RefAddress() 441 ]; 442 //Address[16] addrs; 443 RefAddress[16] addrs; 444 auto ai = ai_orig; 445 size_t addr_count = 0; 446 while (ai !is null && addr_count < addrs.length) { 447 RefAddress ua = addrs_prealloc[addr_count]; // FIXME: avoid heap allocation 448 ua.set(ai.ai_addr, ai.ai_addrlen); 449 addrs[addr_count] = ua; 450 addr_count++; 451 ai = ai.ai_next; 452 } 453 cb(id, status, addrs[0 .. addr_count]); 454 freeaddrinfo(ai_orig); 455 } catch (Exception e) assert(false, e.msg); 456 }