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)