1 /// 2 module libasync.dns; 3 4 import libasync.types; 5 import libasync.events; 6 import core.thread : Thread, ThreadGroup; 7 import core.sync.mutex; 8 import core.sync.condition; 9 import core.atomic; 10 import libasync.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 }