1 module dnslib.net;
2 
3 import dnslib.defs;
4 
5 import vibe.core.net;
6 
7 import std.stdio;
8 import std.datetime.stopwatch;
9 
10 immutable UDP_REQUEST_DATA_SIZE_LIMIT = 500;
11 
12 import core.time:				Duration, seconds;
13 import std.datetime.systime:	SysTime, Clock;
14 import std.datetime.date:		DateTime;
15 
16 // ---------------------------------------------------------------------
17 
18 enum dnsQueryResult : ubyte
19 {
20 	success				= 0,
21 	requestTooLarge,
22 	connectionFailure,
23 	certificateFailure,
24 	timeout,
25 	dataMissing,
26 	tlsConfigurationError,
27 	idMismatch
28 }
29 
30 struct DnsNetConfig
31 {
32 	Protocol			protocol				= Protocol.udptcp;
33 	uint				udpSizeLimit			= UDP_REQUEST_DATA_SIZE_LIMIT;
34 
35 	string				server					= "127.0.0.1";
36 	string				serverName				= "";
37 	
38 	bool				trusted					= true;
39 	string				trustedCertificateFile	= "/etc/ssl/certs/ca-certificates.crt";
40 	
41 	ushort				udpTcpPort				=  53;	
42 	ushort				tlsPort					= 853;	
43 
44 	core.time.Duration	timeout					= 10.seconds;
45 }
46 
47 // All dummy values that should never appear when running query function
48 struct DnsNetStat
49 {
50 	ulong				requestSize				= 0;
51 	ulong				responseSize			= 0;
52 
53 	Protocol			protocol				= Protocol.none;
54 	string				server					= "";
55 
56 	bool				trusted					= false;
57 
58 	ushort				port					= 0;	
59 
60 	DateTime			timestamp				= DateTime(1, 1, 1, 0, 0, 0);
61 	ulong				duration				= 0;
62 
63 	string toString()
64 	{
65 		import std..string: leftJustify;
66 		static import std.ascii;
67 		import std.array:  join;
68 
69 		string[] resultArray = [];
70 		import std.conv: to, text;
71 		import std.stdio;
72 		import std.traits;
73 		//const auto b = [ __traits(allMembers, DnsOptions) ];
74 		const auto b = FieldNameTuple!DnsNetStat;
75 		
76 		static foreach(element; b)
77 		{
78 			mixin("resultArray ~= leftJustify(element, 23, ' ') ~ \": \"  ~ this." ~ element ~ ".text() ~ std.ascii.newline; ");
79 		}
80 			
81 		string s = resultArray.join();
82 		return s;	
83 	}
84 }
85 
86 // ---------------------------------------------------------------------
87 
88 // Function query(...) also checks for matching IDs. This is not the case with functions udpQuery, tcpQuery and tlsQuery
89 dnsQueryResult query(ref DnsNetConfig netConfig, const ref ubyte[] requestData, ref ubyte[] responseData, out DnsNetStat netStat)
90 {
91 	auto result = dnsQueryResult.success;
92 
93 	final switch(netConfig.protocol)
94 	{
95 		case Protocol.udp:		result = udpQuery(netConfig, requestData, responseData, netStat);
96 								break;
97 
98 		case Protocol.tcp:		result = tcpQuery(netConfig, requestData, responseData, netStat);
99 								break;
100 
101 		case Protocol.udptcp:	if (requestData.length <= netConfig.udpSizeLimit)
102 								{
103 									result = udpQuery(netConfig, requestData, responseData, netStat);									
104 								}
105 								else
106 								{
107 									result = tcpQuery(netConfig, requestData, responseData, netStat);		
108 								}
109 								break;
110 								
111 version(ENABLE_TLS)
112 {
113 		case Protocol.tls:		result = tlsQuery(netConfig, requestData, responseData, netStat);
114 								break;
115 
116 		case Protocol.tlstcp:	result = tlsQuery(netConfig, requestData, responseData, netStat);
117 								if (result != dnsQueryResult.success)
118 								{
119 									result = tcpQuery(netConfig, requestData, responseData, netStat);
120 								}
121 								break;
122 }
123 		case Protocol.none:		assert(false);
124 	}
125 
126 	if (result == dnsQueryResult.success)
127 	{
128 		if (requestData.length < 2 || responseData.length < 2 || requestData[0..1] != responseData[0..1])
129 		{
130 			result = dnsQueryResult.idMismatch;
131 		}
132 	}
133 	
134 	return result;
135 }  // query
136 
137 // ---------------------------------------------------------------------
138 
139 dnsQueryResult udpQuery(ref DnsNetConfig netConfig, const ref ubyte[] requestData, ref ubyte[] responseData, out DnsNetStat netStat)
140 {
141 	netStat.requestSize		= requestData.length;
142 	netStat.protocol		= Protocol.udp;
143 	netStat.server			= netConfig.server;
144 	netStat.port			= netConfig.udpTcpPort;	
145 
146 	netStat.timestamp		= cast(DateTime)(Clock.currTime());
147 	auto sw = StopWatch(AutoStart.yes);
148 	
149 	scope(exit)
150 	{
151 		netStat.responseSize	= responseData.length;
152 		netStat.duration		= sw.peek().total!"msecs";		
153 	}
154 	
155 	if (requestData.length > netConfig.udpSizeLimit)
156 	{
157 		return dnsQueryResult.requestTooLarge;
158 	}
159 	
160 	UDPConnection myUdpConnection;
161 	
162 	try
163 	{
164 		
165 		myUdpConnection = listenUDP(0);
166 		myUdpConnection.connect(netConfig.server, netConfig.udpTcpPort);
167 		myUdpConnection.send(requestData);
168 	}
169 	catch (Exception e)
170 	{
171 		return dnsQueryResult.connectionFailure;
172 	}
173 
174 	ubyte[] buf = null;
175 
176 	try
177 	{
178 		responseData = myUdpConnection.recv(netConfig.timeout, buf);
179 	}
180 	catch (Exception e)
181 	{
182 		return dnsQueryResult.timeout;
183 	}	
184 	
185 	//writefln("responseData length: %d", responseData.length);
186 	import std.digest;
187 	import std.conv;
188 	
189 	try
190 	{
191 		myUdpConnection.close();
192 	}
193 	catch (Exception e)
194 	{
195 		return dnsQueryResult.connectionFailure;
196 	}
197 	
198 	return dnsQueryResult.success;
199 }  // udpQuery
200 
201 // ---------------------------------------------------------------------
202 
203 dnsQueryResult tcpQuery(ref DnsNetConfig netConfig, const ref ubyte[] requestData, ref ubyte[] responseData, out DnsNetStat netStat)
204 {
205 	netStat.requestSize		= requestData.length;
206 	netStat.protocol		= Protocol.tcp;
207 	netStat.server			= netConfig.server;
208 	netStat.port			= netConfig.udpTcpPort;	
209 	netStat.timestamp		= cast(DateTime)(Clock.currTime());
210 
211 	auto sw = StopWatch(AutoStart.yes);
212 
213 	scope(exit)
214 	{
215 		netStat.responseSize	= responseData.length;
216 		netStat.duration		= sw.peek().total!"msecs";		
217 	}
218 
219 	TCPConnection myTcpConnection;
220 	
221 	try
222 	{
223 		netStat.server			= netConfig.server;
224 		
225 		immutable string bind_interface	= null;
226 		immutable ushort bind_port		= cast(ushort)0u;
227 		myTcpConnection					= connectTCP(netConfig.server, netConfig.udpTcpPort, bind_interface, bind_port, netConfig.timeout);
228 	}
229 	catch (Exception e)
230 	{	
231 		return dnsQueryResult.connectionFailure;
232 	}
233 
234 	scope(exit)
235 	{
236 		myTcpConnection.close();		
237 	}
238 	
239 	if (!myTcpConnection.connected) return dnsQueryResult.connectionFailure;	
240 
241 	static import std.conv;
242 	ushort requestLength = std.conv.to!ushort(requestData.length);
243 
244 	ubyte hi = requestLength / 256;
245 	ubyte lo = requestLength % 256;
246 	ubyte[] temp = [hi, lo];
247 
248 	try
249 	{
250 		myTcpConnection.write(temp);	
251 		myTcpConnection.write(requestData);
252 		myTcpConnection.flush();
253 	}
254 	catch (Exception e)
255 	{
256 		return dnsQueryResult.connectionFailure;
257 	}
258 		
259 	ushort bufferSize;
260 	
261 	try
262 	{
263 		if(myTcpConnection.waitForData(netConfig.timeout))
264 		{
265 			if (myTcpConnection.leastSize < 2) return dnsQueryResult.timeout;
266 			
267 			ubyte[2] size;
268 			myTcpConnection.read(size);
269 			
270 			bufferSize = size[0]*256 + size[1];
271 		}
272 		else
273 		{
274 			if (!myTcpConnection.connected) return dnsQueryResult.connectionFailure;	
275 
276 			return dnsQueryResult.timeout;
277 		}
278 	
279 		ubyte[] buffer = new ubyte[](bufferSize);
280 
281 		if(myTcpConnection.waitForData(netConfig.timeout))
282 		{	
283 			if (myTcpConnection.leastSize < bufferSize) return dnsQueryResult.dataMissing;
284 
285 			// ToDo: This can be improved. What if octets come in several batches and time for last batch exceeds dnsTimeout limit?
286 			myTcpConnection.read(buffer);
287 
288 			if (buffer.length < bufferSize) return dnsQueryResult.dataMissing;
289 		}
290 		else
291 		{
292 			if (!myTcpConnection.connected) return dnsQueryResult.connectionFailure;	
293 			
294 			return dnsQueryResult.timeout;
295 		}
296 		
297 		responseData = buffer;
298 	}
299 	catch (Exception e)
300 	{
301 		return dnsQueryResult.connectionFailure;
302 	}
303 	
304 	return dnsQueryResult.success;
305 }  // tcpQuery
306 
307 // ---------------------------------------------------------------------
308 
309 version(ENABLE_TLS)
310 {
311 
312 dnsQueryResult tlsQuery(ref DnsNetConfig netConfig, const ref ubyte[] requestData, ref ubyte[] responseData, out DnsNetStat netStat)
313 {
314 	netStat.requestSize		= requestData.length;
315 	netStat.protocol		= Protocol.tls;
316 	netStat.server			= netConfig.server;
317 
318 	netStat.port			= netConfig.tlsPort;	
319 	netStat.timestamp		= cast(DateTime)(Clock.currTime());
320 
321 	auto sw = StopWatch(AutoStart.yes);
322 
323 	scope(exit)
324 	{
325 		netStat.responseSize	= responseData.length;
326 		netStat.duration		= sw.peek().total!"msecs";		
327 	}
328 
329 	TCPConnection	myTcpConnection;
330 	scope(exit)
331 	{
332 		if (myTcpConnection.connected)
333 		{
334 			myTcpConnection.flush();
335 			myTcpConnection.finalize();
336 			myTcpConnection.close();
337 		}
338 	}
339 
340 	import vibe.stream.tls;
341 	TLSStream connStream = null;
342 
343 	scope(exit)
344 	{
345 		if (connStream !is null)
346 		{
347 				connStream.finalize();
348 		}
349 	}
350 
351 	import vibe.stream.wrapper;
352 	ConnectionProxyStream connProxStr = null;
353 
354 	scope(exit)
355 	{
356 		if (connProxStr !is null)
357 		{
358 				connProxStr.finalize();
359 				connProxStr.close();
360 		}
361 	}
362 
363 	//TCPConnection	myTcpConnection;
364 	auto tlsCtx = createTLSContext(TLSContextKind.client, TLSVersion.tls1_2);
365 	
366 	if (netConfig.trusted)
367 	{
368 		if (netConfig.serverName == "" || netConfig.trustedCertificateFile == "")
369 		{
370 			return dnsQueryResult.tlsConfigurationError;
371 		}
372 		// TLSPeerValidationMode.checkCert|requireCert forces:
373 		// 1) Require the peer to always present a certificate. 
374 		// 2) Check the certificate for basic validity. 
375 		// 3) Validate the actual peer name/address against the certificate. 
376 		// 4) Requires that the certificate or any parent certificate is trusted. 
377 		tlsCtx.peerValidationMode = TLSPeerValidationMode.trustedCert;										// FULL CHECK OF CERTIFICATE
378 	}
379 	else if (netConfig.serverName == "")
380 	{
381 		// TLSPeerValidationMode.checkCert|requireCert forces:
382 		// 1) Require the peer to always present a certificate. 
383 		// 2) Check the certificate for basic validity. 
384 		tlsCtx.peerValidationMode = TLSPeerValidationMode.checkCert | TLSPeerValidationMode.requireCert;	// ONLY BASIC CHECK OF CERTIFICATE
385 	}
386 	else
387 	{
388 		// TLSPeerValidationMode.validCert forces:
389 		// 1) Require the peer to always present a certificate. 
390 		// 2) Check the certificate for basic validity.
391 		// 3) Validate the actual peer name/address against the certificate.
392 		tlsCtx.peerValidationMode = TLSPeerValidationMode.validCert;										// ONLY BASIC CHECK OF CERTIFICATE
393 	}
394 
395 	tlsCtx.useTrustedCertificateFile(netConfig.trustedCertificateFile);
396 	
397 	try
398 	{
399 		netStat.server			= netConfig.server;
400 
401 		immutable string bind_interface	= null;
402 		immutable ushort bind_port		= cast(ushort)0u;
403 		myTcpConnection					= connectTCP(netConfig.server, netConfig.tlsPort, bind_interface, bind_port, netConfig.timeout);
404 	}
405 	catch(Exception e)
406 	{
407 		//writefln("Error in tlsQuery -> connectTCP : %s ; %s ; %s", e.msg, e.file, e.line);
408 		return dnsQueryResult.connectionFailure;
409 	}
410 
411 	//scope(exit)
412 	//{
413 		//myTcpConnection.flush();
414 		//myTcpConnection.finalize();
415 		//myTcpConnection.close();
416 	//}
417 
418 	try
419 	{
420 		connStream		= createTLSStream(myTcpConnection, tlsCtx, netConfig.serverName);
421 	}
422 	catch(Exception e)
423 	{
424 		//writefln("Error in tlsQuery (1) -> TLS : %s ; %s ; %s", e.msg, e.file, e.line);
425 		return dnsQueryResult.certificateFailure;
426 	}
427 
428 	try
429 	{
430 		//static import vibe.stream.wrapper;
431 		connProxStr = vibe.stream.wrapper.createConnectionProxyStream(connStream, myTcpConnection);				
432 	}
433 	catch(Exception e)
434 	{
435 		//writefln("Error in tlsQuery (2) -> TLS : %s ; %s ; %s", e.msg, e.file, e.line);
436 		//return dnsQueryResult.certificateFailure;
437 		return dnsQueryResult.connectionFailure;
438 	}
439 
440 
441 
442 	if (connStream is null || connProxStr is null)
443 	{
444 		//writeln("Error in tlsQuery; connStream or connProxStr is null");
445 		return dnsQueryResult.connectionFailure;
446 	}
447 	
448 	if (!myTcpConnection.connected) return dnsQueryResult.connectionFailure;	
449 
450 	static import std.conv;
451 	ushort requestLength = std.conv.to!ushort(requestData.length);
452 
453 	ubyte hi = requestLength / 256;
454 	ubyte lo = requestLength % 256;
455 	ubyte[] temp = [hi, lo];
456 
457 	try
458 	{
459 		connProxStr.write(temp);	
460 		connProxStr.write(requestData);
461 		connProxStr.flush();
462 	}
463 	catch (Exception e)
464 	{
465 		return dnsQueryResult.connectionFailure;
466 	}
467 		
468 	ushort bufferSize;
469 	
470 	if(myTcpConnection.waitForData(netConfig.timeout))
471 	{
472 		if (myTcpConnection.leastSize < 2) return dnsQueryResult.timeout;
473 		
474 		ubyte[2] size;
475 		connProxStr.read(size);
476 		
477 		bufferSize = size[0]*256 + size[1];
478 	}
479 	else
480 	{
481 		if (!connProxStr.connected) return dnsQueryResult.connectionFailure;	
482 
483 		return dnsQueryResult.timeout;
484 	}
485 	
486 	ubyte[] buffer = new ubyte[](bufferSize);
487 
488 	if(connProxStr.waitForData(netConfig.timeout))
489 	{	
490 		if (connProxStr.leastSize < bufferSize) return dnsQueryResult.dataMissing;
491 
492 		// ToDo: This can be improved. What if octets come in several batches and time for last batch exceeds dnsTimeout limit?
493 		connProxStr.read(buffer);
494 
495 		if (buffer.length < bufferSize) return dnsQueryResult.dataMissing;
496 	}
497 	else
498 	{
499 		if (!connProxStr.connected) return dnsQueryResult.connectionFailure;	
500 		
501 		return dnsQueryResult.timeout;
502 	}
503 	
504 	responseData = buffer;
505 
506 	return dnsQueryResult.success;
507 }  // tlsQuery
508 
509 }  // version(ENABLE_TLS)