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 }