1 ///
2 module async.dns;
3 
4 import async.types;
5 import async.events;
6 import core.thread : Thread, ThreadGroup;
7 import core.sync.mutex;
8 import core.sync.condition;
9 import core.atomic;
10 import async.threads;
11 
12 ///
13 enum DNSCmd {
14 	///
15 	RESOLVEHOST,
16 	///
17 	RESOLVEIP
18 }
19 
20 /// Resolves internet addresses and returns the results in a specified callback.
21 shared final class AsyncDNS
22 {
23 nothrow:
24 private:
25 	EventLoop m_evLoop;
26 	bool m_busy;
27 	bool m_error;
28 	DNSReadyHandler m_handler;
29 	DNSCmdInfo m_cmdInfo;
30 	StatusInfo m_status;
31 	Thread m_owner;
32 
33 public:
34 	///
35 	this(EventLoop evl) {
36 		m_evLoop = cast(shared) evl;
37 		try {
38 			m_cmdInfo.ready = new shared AsyncSignal(cast(EventLoop)m_evLoop);
39 		} catch (Throwable) {
40 			assert(false, "Failed to start DNS Signaling");
41 		}
42 		m_cmdInfo.ready.run(cast(void delegate())&callback);
43 		m_owner = cast(shared)Thread.getThis();
44 		try m_cmdInfo.mtx = cast(shared) new Mutex; catch (Exception) {}
45 	}
46 
47 	///
48 	synchronized @property StatusInfo status() const
49 	{
50 		return cast(StatusInfo) m_status;
51 	}
52 
53 	///
54 	@property string error() const
55 	{
56 		return status.text;
57 	}
58 
59 	/// Uses the callback for all resolved addresses.
60 	shared(typeof(this)) handler(void delegate(NetworkAddress) del) {
61 		shared DNSReadyHandler handler;
62 		handler.del = cast(shared) del;
63 		handler.ctxt = this;
64 		try synchronized(this) m_handler = handler;
65 		catch (Throwable) assert(false, "Failed to set handler in AsyncDNS");
66 		return this;
67 	}
68 
69 	/// Sends a request through a thread pool for the specified host to be resolved. The
70 	/// callback specified in run() will be signaled with the OS-specific NetworkAddress
71 	/// structure.
72 	bool resolveHost(string url, bool ipv6 = false, bool force_async = false)
73 	in {
74 		assert(!m_busy, "Resolver is busy or closed");
75 		assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on.");
76 	}
77 	body {
78 		if (force_async == true) {
79 			try synchronized(m_cmdInfo.mtx) {
80 				m_cmdInfo.command = DNSCmd.RESOLVEHOST;
81 				m_cmdInfo.ipv6 = ipv6;
82 				m_cmdInfo.url = cast(shared) url;
83 			} catch (Exception) {}
84 		} else {
85 			m_cmdInfo.command = DNSCmd.RESOLVEHOST;
86 			m_cmdInfo.ipv6 = ipv6;
87 			m_cmdInfo.url = cast(shared) url;
88 			m_cmdInfo.addr = cast(shared)( (cast(EventLoop)m_evLoop).resolveHost(cmdInfo.url, 0, cmdInfo.ipv6?isIPv6.yes:isIPv6.no) );
89 			callback();
90 			return true;
91 		}
92 
93 		return doOffThread({ process(this); });
94 	}
95 
96 	/// Returns an OS-specific NetworkAddress structure from the specified IP.
97 	NetworkAddress resolveIP(string url, bool ipv6)
98 	in {
99 		assert(!m_busy, "Resolver is busy or closed");
100 		assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on.");
101 	}
102 	body {
103 		return (cast(EventLoop)m_evLoop).resolveIP(url, 0, ipv6?isIPv6.yes:isIPv6.no);
104 	}
105 
106 	/// Cleans up underlying resources. Used as a placeholder for possible future purposes.
107 	bool kill() {
108 		return true;
109 	}
110 
111 package:
112 	synchronized @property DNSCmdInfo cmdInfo() {
113 		return m_cmdInfo;
114 	}
115 
116 	shared(NetworkAddress*) addr() {
117 		try synchronized(m_cmdInfo.mtx)
118 			return cast(shared)&m_cmdInfo.addr;
119 		catch (Exception) {}
120 		return null;
121 	}
122 
123 	synchronized @property void status(StatusInfo stat) {
124 		m_status = cast(shared) stat;
125 	}
126 
127 	synchronized @property bool waiting() const {
128 		return cast(bool) m_busy;
129 	}
130 
131 	synchronized @property void waiting(bool b) {
132 		m_busy = cast(shared) b;
133 	}
134 
135 	void callback() {
136 
137 		try {
138 			m_handler(cast(NetworkAddress)m_cmdInfo.addr);
139 		}
140 		catch (Throwable e) {
141 			static if (DEBUG) {
142 				import std.stdio : writeln;
143 				try writeln("Failed to send command. ", e.toString()); catch (Throwable) {}
144 			}
145 		}
146 	}
147 
148 }
149 
150 package shared struct DNSCmdInfo
151 {
152 	DNSCmd command;
153 	bool ipv6;
154 	string url;
155 	NetworkAddress addr;
156 	AsyncSignal ready;
157 	AsyncDNS dns;
158 	Mutex mtx; // for NetworkAddress writing
159 }
160 
161 package shared struct DNSReadyHandler {
162 	AsyncDNS ctxt;
163 	void delegate(NetworkAddress) del;
164 
165 	void opCall(NetworkAddress addr) {
166 		assert(ctxt !is null);
167 		del(addr);
168 		return;
169 	}
170 }
171 
172 private void process(shared AsyncDNS ctxt) {
173 	auto evLoop = getThreadEventLoop();
174 
175 	DNSCmdInfo cmdInfo = ctxt.cmdInfo();
176 	auto mutex = cmdInfo.mtx;
177 	DNSCmd cmd;
178 	string url;
179 	cmd = cmdInfo.command;
180 	url = cmdInfo.url;
181 
182 	try final switch (cmd)
183 	{
184 		case DNSCmd.RESOLVEHOST:
185 			*ctxt.addr = cast(shared) evLoop.resolveHost(url, 0, cmdInfo.ipv6 ? isIPv6.yes : isIPv6.no);
186 			break;
187 
188 		case DNSCmd.RESOLVEIP:
189 			*ctxt.addr = cast(shared) evLoop.resolveIP(url, 0, cmdInfo.ipv6 ? isIPv6.yes : isIPv6.no);
190 			break;
191 
192 	} catch (Throwable e) {
193 		auto status = StatusInfo.init;
194 		status.code = Status.ERROR;
195 		try status.text = e.toString(); catch (Throwable) {}
196 		ctxt.status = status;
197 	}
198 
199 	try cmdInfo.ready.trigger(evLoop);
200 	catch (Throwable e) {
201 		auto status = StatusInfo.init;
202 		status.code = Status.ERROR;
203 		try status.text = e.toString(); catch (Throwable) {}
204 		ctxt.status = status;
205 	}
206 }