diff --git a/DnsServerCore/Dns/DnsServer.cs b/DnsServerCore/Dns/DnsServer.cs index 71c22fe5..40415fdc 100644 --- a/DnsServerCore/Dns/DnsServer.cs +++ b/DnsServerCore/Dns/DnsServer.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,28 +23,40 @@ using DnsServerCore.Dns.ResourceRecords; using DnsServerCore.Dns.Trees; using DnsServerCore.Dns.ZoneManagers; using DnsServerCore.Dns.Zones; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Https; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Quic; using System.Net.Security; using System.Net.Sockets; using System.Runtime.ExceptionServices; using System.Security.Cryptography.X509Certificates; -using System.Text; using System.Threading; using System.Threading.Tasks; using TechnitiumLibrary; using TechnitiumLibrary.Net; using TechnitiumLibrary.Net.Dns; +using TechnitiumLibrary.Net.Dns.ClientConnection; using TechnitiumLibrary.Net.Dns.EDnsOptions; using TechnitiumLibrary.Net.Dns.ResourceRecords; -using TechnitiumLibrary.Net.Http; using TechnitiumLibrary.Net.Proxy; namespace DnsServerCore.Dns { +#pragma warning disable CA2252 // This API requires opting into preview features +#pragma warning disable CA1416 // Validate platform compatibility + public enum DnsServerRecursion : byte { Deny = 0, @@ -60,7 +72,7 @@ namespace DnsServerCore.Dns CustomAddress = 2 } - public sealed class DnsServer : IDisposable, IDnsClient + public sealed class DnsServer : IAsyncDisposable, IDisposable, IDnsClient { #region enum @@ -81,6 +93,8 @@ namespace DnsServerCore.Dns const int SERVE_STALE_WAIT_TIME = 1800; static readonly IPEndPoint IPENDPOINT_ANY_0 = new IPEndPoint(IPAddress.Any, 0); + static readonly IReadOnlyCollection _aRecords = new DnsARecordData[] { new DnsARecordData(IPAddress.Any) }; + static readonly IReadOnlyCollection _aaaaRecords = new DnsAAAARecordData[] { new DnsAAAARecordData(IPAddress.IPv6Any) }; string _serverDomain; readonly string _configFolder; @@ -92,16 +106,11 @@ namespace DnsServerCore.Dns readonly List _udpListeners = new List(); readonly List _tcpListeners = new List(); - readonly List _httpListeners = new List(); readonly List _tlsListeners = new List(); - readonly List _httpsListeners = new List(); + readonly List _quicListeners = new List(); - bool _enableDnsOverHttp; - bool _enableDnsOverTls; - bool _enableDnsOverHttps; - bool _isDnsOverHttpsEnabled; - X509Certificate2 _certificate; - IReadOnlyDictionary _tsigKeys; + WebApplication _dohPrivateWebService; + WebApplication _dohWebService; readonly AuthZoneManager _authZoneManager; readonly AllowedZoneManager _allowedZoneManager; @@ -111,51 +120,73 @@ namespace DnsServerCore.Dns readonly DnsApplicationManager _dnsApplicationManager; readonly ResolverDnsCache _dnsCache; + readonly StatsManager _stats; - readonly IReadOnlyCollection _aRecords = new DnsARecordData[] { new DnsARecordData(IPAddress.Any) }; - readonly IReadOnlyCollection _aaaaRecords = new DnsAAAARecordData[] { new DnsAAAARecordData(IPAddress.IPv6Any) }; - - DnsServerRecursion _recursion; - IReadOnlyCollection _recursionDeniedNetworks; - IReadOnlyCollection _recursionAllowedNetworks; - NetProxy _proxy; - IReadOnlyList _forwarders; bool _preferIPv6; ushort _udpPayloadSize = DnsDatagram.EDNS_DEFAULT_UDP_PAYLOAD_SIZE; - bool _randomizeName; - bool _qnameMinimization; - bool _nsRevalidation; bool _dnssecValidation = true; + bool _eDnsClientSubnet; byte _eDnsClientSubnetIPv4PrefixLength = 24; byte _eDnsClientSubnetIPv6PrefixLength = 56; + int _qpmLimitRequests = 0; int _qpmLimitErrors = 0; int _qpmLimitSampleMinutes = 5; int _qpmLimitIPv4PrefixLength = 24; int _qpmLimitIPv6PrefixLength = 56; - int _forwarderRetries = 3; - int _resolverRetries = 2; - int _forwarderTimeout = 2000; - int _resolverTimeout = 2000; + int _clientTimeout = 4000; - int _forwarderConcurrency = 2; + int _tcpSendTimeout = 10000; + int _tcpReceiveTimeout = 10000; + int _quicIdleTimeout = 60000; + int _quicMaxInboundStreams = 100; + int _listenBacklog = 100; + + bool _enableDnsOverHttp; + bool _enableDnsOverTls; + bool _enableDnsOverHttps; + bool _enableDnsOverHttpPort80; + bool _enableDnsOverQuic; + int _dnsOverHttpPort = 8053; + int _dnsOverTlsPort = 853; + int _dnsOverHttpsPort = 443; + int _dnsOverQuicPort = 853; + X509Certificate2 _certificate; + + IReadOnlyDictionary _tsigKeys; + + DnsServerRecursion _recursion; + IReadOnlyCollection _recursionDeniedNetworks; + IReadOnlyCollection _recursionAllowedNetworks; + + bool _randomizeName; + bool _qnameMinimization; + bool _nsRevalidation; + + int _resolverRetries = 2; + int _resolverTimeout = 2000; int _resolverMaxStackCount = 16; + bool _serveStale = true; int _cachePrefetchEligibility = 2; int _cachePrefetchTrigger = 9; int _cachePrefetchSampleIntervalInMinutes = 5; int _cachePrefetchSampleEligibilityHitsPerHour = 30; + bool _enableBlocking = true; bool _allowTxtBlockingReport = true; DnsServerBlockingType _blockingType = DnsServerBlockingType.AnyAddress; IReadOnlyCollection _customBlockingARecords = Array.Empty(); IReadOnlyCollection _customBlockingAAAARecords = Array.Empty(); - LogManager _queryLog; - readonly StatsManager _stats; - int _tcpSendTimeout = 10000; - int _tcpReceiveTimeout = 10000; + NetProxy _proxy; + IReadOnlyList _forwarders; + int _forwarderRetries = 3; + int _forwarderTimeout = 2000; + int _forwarderConcurrency = 2; + + LogManager _queryLog; Timer _cachePrefetchSamplingTimer; readonly object _cachePrefetchSamplingTimerLock = new object(); @@ -203,9 +234,6 @@ namespace DnsServerCore.Dns ThreadPool.SetMinThreads(minWorker, minIOC); } - - if (ServicePointManager.DefaultConnectionLimit < 10) - ServicePointManager.DefaultConnectionLimit = 10; //concurrent http request limit required when using DNS-over-HTTPS forwarders } public DnsServer(string serverDomain, string configFolder, string dohwwwFolder, LogManager log = null) @@ -243,31 +271,25 @@ namespace DnsServerCore.Dns bool _disposed; - private void Dispose(bool disposing) + public async ValueTask DisposeAsync() { if (_disposed) return; - if (disposing) - { - Stop(); + await StopAsync(); - if (_authZoneManager is not null) - _authZoneManager.Dispose(); + _authZoneManager?.Dispose(); - if (_dnsApplicationManager is not null) - _dnsApplicationManager.Dispose(); + _dnsApplicationManager?.Dispose(); - if (_stats is not null) - _stats.Dispose(); - } + _stats?.Dispose(); _disposed = true; } public void Dispose() { - Dispose(true); + DisposeAsync().Sync(); } #endregion @@ -328,7 +350,7 @@ namespace DnsServerCore.Dns if (result.RemoteEndPoint is not IPEndPoint remoteEP) continue; - if (IsQpmLimitCrossed(remoteEP)) + if (IsQpmLimitCrossed(remoteEP.Address)) continue; try @@ -346,9 +368,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, DnsTransportProtocol.Udp, ex); + _log?.Write(remoteEP, DnsTransportProtocol.Udp, ex); } } } @@ -369,10 +389,7 @@ namespace DnsServerCore.Dns if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped)) return; //server stopping - LogManager log = _log; - if (log != null) - log.Write(ex); - + _log?.Write(ex); break; } } @@ -381,9 +398,7 @@ namespace DnsServerCore.Dns if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped)) return; //server stopping - LogManager log = _log; - if (log is not null) - log.Write(ex); + _log?.Write(ex); } } @@ -391,7 +406,7 @@ namespace DnsServerCore.Dns { try { - DnsDatagram response = await PreProcessQueryAsync(request, remoteEP, DnsTransportProtocol.Udp, IsRecursionAllowed(remoteEP)); + DnsDatagram response = await PreProcessQueryAsync(request, remoteEP, DnsTransportProtocol.Udp, IsRecursionAllowed(remoteEP.Address)); if (response is null) return; //drop request @@ -434,10 +449,7 @@ namespace DnsServerCore.Dns await udpListener.SendToAsync(new ArraySegment(sendBuffer, 0, (int)sendBufferStream.Position), SocketFlags.None, remoteEP); } - LogManager queryLog = _queryLog; - if (queryLog is not null) - queryLog.Write(remoteEP, DnsTransportProtocol.Udp, request, response); - + _queryLog?.Write(remoteEP, DnsTransportProtocol.Udp, request, response); _stats.QueueUpdate(request, remoteEP, DnsTransportProtocol.Udp, response); } catch (Exception ex) @@ -445,17 +457,12 @@ namespace DnsServerCore.Dns if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped)) return; //server stopping - LogManager queryLog = _queryLog; - if (queryLog is not null) - queryLog.Write(remoteEP, DnsTransportProtocol.Udp, request, null); - - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, DnsTransportProtocol.Udp, ex); + _queryLog?.Write(remoteEP, DnsTransportProtocol.Udp, request, null); + _log?.Write(remoteEP, DnsTransportProtocol.Udp, ex); } } - private async Task AcceptConnectionAsync(Socket tcpListener, DnsTransportProtocol protocol, bool usingHttps) + private async Task AcceptConnectionAsync(Socket tcpListener, DnsTransportProtocol protocol) { IPEndPoint localEP = tcpListener.LocalEndPoint as IPEndPoint; @@ -469,7 +476,7 @@ namespace DnsServerCore.Dns { Socket socket = await tcpListener.AcceptAsync(); - _ = ProcessConnectionAsync(socket, protocol, usingHttps); + _ = ProcessConnectionAsync(socket, protocol); } } catch (SocketException ex) @@ -477,9 +484,7 @@ namespace DnsServerCore.Dns if (ex.SocketErrorCode == SocketError.OperationAborted) return; //server stopping - LogManager log = _log; - if (log is not null) - log.Write(localEP, protocol, ex); + _log?.Write(localEP, protocol, ex); } catch (ObjectDisposedException) { @@ -490,13 +495,11 @@ namespace DnsServerCore.Dns if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped)) return; //server stopping - LogManager log = _log; - if (log is not null) - log.Write(localEP, protocol, ex); + _log?.Write(localEP, protocol, ex); } } - private async Task ProcessConnectionAsync(Socket socket, DnsTransportProtocol protocol, bool usingHttps) + private async Task ProcessConnectionAsync(Socket socket, DnsTransportProtocol protocol) { IPEndPoint remoteEP = null; @@ -507,29 +510,18 @@ namespace DnsServerCore.Dns switch (protocol) { case DnsTransportProtocol.Tcp: - await ReadStreamRequestAsync(new NetworkStream(socket), _tcpReceiveTimeout, remoteEP, protocol); + await ReadStreamRequestAsync(new NetworkStream(socket), remoteEP, protocol); break; case DnsTransportProtocol.Tls: SslStream tlsStream = new SslStream(new NetworkStream(socket)); await tlsStream.AuthenticateAsServerAsync(_certificate); - await ReadStreamRequestAsync(tlsStream, _tcpReceiveTimeout, remoteEP, protocol); + await ReadStreamRequestAsync(tlsStream, remoteEP, protocol); break; - case DnsTransportProtocol.Https: - Stream stream = new NetworkStream(socket); - - if (usingHttps) - { - SslStream httpsStream = new SslStream(stream); - await httpsStream.AuthenticateAsServerAsync(_certificate); - - stream = httpsStream; - } - - await ProcessDoHRequestAsync(stream, _tcpReceiveTimeout, remoteEP, usingHttps); - break; + default: + throw new InvalidOperationException(); } } catch (IOException) @@ -538,28 +530,25 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, ex); + _log?.Write(remoteEP, protocol, ex); } finally { - if (socket is not null) - socket.Dispose(); + socket.Dispose(); } } - private async Task ReadStreamRequestAsync(Stream stream, int receiveTimeout, IPEndPoint remoteEP, DnsTransportProtocol protocol) + private async Task ReadStreamRequestAsync(Stream stream, IPEndPoint remoteEP, DnsTransportProtocol protocol) { try { using MemoryStream readBuffer = new MemoryStream(64); - using MemoryStream writeBuffer = new MemoryStream(4096); + using MemoryStream writeBuffer = new MemoryStream(2048); using SemaphoreSlim writeSemaphore = new SemaphoreSlim(1, 1); while (true) { - if (IsQpmLimitCrossed(remoteEP)) + if (IsQpmLimitCrossed(remoteEP.Address)) break; DnsDatagram request; @@ -569,7 +558,7 @@ namespace DnsServerCore.Dns { Task task = DnsDatagram.ReadFromTcpAsync(stream, readBuffer, cancellationTokenSource.Token); - if (await Task.WhenAny(task, Task.Delay(receiveTimeout, cancellationTokenSource.Token)) != task) + if (await Task.WhenAny(task, Task.Delay(_tcpReceiveTimeout, cancellationTokenSource.Token)) != task) { //read timed out await stream.DisposeAsync(); @@ -595,9 +584,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, ex); + _log?.Write(remoteEP, protocol, ex); } } @@ -605,7 +592,7 @@ namespace DnsServerCore.Dns { try { - DnsDatagram response = await PreProcessQueryAsync(request, remoteEP, protocol, IsRecursionAllowed(remoteEP)); + DnsDatagram response = await PreProcessQueryAsync(request, remoteEP, protocol, IsRecursionAllowed(remoteEP.Address)); if (response is null) { await stream.DisposeAsync(); @@ -625,10 +612,7 @@ namespace DnsServerCore.Dns writeSemaphore.Release(); } - LogManager queryLog = _queryLog; - if (queryLog is not null) - queryLog.Write(remoteEP, protocol, request, response); - + _queryLog?.Write(remoteEP, protocol, request, response); _stats.QueueUpdate(request, remoteEP, protocol, response); } catch (IOException) @@ -637,294 +621,258 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager queryLog = _queryLog; - if ((queryLog is not null) && (request is not null)) - queryLog.Write(remoteEP, protocol, request, null); + if (request is not null) + _queryLog.Write(remoteEP, protocol, request, null); - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, ex); + _log?.Write(remoteEP, protocol, ex); } } - private async Task ProcessDoHRequestAsync(Stream stream, int receiveTimeout, IPEndPoint remoteEP, bool usingHttps) + private async Task AcceptQuicConnectionAsync(QuicListener quicListener) { - DnsDatagram dnsRequest = null; - DnsTransportProtocol dnsProtocol = DnsTransportProtocol.Https; - try { while (true) { - bool isSocketRemoteIpPrivate = NetUtilities.IsPrivateIP(remoteEP.Address); - HttpRequest httpRequest; + QuicConnection quicConnection = await quicListener.AcceptConnectionAsync(); - if (usingHttps || !isSocketRemoteIpPrivate) - { - //is HTTPS request or is over public IP - if (IsQpmLimitCrossed(remoteEP)) - break; - - httpRequest = await HttpRequest.ReadRequestAsync(stream, 512).WithTimeout(receiveTimeout); - if (httpRequest is null) - return; //connection closed gracefully by client - } - else - { - //is HTTP request (probably via reverse proxy) and is over private IP - httpRequest = await HttpRequest.ReadRequestAsync(stream, 512).WithTimeout(receiveTimeout); - if (httpRequest is null) - return; //connection closed gracefully by client - - string xRealIp = httpRequest.Headers["X-Real-IP"]; - if (IPAddress.TryParse(xRealIp, out IPAddress address)) - { - //get the real IP address of the requesting client from X-Real-IP header set in nginx proxy_pass block - remoteEP = new IPEndPoint(address, 0); - } - - if (IsQpmLimitCrossed(remoteEP)) - break; - } - - string requestConnection = httpRequest.Headers[HttpRequestHeader.Connection]; - if (string.IsNullOrEmpty(requestConnection)) - requestConnection = "close"; - - switch (httpRequest.RequestPath) - { - case "/dns-query": - if (!usingHttps && !isSocketRemoteIpPrivate) - { - //intentionally blocking public IP addresses from using DNS-over-HTTP (without TLS) - //this feature is intended to be used with an SSL terminated reverse proxy like nginx on private network - await SendErrorAsync(stream, "close", 403, "DNS-over-HTTPS (DoH) queries are supported only on HTTPS."); - return; - } - - DnsTransportProtocol protocol = DnsTransportProtocol.Udp; - - string strRequestAcceptTypes = httpRequest.Headers[HttpRequestHeader.Accept]; - if (string.IsNullOrEmpty(strRequestAcceptTypes)) - { - string strContentType = httpRequest.Headers[HttpRequestHeader.ContentType]; - if (strContentType == "application/dns-message") - protocol = DnsTransportProtocol.Https; - } - else - { - foreach (string acceptType in strRequestAcceptTypes.Split(',')) - { - if (acceptType == "application/dns-message") - { - protocol = DnsTransportProtocol.Https; - break; - } - } - } - - switch (protocol) - { - case DnsTransportProtocol.Https: - #region https wire format - { - switch (httpRequest.HttpMethod) - { - case "GET": - string strRequest = httpRequest.QueryString["dns"]; - if (string.IsNullOrEmpty(strRequest)) - throw new DnsServerException("Missing query string parameter: dns"); - - //convert from base64url to base64 - strRequest = strRequest.Replace('-', '+'); - strRequest = strRequest.Replace('_', '/'); - - //add padding - int x = strRequest.Length % 4; - if (x > 0) - strRequest = strRequest.PadRight(strRequest.Length - x + 4, '='); - - using (MemoryStream mS = new MemoryStream(Convert.FromBase64String(strRequest))) - { - dnsRequest = DnsDatagram.ReadFrom(mS); - } - - break; - - case "POST": - string strContentType = httpRequest.Headers[HttpRequestHeader.ContentType]; - if (string.IsNullOrEmpty(strContentType)) - throw new DnsServerException("Missing Content-Type header."); - - if (strContentType != "application/dns-message") - throw new NotSupportedException("DNS request type not supported: " + strContentType); - - using (MemoryStream mS = new MemoryStream(32)) - { - await httpRequest.InputStream.CopyToAsync(mS, 32); - - mS.Position = 0; - dnsRequest = DnsDatagram.ReadFrom(mS); - } - - break; - - default: - throw new NotSupportedException("DoH request type not supported."); - } - - DnsDatagram dnsResponse = await PreProcessQueryAsync(dnsRequest, remoteEP, protocol, IsRecursionAllowed(remoteEP)); - if (dnsResponse is null) - return; //drop request - - using (MemoryStream mS = new MemoryStream(512)) - { - dnsResponse.WriteTo(mS); - - mS.Position = 0; - await SendContentAsync(stream, requestConnection, "application/dns-message", mS); - } - - LogManager queryLog = _queryLog; - if (queryLog is not null) - queryLog.Write(remoteEP, protocol, dnsRequest, dnsResponse); - - _stats.QueueUpdate(dnsRequest, remoteEP, protocol, dnsResponse); - } - #endregion - break; - - default: - await RedirectAsync(stream, httpRequest.Protocol, requestConnection, (usingHttps ? "https://" : "http://") + httpRequest.Headers[HttpRequestHeader.Host]); - break; - } - - if (requestConnection.Equals("close", StringComparison.OrdinalIgnoreCase)) - return; - - break; - - default: - string path = httpRequest.RequestPath; - - if (!path.StartsWith("/") || path.Contains("/../") || path.Contains("/.../")) - { - await SendErrorAsync(stream, requestConnection, 404); - break; - } - - if (path == "/") - path = "/index.html"; - - path = Path.GetFullPath(_dohwwwFolder + path.Replace('/', Path.DirectorySeparatorChar)); - - if (!path.StartsWith(_dohwwwFolder) || !File.Exists(path)) - { - await SendErrorAsync(stream, requestConnection, 404); - break; - } - - await SendFileAsync(stream, requestConnection, path); - break; - } + _ = ProcessQuicConnectionAsync(quicConnection); } } - catch (TimeoutException) + catch (ObjectDisposedException) { - //ignore timeout exception - } - catch (IOException) - { - //ignore IO exceptions + //server stopped } catch (Exception ex) { - LogManager queryLog = _queryLog; - if ((queryLog is not null) && (dnsRequest is not null)) - queryLog.Write(remoteEP, dnsProtocol, dnsRequest, null); + if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped)) + return; //server stopping - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, dnsProtocol, ex); - - await SendErrorAsync(stream, "close", ex); + _log?.Write(quicListener.LocalEndPoint, DnsTransportProtocol.Quic, ex); } } - private static async Task SendContentAsync(Stream outputStream, string connection, string contentType, Stream content) - { - byte[] bufferHeader = Encoding.UTF8.GetBytes("HTTP/1.1 200 OK\r\nDate: " + DateTime.UtcNow.ToString("r") + "\r\nContent-Type: " + contentType + "\r\nContent-Length: " + content.Length + "\r\nX-Robots-Tag: noindex, nofollow\r\nConnection: " + connection + "\r\n\r\n"); - - await outputStream.WriteAsync(bufferHeader); - await content.CopyToAsync(outputStream); - await outputStream.FlushAsync(); - } - - private static Task SendErrorAsync(Stream outputStream, string connection, Exception ex) - { - return SendErrorAsync(outputStream, connection, 500, ex.ToString()); - } - - private static async Task SendErrorAsync(Stream outputStream, string connection, int statusCode, string message = null) + private async Task ProcessQuicConnectionAsync(QuicConnection quicConnection) { try { - string statusString = statusCode + " " + GetHttpStatusString((HttpStatusCode)statusCode); - byte[] bufferContent = Encoding.UTF8.GetBytes("" + statusString + "

" + statusString + "

" + (message is null ? "" : "

" + message + "

") + ""); - byte[] bufferHeader = Encoding.UTF8.GetBytes("HTTP/1.1 " + statusString + "\r\nDate: " + DateTime.UtcNow.ToString("r") + "\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " + bufferContent.Length + "\r\nX-Robots-Tag: noindex, nofollow\r\nConnection: " + connection + "\r\n\r\n"); + while (true) + { + if (IsQpmLimitCrossed(quicConnection.RemoteEndPoint.Address)) + break; - await outputStream.WriteAsync(bufferHeader); - await outputStream.WriteAsync(bufferContent); - await outputStream.FlushAsync(); + QuicStream quicStream = await quicConnection.AcceptInboundStreamAsync(); + + _ = ProcessQuicStreamRequestAsync(quicStream, quicConnection.RemoteEndPoint); + } + } + catch (QuicException ex) + { + switch (ex.QuicError) + { + case QuicError.ConnectionIdle: + case QuicError.ConnectionAborted: + case QuicError.ConnectionTimeout: + break; + + default: + _log?.Write(quicConnection.RemoteEndPoint, DnsTransportProtocol.Quic, ex); + break; + } + } + catch (Exception ex) + { + _log?.Write(quicConnection.RemoteEndPoint, DnsTransportProtocol.Quic, ex); + } + finally + { + await quicConnection.DisposeAsync(); } - catch - { } } - private static async Task RedirectAsync(Stream outputStream, string protocol, string connection, string location) + private async Task ProcessQuicStreamRequestAsync(QuicStream quicStream, IPEndPoint remoteEP) { + MemoryStream sharedBuffer = new MemoryStream(512); + DnsDatagram request = null; + try { - string statusString = "302 Found"; - byte[] bufferContent = Encoding.UTF8.GetBytes("" + statusString + "

" + statusString + "

Location: " + location + "

"); - byte[] bufferHeader = Encoding.UTF8.GetBytes(protocol + " " + statusString + "\r\nDate: " + DateTime.UtcNow.ToString("r") + "\r\nLocation: " + location + "\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " + bufferContent.Length + "\r\nX-Robots-Tag: noindex, nofollow\r\nConnection: " + connection + "\r\n\r\n"); + //read dns datagram with timeout + using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource()) + { + Task task = DnsDatagram.ReadFromTcpAsync(quicStream, sharedBuffer, cancellationTokenSource.Token); - await outputStream.WriteAsync(bufferHeader); - await outputStream.WriteAsync(bufferContent); - await outputStream.FlushAsync(); + if (await Task.WhenAny(task, Task.Delay(_tcpReceiveTimeout, cancellationTokenSource.Token)) != task) + { + //read timed out + quicStream.Abort(QuicAbortDirection.Both, (long)DnsOverQuicErrorCodes.DOQ_UNSPECIFIED_ERROR); + return; + } + + cancellationTokenSource.Cancel(); //cancel delay task + + request = await task; + } + + //process request async + DnsDatagram response = await PreProcessQueryAsync(request, remoteEP, DnsTransportProtocol.Quic, IsRecursionAllowed(remoteEP.Address)); + if (response is null) + return; //drop request + + //send response + await response.WriteToTcpAsync(quicStream, sharedBuffer); + + _queryLog?.Write(remoteEP, DnsTransportProtocol.Quic, request, response); + _stats.QueueUpdate(request, remoteEP, DnsTransportProtocol.Quic, response); } - catch - { } - } - - private static async Task SendFileAsync(Stream outputStream, string connection, string filePath) - { - using (FileStream fS = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + catch (IOException) { - byte[] bufferHeader = Encoding.UTF8.GetBytes("HTTP/1.1 200 OK\r\nDate: " + DateTime.UtcNow.ToString("r") + "\r\nContent-Type: " + WebUtilities.GetContentType(filePath).MediaType + "\r\nContent-Length: " + fS.Length + "\r\nCache-Control: private, max-age=300\r\nX-Robots-Tag: noindex, nofollow\r\nConnection: " + connection + "\r\n\r\n"); - - await outputStream.WriteAsync(bufferHeader); - await fS.CopyToAsync(outputStream); - await outputStream.FlushAsync(); + //ignore QuicException / IOException } - } - - internal static string GetHttpStatusString(HttpStatusCode statusCode) - { - StringBuilder sb = new StringBuilder(); - - foreach (char c in statusCode.ToString().ToCharArray()) + catch (Exception ex) { - if (char.IsUpper(c) && sb.Length > 0) - sb.Append(' '); + if (request is not null) + _queryLog.Write(remoteEP, DnsTransportProtocol.Quic, request, null); - sb.Append(c); + _log?.Write(remoteEP, DnsTransportProtocol.Quic, ex); + } + finally + { + await sharedBuffer.DisposeAsync(); + await quicStream.DisposeAsync(); } - - return sb.ToString(); } - private bool IsRecursionAllowed(IPEndPoint remoteEP) + private async Task ProcessDoHRequestAsync(HttpContext context) + { + IPEndPoint remoteEP = context.GetRemoteEndPoint(); + DnsDatagram dnsRequest = null; + + try + { + HttpRequest request = context.Request; + HttpResponse response = context.Response; + + if (IsQpmLimitCrossed(remoteEP.Address)) + { + response.StatusCode = 429; + await response.WriteAsync("Too Many Requests"); + return; + } + + if (!request.IsHttps && !NetUtilities.IsPrivateIP(remoteEP.Address)) + { + //intentionally blocking public IP addresses from using DNS-over-HTTP (without TLS) + //this feature is intended to be used with an SSL terminated reverse proxy like nginx on private network + response.StatusCode = 403; + await response.WriteAsync("DNS-over-HTTPS (DoH) queries are supported only on HTTPS."); + return; + } + + switch (request.Method) + { + case "GET": + bool acceptsDoH = false; + string requestAccept = request.Headers["Accept"]; + if (!string.IsNullOrEmpty(requestAccept)) + { + foreach (string mediaType in requestAccept.Split(',')) + { + if (mediaType.Equals("application/dns-message", StringComparison.OrdinalIgnoreCase)) + { + acceptsDoH = true; + break; + } + } + } + + if (!acceptsDoH) + { + response.Redirect((request.IsHttps ? "https://" : "http://") + request.Headers["Host"]); + return; + } + + string dnsRequestBase64Url = request.Query["dns"]; + if (string.IsNullOrEmpty(dnsRequestBase64Url)) + { + response.StatusCode = 400; + await response.WriteAsync("Bad Request"); + return; + } + + //convert from base64url to base64 + dnsRequestBase64Url = dnsRequestBase64Url.Replace('-', '+'); + dnsRequestBase64Url = dnsRequestBase64Url.Replace('_', '/'); + + //add padding + int x = dnsRequestBase64Url.Length % 4; + if (x > 0) + dnsRequestBase64Url = dnsRequestBase64Url.PadRight(dnsRequestBase64Url.Length - x + 4, '='); + + using (MemoryStream mS = new MemoryStream(Convert.FromBase64String(dnsRequestBase64Url))) + { + dnsRequest = DnsDatagram.ReadFrom(mS); + } + + break; + + case "POST": + if (!string.Equals(request.Headers["Content-Type"], "application/dns-message", StringComparison.OrdinalIgnoreCase)) + { + response.StatusCode = 415; + await response.WriteAsync("Unsupported Media Type"); + return; + } + + using (MemoryStream mS = new MemoryStream(32)) + { + await request.Body.CopyToAsync(mS, 32); + + mS.Position = 0; + dnsRequest = DnsDatagram.ReadFrom(mS); + } + + break; + + default: + throw new InvalidOperationException(); + } + + DnsDatagram dnsResponse = await PreProcessQueryAsync(dnsRequest, remoteEP, DnsTransportProtocol.Https, IsRecursionAllowed(remoteEP.Address)); + if (dnsResponse is null) + { + //drop request + context.Connection.RequestClose(); + return; + } + + using (MemoryStream mS = new MemoryStream(512)) + { + dnsResponse.WriteTo(mS); + + mS.Position = 0; + response.ContentType = "application/dns-message"; + response.ContentLength = mS.Length; + + using (Stream s = response.Body) + { + await mS.CopyToAsync(s, 512); + } + } + + _queryLog?.Write(remoteEP, DnsTransportProtocol.Https, dnsRequest, dnsResponse); + _stats.QueueUpdate(dnsRequest, remoteEP, DnsTransportProtocol.Https, dnsResponse); + } + catch (Exception ex) + { + if (dnsRequest is not null) + _queryLog?.Write(remoteEP, DnsTransportProtocol.Https, dnsRequest, null); + + _log?.Write(remoteEP, DnsTransportProtocol.Https, ex); + } + } + + private bool IsRecursionAllowed(IPAddress remoteIP) { switch (_recursion) { @@ -932,24 +880,22 @@ namespace DnsServerCore.Dns return true; case DnsServerRecursion.AllowOnlyForPrivateNetworks: - switch (remoteEP.AddressFamily) + switch (remoteIP.AddressFamily) { case AddressFamily.InterNetwork: case AddressFamily.InterNetworkV6: - return NetUtilities.IsPrivateIP(remoteEP.Address); + return NetUtilities.IsPrivateIP(remoteIP); default: return false; } case DnsServerRecursion.UseSpecifiedNetworks: - IPAddress address = remoteEP.Address; - if (_recursionDeniedNetworks is not null) { foreach (NetworkAddress deniedNetworkAddress in _recursionDeniedNetworks) { - if (deniedNetworkAddress.Contains(address)) + if (deniedNetworkAddress.Contains(remoteIP)) return false; } } @@ -958,12 +904,12 @@ namespace DnsServerCore.Dns { foreach (NetworkAddress allowedNetworkAddress in _recursionAllowedNetworks) { - if (allowedNetworkAddress.Contains(address)) + if (allowedNetworkAddress.Contains(remoteIP)) return true; } } - if (IPAddress.IsLoopback(address)) + if (IPAddress.IsLoopback(remoteIP)) return true; return false; @@ -991,9 +937,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, ex); + _log?.Write(remoteEP, protocol, ex); } } @@ -1001,11 +945,7 @@ namespace DnsServerCore.Dns { //format error if (request.ParsingException is not IOException) - { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, request.ParsingException); - } + _log?.Write(remoteEP, protocol, request.ParsingException); //format error response return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.FormatError, request.Question, null, null, null, request.EDNS is null ? ushort.MinValue : _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None) { Tag = DnsServerResponseType.Authoritative }; @@ -1015,9 +955,7 @@ namespace DnsServerCore.Dns { if (!request.VerifySignedRequest(_tsigKeys, out DnsDatagram unsignedRequest, out DnsDatagram errorResponse)) { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server received a request that failed TSIG signature verification (RCODE: " + errorResponse.RCODE + "; TSIG Error: " + errorResponse.TsigError + ")"); + _log?.Write(remoteEP, protocol, "DNS Server received a request that failed TSIG signature verification (RCODE: " + errorResponse.RCODE + "; TSIG Error: " + errorResponse.TsigError + ")"); errorResponse.Tag = DnsServerResponseType.Authoritative; return errorResponse; @@ -1046,9 +984,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, ex); + _log?.Write(remoteEP, protocol, ex); } } @@ -1145,9 +1081,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, ex); + _log?.Write(remoteEP, protocol, ex); return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.ServerFailure, request.Question) { Tag = DnsServerResponseType.Authoritative }; } @@ -1183,18 +1117,14 @@ namespace DnsServerCore.Dns } } - LogManager log = _log; - if (!remoteVerified) { - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server refused a NOTIFY request since the request IP address was not recognized by the secondary zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server refused a NOTIFY request since the request IP address was not recognized by the secondary zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); return new DnsDatagram(request.Identifier, true, DnsOpcode.Notify, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative }; } - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server received a NOTIFY request for secondary zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server received a NOTIFY request for secondary zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); if ((request.Answer.Count > 0) && (request.Answer[0].Type == DnsResourceRecordType.SOA)) { @@ -1223,9 +1153,7 @@ namespace DnsServerCore.Dns if ((authZoneInfo is null) || authZoneInfo.Disabled) return new DnsDatagram(request.Identifier, true, DnsOpcode.Update, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NotAuth, request.Question) { Tag = DnsServerResponseType.Authoritative }; - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server received a zone UPDATE request for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server received a zone UPDATE request for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); async Task IsZoneNameServerAllowedAsync() { @@ -1287,8 +1215,7 @@ namespace DnsServerCore.Dns if (!isUpdateAllowed) { - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server refused a zone UPDATE request since the request IP address is not allowed by the zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server refused a zone UPDATE request since the request IP address is not allowed by the zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); return false; } @@ -1298,8 +1225,7 @@ namespace DnsServerCore.Dns { if ((tsigAuthenticatedKeyName is null) || !authZoneInfo.UpdateSecurityPolicies.TryGetValue(tsigAuthenticatedKeyName.ToLower(), out IReadOnlyDictionary> policyMap)) { - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server refused a zone UPDATE request since the request is missing TSIG auth required by the zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server refused a zone UPDATE request since the request is missing TSIG auth required by the zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); return false; } @@ -1464,8 +1390,7 @@ namespace DnsServerCore.Dns //check for permissions if (!await IsUpdatePermittedAsync()) { - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server refused a zone UPDATE request due to Dynamic Updates Security Policy for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server refused a zone UPDATE request due to Dynamic Updates Security Policy for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); return new DnsDatagram(request.Identifier, true, DnsOpcode.Update, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative }; } @@ -1707,8 +1632,7 @@ namespace DnsServerCore.Dns _authZoneManager.SaveZoneFile(authZoneInfo.Name); - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server successfully processed a zone UPDATE request for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server successfully processed a zone UPDATE request for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); //NOERROR return new DnsDatagram(request.Identifier, true, DnsOpcode.Update, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question) { Tag = DnsServerResponseType.Authoritative }; @@ -1720,37 +1644,45 @@ namespace DnsServerCore.Dns IReadOnlyList primaryNameServers = await authZoneInfo.GetPrimaryNameServerAddressesAsync(this); DnsResourceRecord soaRecord = authZoneInfo.GetApexRecords(DnsResourceRecordType.SOA)[0]; - DnsResourceRecordInfo recordInfo = soaRecord.GetRecordInfo(); + AuthRecordInfo recordInfo = soaRecord.GetAuthRecordInfo(); - if (recordInfo.ZoneTransferProtocol == DnsTransportProtocol.Tls) + switch (recordInfo.ZoneTransferProtocol) { - //change name server protocol to TLS - List tcpNameServers = new List(primaryNameServers.Count); + case DnsTransportProtocol.Tls: + case DnsTransportProtocol.Quic: + { + //change name server protocol to TLS/QUIC + List updatedNameServers = new List(primaryNameServers.Count); - foreach (NameServerAddress primaryNameServer in primaryNameServers) - { - if (primaryNameServer.Protocol == DnsTransportProtocol.Tls) - tcpNameServers.Add(primaryNameServer); - else - tcpNameServers.Add(primaryNameServer.ChangeProtocol(DnsTransportProtocol.Tls)); - } + foreach (NameServerAddress primaryNameServer in primaryNameServers) + { + if (primaryNameServer.Protocol == recordInfo.ZoneTransferProtocol) + updatedNameServers.Add(primaryNameServer); + else + updatedNameServers.Add(primaryNameServer.ChangeProtocol(recordInfo.ZoneTransferProtocol)); + } - primaryNameServers = tcpNameServers; - } - else if (protocol == DnsTransportProtocol.Tcp) - { - //change name server protocol to TCP - List tcpNameServers = new List(primaryNameServers.Count); + primaryNameServers = updatedNameServers; + } + break; - foreach (NameServerAddress primaryNameServer in primaryNameServers) - { - if (primaryNameServer.Protocol == DnsTransportProtocol.Tcp) - tcpNameServers.Add(primaryNameServer); - else - tcpNameServers.Add(primaryNameServer.ChangeProtocol(DnsTransportProtocol.Tcp)); - } + default: + if (protocol == DnsTransportProtocol.Tcp) + { + //change name server protocol to TCP + List updatedNameServers = new List(primaryNameServers.Count); - primaryNameServers = tcpNameServers; + foreach (NameServerAddress primaryNameServer in primaryNameServers) + { + if (primaryNameServer.Protocol == DnsTransportProtocol.Tcp) + updatedNameServers.Add(primaryNameServer); + else + updatedNameServers.Add(primaryNameServer.ChangeProtocol(DnsTransportProtocol.Tcp)); + } + + primaryNameServers = updatedNameServers; + } + break; } TsigKey key = null; @@ -1788,13 +1720,10 @@ namespace DnsServerCore.Dns private async Task ProcessZoneTransferQueryAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, string tsigAuthenticatedKeyName) { - LogManager log = _log; - AuthZoneInfo authZoneInfo = _authZoneManager.GetAuthZoneInfo(request.Question[0].Name); if ((authZoneInfo is null) || authZoneInfo.Disabled || authZoneInfo.IsExpired) { - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server refused a zone transfer request due to zone not found, zone disabled, or zone expired reasons for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server refused a zone transfer request due to zone not found, zone disabled, or zone expired reasons for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative }; } @@ -1806,8 +1735,7 @@ namespace DnsServerCore.Dns break; default: - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server refused a zone transfer request since the DNS server is not authoritative for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server refused a zone transfer request since the DNS server is not authoritative for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative }; } @@ -1868,8 +1796,7 @@ namespace DnsServerCore.Dns if (!isZoneTransferAllowed) { - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server refused a zone transfer request since the request IP address is not allowed by the zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server refused a zone transfer request since the request IP address is not allowed by the zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative }; } @@ -1878,15 +1805,13 @@ namespace DnsServerCore.Dns { if ((tsigAuthenticatedKeyName is null) || !authZoneInfo.ZoneTransferTsigKeyNames.ContainsKey(tsigAuthenticatedKeyName.ToLower())) { - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server refused a zone transfer request since the request is missing TSIG auth required by the zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server refused a zone transfer request since the request is missing TSIG auth required by the zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative }; } } - if (log is not null) - log.Write(remoteEP, protocol, "DNS Server received zone transfer request for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); + _log?.Write(remoteEP, protocol, "DNS Server received zone transfer request for zone: " + (authZoneInfo.Name == "" ? "" : authZoneInfo.Name)); IReadOnlyList xfrRecords; @@ -1929,9 +1854,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, ex); + _log?.Write(remoteEP, protocol, ex); } } } @@ -2055,16 +1978,12 @@ namespace DnsServerCore.Dns } else { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, "DNS request handler '" + appRecord.ClassPath + "' was not found in the application '" + appRecord.AppName + "': " + appResourceRecord.Name); + _log?.Write(remoteEP, protocol, "DNS request handler '" + appRecord.ClassPath + "' was not found in the application '" + appRecord.AppName + "': " + appResourceRecord.Name); } } else { - LogManager log = _log; - if (log is not null) - log.Write(remoteEP, protocol, "DNS application '" + appRecord.AppName + "' was not found: " + appResourceRecord.Name); + _log?.Write(remoteEP, protocol, "DNS application '" + appRecord.AppName + "' was not found: " + appResourceRecord.Name); } //return server failure response with SOA @@ -2426,7 +2345,7 @@ namespace DnsServerCore.Dns DateTime utcNow = DateTime.UtcNow; foreach (DnsResourceRecord record in authority) - record.GetRecordInfo().LastUsedOn = utcNow; + record.GetAuthRecordInfo().LastUsedOn = utcNow; } } @@ -2951,8 +2870,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) + if (_log is not null) { string strForwarders = null; @@ -2979,7 +2897,7 @@ namespace DnsServerCore.Dns } } - log.Write("DNS Server failed to resolve the request with QNAME: " + question.Name + "; QTYPE: " + question.Type.ToString() + "; QCLASS: " + question.Class.ToString() + (strForwarders is null ? "" : "; Forwarders: " + strForwarders) + ";\r\n" + ex.ToString()); + _log.Write("DNS Server failed to resolve the request with QNAME: " + question.Name + "; QTYPE: " + question.Type.ToString() + "; QCLASS: " + question.Class.ToString() + (strForwarders is null ? "" : "; Forwarders: " + strForwarders) + ";\r\n" + ex.ToString()); } if (_serveStale) @@ -3359,9 +3277,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(ex); + _log?.Write(ex); } } @@ -3391,9 +3307,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(ex); + _log?.Write(ex); cacheRefreshSampleList[sampleQuestionIndex] = sample; //put back into sample list to allow refreshing it again } @@ -3535,9 +3449,7 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(ex); + _log?.Write(ex); } finally { @@ -3577,16 +3489,13 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(ex); + _log?.Write(ex); } finally { lock (_cachePrefetchRefreshTimerLock) { - if (_cachePrefetchRefreshTimer is not null) - _cachePrefetchRefreshTimer.Change((_cachePrefetchTrigger + 1) * 1000, Timeout.Infinite); + _cachePrefetchRefreshTimer?.Change((_cachePrefetchTrigger + 1) * 1000, Timeout.Infinite); } } } @@ -3602,16 +3511,13 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(ex); + _log?.Write(ex); } finally { lock (_cacheMaintenanceTimerLock) { - if (_cacheMaintenanceTimer is not null) - _cacheMaintenanceTimer.Change(CACHE_MAINTENANCE_TIMER_PERIODIC_INTERVAL, Timeout.Infinite); + _cacheMaintenanceTimer?.Change(CACHE_MAINTENANCE_TIMER_PERIODIC_INTERVAL, Timeout.Infinite); } } } @@ -3622,14 +3528,12 @@ namespace DnsServerCore.Dns { lock (_cachePrefetchSamplingTimerLock) { - if (_cachePrefetchSamplingTimer is not null) - _cachePrefetchSamplingTimer.Change(Timeout.Infinite, Timeout.Infinite); + _cachePrefetchSamplingTimer?.Change(Timeout.Infinite, Timeout.Infinite); } lock (_cachePrefetchRefreshTimerLock) { - if (_cachePrefetchRefreshTimer is not null) - _cachePrefetchRefreshTimer.Change(Timeout.Infinite, Timeout.Infinite); + _cachePrefetchRefreshTimer?.Change(Timeout.Infinite, Timeout.Infinite); } } else if (_state == ServiceState.Running) @@ -3645,19 +3549,16 @@ namespace DnsServerCore.Dns lock (_cachePrefetchRefreshTimerLock) { - if (_cachePrefetchRefreshTimer is not null) - _cachePrefetchRefreshTimer.Change(CACHE_PREFETCH_REFRESH_TIMER_INITIAL_INTEVAL, Timeout.Infinite); + _cachePrefetchRefreshTimer?.Change(CACHE_PREFETCH_REFRESH_TIMER_INITIAL_INTEVAL, Timeout.Infinite); } } } - private bool IsQpmLimitCrossed(IPEndPoint remoteEP) + private bool IsQpmLimitCrossed(IPAddress remoteIP) { if ((_qpmLimitRequests < 1) && (_qpmLimitErrors < 1)) return false; - IPAddress remoteIP = remoteEP.Address; - if (IPAddress.IsLoopback(remoteIP)) return false; @@ -3702,16 +3603,13 @@ namespace DnsServerCore.Dns } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(ex); + _log?.Write(ex); } finally { lock (_qpmLimitSamplingTimerLock) { - if (_qpmLimitSamplingTimer is not null) - _qpmLimitSamplingTimer.Change(QPM_LIMIT_SAMPLING_TIMER_INTERVAL, Timeout.Infinite); + _qpmLimitSamplingTimer?.Change(QPM_LIMIT_SAMPLING_TIMER_INTERVAL, Timeout.Infinite); } } } @@ -3722,8 +3620,7 @@ namespace DnsServerCore.Dns { lock (_qpmLimitSamplingTimerLock) { - if (_qpmLimitSamplingTimer is not null) - _qpmLimitSamplingTimer.Change(Timeout.Infinite, Timeout.Infinite); + _qpmLimitSamplingTimer?.Change(Timeout.Infinite, Timeout.Infinite); _qpmLimitClientSubnetStats = null; _qpmLimitErrorClientSubnetStats = null; @@ -3733,8 +3630,7 @@ namespace DnsServerCore.Dns { lock (_qpmLimitSamplingTimerLock) { - if (_qpmLimitSamplingTimer is not null) - _qpmLimitSamplingTimer.Change(0, Timeout.Infinite); + _qpmLimitSamplingTimer?.Change(0, Timeout.Infinite); } } } @@ -3768,9 +3664,301 @@ namespace DnsServerCore.Dns #endregion + #region doh web service + + private async Task StartDoHPrivateAsync() + { + WebApplicationBuilder builder = WebApplication.CreateBuilder(); + + builder.Environment.ContentRootFileProvider = new PhysicalFileProvider(Path.GetDirectoryName(_dohwwwFolder)) + { + UseActivePolling = true, + UsePollingFileWatcher = true + }; + + builder.Environment.WebRootFileProvider = new PhysicalFileProvider(_dohwwwFolder) + { + UseActivePolling = true, + UsePollingFileWatcher = true + }; + + IReadOnlyList localAddresses = GetValidKestralLocalAddresses(_localEndPoints.Convert(delegate (IPEndPoint ep) { return ep.Address; })); + + builder.WebHost.ConfigureKestrel(delegate (WebHostBuilderContext context, KestrelServerOptions serverOptions) + { + foreach (IPAddress localAddress in localAddresses) + serverOptions.Listen(localAddress, _dnsOverHttpPort); + + serverOptions.AddServerHeader = false; + serverOptions.Limits.RequestHeadersTimeout = TimeSpan.FromMilliseconds(_tcpReceiveTimeout); + serverOptions.Limits.KeepAliveTimeout = TimeSpan.FromMilliseconds(_tcpReceiveTimeout); + serverOptions.Limits.MaxRequestHeadersTotalSize = 4096; + serverOptions.Limits.MaxRequestLineSize = serverOptions.Limits.MaxRequestHeadersTotalSize; + serverOptions.Limits.MaxRequestBufferSize = serverOptions.Limits.MaxRequestLineSize; + serverOptions.Limits.MaxRequestBodySize = 64 * 1024; + serverOptions.Limits.MaxResponseBufferSize = 4096; + }); + + builder.Logging.ClearProviders(); + + _dohPrivateWebService = builder.Build(); + + _dohPrivateWebService.UseDefaultFiles(); + _dohPrivateWebService.UseStaticFiles(new StaticFileOptions() + { + OnPrepareResponse = delegate (StaticFileResponseContext ctx) + { + ctx.Context.Response.Headers.Add("X-Robots-Tag", "noindex, nofollow"); + ctx.Context.Response.Headers.Add("Cache-Control", "private, max-age=300"); + } + }); + + _dohPrivateWebService.UseRouting(); + _dohPrivateWebService.MapGet("/dns-query", ProcessDoHRequestAsync); + _dohPrivateWebService.MapPost("/dns-query", ProcessDoHRequestAsync); + + try + { + await _dohPrivateWebService.StartAsync(); + + if (_log is not null) + { + foreach (IPAddress localAddress in localAddresses) + _log?.Write(new IPEndPoint(localAddress, _dnsOverHttpPort), "Http", "DNS Server was bound successfully."); + } + } + catch (Exception ex) + { + await StopDoHPrivateAsync(); + + if (_log is not null) + { + foreach (IPAddress localAddress in localAddresses) + _log?.Write(new IPEndPoint(localAddress, _dnsOverHttpPort), "Http", "DNS Server failed to bind."); + + _log?.Write(ex); + } + } + } + + private async Task StopDoHPrivateAsync() + { + if (_dohPrivateWebService is not null) + { + await _dohPrivateWebService.DisposeAsync(); + _dohPrivateWebService = null; + } + } + + private async Task StartDoHAsync() + { + WebApplicationBuilder builder = WebApplication.CreateBuilder(); + + builder.Environment.ContentRootFileProvider = new PhysicalFileProvider(Path.GetDirectoryName(_dohwwwFolder)) + { + UseActivePolling = true, + UsePollingFileWatcher = true + }; + + builder.Environment.WebRootFileProvider = new PhysicalFileProvider(_dohwwwFolder) + { + UseActivePolling = true, + UsePollingFileWatcher = true + }; + + IReadOnlyList localAddresses = GetValidKestralLocalAddresses(_localEndPoints.Convert(delegate (IPEndPoint ep) { return ep.Address; })); + + builder.WebHost.ConfigureKestrel(delegate (WebHostBuilderContext context, KestrelServerOptions serverOptions) + { + //bind to http port 80 for certbot webroot support + if (_enableDnsOverHttpPort80) + { + foreach (IPAddress localAddress in localAddresses) + serverOptions.Listen(localAddress, 80); + } + + //bind to https port + if (_certificate is not null) + { + serverOptions.ConfigureHttpsDefaults(delegate (HttpsConnectionAdapterOptions configureOptions) + { + configureOptions.ServerCertificateSelector = delegate (ConnectionContext context, string dnsName) + { + return _certificate; + }; + }); + + foreach (IPAddress localAddress in localAddresses) + { + serverOptions.Listen(localAddress, _dnsOverHttpsPort, delegate (ListenOptions listenOptions) + { + listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + listenOptions.UseHttps(); + }); + } + } + + serverOptions.AddServerHeader = false; + serverOptions.Limits.RequestHeadersTimeout = TimeSpan.FromMilliseconds(_tcpReceiveTimeout); + serverOptions.Limits.KeepAliveTimeout = TimeSpan.FromMilliseconds(_tcpReceiveTimeout); + serverOptions.Limits.MaxRequestHeadersTotalSize = 4096; + serverOptions.Limits.MaxRequestLineSize = serverOptions.Limits.MaxRequestHeadersTotalSize; + serverOptions.Limits.MaxRequestBufferSize = serverOptions.Limits.MaxRequestLineSize; + serverOptions.Limits.MaxRequestBodySize = 64 * 1024; + serverOptions.Limits.MaxResponseBufferSize = 4096; + }); + + builder.Logging.ClearProviders(); + + _dohWebService = builder.Build(); + + _dohWebService.UseDefaultFiles(); + _dohWebService.UseStaticFiles(new StaticFileOptions() + { + OnPrepareResponse = delegate (StaticFileResponseContext ctx) + { + ctx.Context.Response.Headers.Add("X-Robots-Tag", "noindex, nofollow"); + ctx.Context.Response.Headers.Add("Cache-Control", "private, max-age=300"); + } + }); + + _dohWebService.UseRouting(); + _dohWebService.MapGet("/dns-query", ProcessDoHRequestAsync); + _dohWebService.MapPost("/dns-query", ProcessDoHRequestAsync); + + try + { + await _dohWebService.StartAsync(); + + if (_log is not null) + { + foreach (IPAddress localAddress in localAddresses) + { + if (_enableDnsOverHttpPort80) + _log?.Write(new IPEndPoint(localAddress, 80), "Http", "DNS Server was bound successfully."); + + if (_certificate is not null) + _log?.Write(new IPEndPoint(localAddress, _dnsOverHttpsPort), "Https", "DNS Server was bound successfully."); + } + } + } + catch (Exception ex) + { + await StopDoHAsync(); + + if (_log is not null) + { + foreach (IPAddress localAddress in localAddresses) + { + if (_enableDnsOverHttpPort80) + _log?.Write(new IPEndPoint(localAddress, 80), "Http", "DNS Server failed to bind."); + + if (_certificate is not null) + _log?.Write(new IPEndPoint(localAddress, _dnsOverHttpsPort), "Https", "DNS Server failed to bind."); + } + + _log?.Write(ex); + } + } + } + + private async Task StopDoHAsync() + { + if (_dohWebService is not null) + { + await _dohWebService.DisposeAsync(); + _dohWebService = null; + } + } + + internal static IReadOnlyList GetValidKestralLocalAddresses(IReadOnlyList localAddresses) + { + List supportedLocalAddresses = new List(localAddresses.Count); + + foreach (IPAddress localAddress in localAddresses) + { + switch (localAddress.AddressFamily) + { + case AddressFamily.InterNetwork: + if (Socket.OSSupportsIPv4) + { + if (!supportedLocalAddresses.Contains(localAddress)) + supportedLocalAddresses.Add(localAddress); + } + + break; + + case AddressFamily.InterNetworkV6: + if (Socket.OSSupportsIPv6) + { + if (!supportedLocalAddresses.Contains(localAddress)) + supportedLocalAddresses.Add(localAddress); + } + + break; + } + } + + bool containsUnicastAddress = false; + + foreach (IPAddress localAddress in supportedLocalAddresses) + { + if (!localAddress.Equals(IPAddress.Any) && !localAddress.Equals(IPAddress.IPv6Any)) + { + containsUnicastAddress = true; + break; + } + } + + List newLocalAddresses = new List(supportedLocalAddresses.Count); + + if (containsUnicastAddress) + { + //replace any with loopback address + foreach (IPAddress localAddress in supportedLocalAddresses) + { + if (localAddress.Equals(IPAddress.Any)) + { + if (!newLocalAddresses.Contains(IPAddress.Loopback)) + newLocalAddresses.Add(IPAddress.Loopback); + } + else if (localAddress.Equals(IPAddress.IPv6Any)) + { + if (!newLocalAddresses.Contains(IPAddress.IPv6Loopback)) + newLocalAddresses.Add(IPAddress.IPv6Loopback); + } + else + { + if (!newLocalAddresses.Contains(localAddress)) + newLocalAddresses.Add(localAddress); + } + } + } + else + { + //remove "0.0.0.0" if [::] exists + foreach (IPAddress localAddress in supportedLocalAddresses) + { + if (localAddress.Equals(IPAddress.Any)) + { + if (!supportedLocalAddresses.Contains(IPAddress.IPv6Any)) + newLocalAddresses.Add(localAddress); + } + else + { + newLocalAddresses.Add(localAddress); + } + } + } + + return newLocalAddresses; + } + + #endregion + #region public - public void Start() + public async Task StartAsync() { if (_disposed) throw new ObjectDisposedException("DnsServer"); @@ -3809,18 +3997,13 @@ namespace DnsServerCore.Dns _udpListeners.Add(udpListener); - LogManager log = _log; - if (log is not null) - log.Write(localEP, DnsTransportProtocol.Udp, "DNS Server was bound successfully."); + _log?.Write(localEP, DnsTransportProtocol.Udp, "DNS Server was bound successfully."); } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(localEP, DnsTransportProtocol.Udp, "DNS Server failed to bind.\r\n" + ex.ToString()); + _log?.Write(localEP, DnsTransportProtocol.Udp, "DNS Server failed to bind.\r\n" + ex.ToString()); - if (udpListener is not null) - udpListener.Dispose(); + udpListener?.Dispose(); } Socket tcpListener = null; @@ -3830,58 +4013,22 @@ namespace DnsServerCore.Dns tcpListener = new Socket(localEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); tcpListener.Bind(localEP); - tcpListener.Listen(100); + tcpListener.Listen(_listenBacklog); _tcpListeners.Add(tcpListener); - LogManager log = _log; - if (log is not null) - log.Write(localEP, DnsTransportProtocol.Tcp, "DNS Server was bound successfully."); + _log?.Write(localEP, DnsTransportProtocol.Tcp, "DNS Server was bound successfully."); } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(localEP, DnsTransportProtocol.Tcp, "DNS Server failed to bind.\r\n" + ex.ToString()); + _log?.Write(localEP, DnsTransportProtocol.Tcp, "DNS Server failed to bind.\r\n" + ex.ToString()); - if (tcpListener is not null) - tcpListener.Dispose(); - } - - if (_enableDnsOverHttp) - { - IPEndPoint httpEP = new IPEndPoint(localEP.Address, 8053); - Socket httpListener = null; - - try - { - httpListener = new Socket(httpEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - - httpListener.Bind(httpEP); - httpListener.Listen(100); - - _httpListeners.Add(httpListener); - - _isDnsOverHttpsEnabled = true; - - LogManager log = _log; - if (log is not null) - log.Write(httpEP, "Http", "DNS Server was bound successfully."); - } - catch (Exception ex) - { - LogManager log = _log; - if (log is not null) - log.Write(httpEP, "Http", "DNS Server failed to bind.\r\n" + ex.ToString()); - - if (httpListener is not null) - httpListener.Dispose(); - } + tcpListener?.Dispose(); } if (_enableDnsOverTls && (_certificate is not null)) { - IPEndPoint tlsEP = new IPEndPoint(localEP.Address, 853); + IPEndPoint tlsEP = new IPEndPoint(localEP.Address, _dnsOverTlsPort); Socket tlsListener = null; try @@ -3889,86 +4036,64 @@ namespace DnsServerCore.Dns tlsListener = new Socket(tlsEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); tlsListener.Bind(tlsEP); - tlsListener.Listen(100); + tlsListener.Listen(_listenBacklog); _tlsListeners.Add(tlsListener); - LogManager log = _log; - if (log is not null) - log.Write(tlsEP, DnsTransportProtocol.Tls, "DNS Server was bound successfully."); + _log?.Write(tlsEP, DnsTransportProtocol.Tls, "DNS Server was bound successfully."); } catch (Exception ex) { - LogManager log = _log; - if (log is not null) - log.Write(tlsEP, DnsTransportProtocol.Tls, "DNS Server failed to bind.\r\n" + ex.ToString()); + _log?.Write(tlsEP, DnsTransportProtocol.Tls, "DNS Server failed to bind.\r\n" + ex.ToString()); - if (tlsListener is not null) - tlsListener.Dispose(); + tlsListener?.Dispose(); } } - if (_enableDnsOverHttps) + if (_enableDnsOverQuic && (_certificate is not null)) { - //bind to http port 80 for certbot webroot support + IPEndPoint quicEP = new IPEndPoint(localEP.Address, _dnsOverQuicPort); + QuicListener quicListener = null; + + try { - IPEndPoint httpEP = new IPEndPoint(localEP.Address, 80); - Socket httpListener = null; - - try + QuicListenerOptions listenerOptions = new QuicListenerOptions() { - httpListener = new Socket(httpEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + ListenEndPoint = quicEP, + ListenBacklog = _listenBacklog, + ApplicationProtocols = new List() { new SslApplicationProtocol("doq") }, + ConnectionOptionsCallback = delegate (QuicConnection quicConnection, SslClientHelloInfo sslClientHello, CancellationToken cancellationToken) + { + QuicServerConnectionOptions serverConnectionOptions = new QuicServerConnectionOptions() + { + DefaultCloseErrorCode = (long)DnsOverQuicErrorCodes.DOQ_NO_ERROR, + DefaultStreamErrorCode = (long)DnsOverQuicErrorCodes.DOQ_UNSPECIFIED_ERROR, + MaxInboundUnidirectionalStreams = 0, + MaxInboundBidirectionalStreams = _quicMaxInboundStreams, + IdleTimeout = TimeSpan.FromMilliseconds(_quicIdleTimeout), + ServerAuthenticationOptions = new SslServerAuthenticationOptions + { + ApplicationProtocols = new List() { new SslApplicationProtocol("doq") }, + ServerCertificate = _certificate + } + }; - httpListener.Bind(httpEP); - httpListener.Listen(100); + return ValueTask.FromResult(serverConnectionOptions); + } + }; - _httpListeners.Add(httpListener); + quicListener = await QuicListener.ListenAsync(listenerOptions); - LogManager log = _log; - if (log is not null) - log.Write(httpEP, "Http", "DNS Server was bound successfully."); - } - catch (Exception ex) - { - LogManager log = _log; - if (log is not null) - log.Write(httpEP, "Http", "DNS Server failed to bind.\r\n" + ex.ToString()); + _quicListeners.Add(quicListener); - if (httpListener is not null) - httpListener.Dispose(); - } + _log?.Write(quicEP, DnsTransportProtocol.Quic, "DNS Server was bound successfully."); } - - //bind to https port 443 - if (_certificate is not null) + catch (Exception ex) { - IPEndPoint httpsEP = new IPEndPoint(localEP.Address, 443); - Socket httpsListener = null; + _log?.Write(quicEP, DnsTransportProtocol.Quic, "DNS Server failed to bind.\r\n" + ex.ToString()); - try - { - httpsListener = new Socket(httpsEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - - httpsListener.Bind(httpsEP); - httpsListener.Listen(100); - - _httpsListeners.Add(httpsListener); - - _isDnsOverHttpsEnabled = true; - - LogManager log = _log; - if (log is not null) - log.Write(httpsEP, DnsTransportProtocol.Https, "DNS Server was bound successfully."); - } - catch (Exception ex) - { - LogManager log = _log; - if (log is not null) - log.Write(httpsEP, DnsTransportProtocol.Https, "DNS Server failed to bind.\r\n" + ex.ToString()); - - if (httpsListener is not null) - httpsListener.Dispose(); - } + if (quicListener is not null) + await quicListener.DisposeAsync(); } } } @@ -3993,18 +4118,7 @@ namespace DnsServerCore.Dns { _ = Task.Factory.StartNew(delegate () { - return AcceptConnectionAsync(tcpListener, DnsTransportProtocol.Tcp, false); - }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Current); - } - } - - foreach (Socket httpListener in _httpListeners) - { - for (int i = 0; i < listenerTaskCount; i++) - { - _ = Task.Factory.StartNew(delegate () - { - return AcceptConnectionAsync(httpListener, DnsTransportProtocol.Https, false); + return AcceptConnectionAsync(tcpListener, DnsTransportProtocol.Tcp); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Current); } } @@ -4015,22 +4129,28 @@ namespace DnsServerCore.Dns { _ = Task.Factory.StartNew(delegate () { - return AcceptConnectionAsync(tlsListener, DnsTransportProtocol.Tls, false); + return AcceptConnectionAsync(tlsListener, DnsTransportProtocol.Tls); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Current); } } - foreach (Socket httpsListener in _httpsListeners) + foreach (QuicListener quicListener in _quicListeners) { for (int i = 0; i < listenerTaskCount; i++) { _ = Task.Factory.StartNew(delegate () { - return AcceptConnectionAsync(httpsListener, DnsTransportProtocol.Https, true); + return AcceptQuicConnectionAsync(quicListener); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Current); } } + if (_enableDnsOverHttp) + await StartDoHPrivateAsync(); + + if (_enableDnsOverHttps) + await StartDoHAsync(); + _cachePrefetchSamplingTimer = new Timer(CachePrefetchSamplingTimerCallback, null, Timeout.Infinite, Timeout.Infinite); _cachePrefetchRefreshTimer = new Timer(CachePrefetchRefreshTimerCallback, null, Timeout.Infinite, Timeout.Infinite); _cacheMaintenanceTimer = new Timer(CacheMaintenanceTimerCallback, null, CACHE_MAINTENANCE_TIMER_INITIAL_INTEVAL, Timeout.Infinite); @@ -4043,7 +4163,7 @@ namespace DnsServerCore.Dns ResetQpsLimitTimer(); } - public void Stop() + public async Task StopAsync() { if (_state != ServiceState.Running) return; @@ -4092,20 +4212,19 @@ namespace DnsServerCore.Dns foreach (Socket tcpListener in _tcpListeners) tcpListener.Dispose(); - foreach (Socket httpListener in _httpListeners) - httpListener.Dispose(); - foreach (Socket tlsListener in _tlsListeners) tlsListener.Dispose(); - foreach (Socket httpsListener in _httpsListeners) - httpsListener.Dispose(); + foreach (QuicListener quicListener in _quicListeners) + await quicListener.DisposeAsync(); _udpListeners.Clear(); _tcpListeners.Clear(); - _httpListeners.Clear(); _tlsListeners.Clear(); - _httpsListeners.Clear(); + _quicListeners.Clear(); + + await StopDoHPrivateAsync(); + await StopDoHAsync(); _state = ServiceState.Stopped; } @@ -4152,48 +4271,15 @@ namespace DnsServerCore.Dns set { _localEndPoints = value; } } + public LogManager LogManager + { + get { return _log; } + set { _log = value; } + } + public NameServerAddress ThisServer { get { return _thisServer; } } - public bool EnableDnsOverHttp - { - get { return _enableDnsOverHttp; } - set { _enableDnsOverHttp = value; } - } - - public bool EnableDnsOverTls - { - get { return _enableDnsOverTls; } - set { _enableDnsOverTls = value; } - } - - public bool EnableDnsOverHttps - { - get { return _enableDnsOverHttps; } - set { _enableDnsOverHttps = value; } - } - - public bool IsDnsOverHttpsEnabled - { get { return _isDnsOverHttpsEnabled; } } - - public X509Certificate2 Certificate - { - get { return _certificate; } - set - { - if (!value.HasPrivateKey) - throw new ArgumentException("Tls certificate does not contain private key."); - - _certificate = value; - } - } - - public IReadOnlyDictionary TsigKeys - { - get { return _tsigKeys; } - set { _tsigKeys = value; } - } - public AuthZoneManager AuthZoneManager { get { return _authZoneManager; } } @@ -4215,61 +4301,8 @@ namespace DnsServerCore.Dns public IDnsCache DnsCache { get { return _dnsCache; } } - public DnsServerRecursion Recursion - { - get { return _recursion; } - set - { - if (_recursion != value) - { - if ((_recursion == DnsServerRecursion.Deny) || (value == DnsServerRecursion.Deny)) - { - _recursion = value; - ResetPrefetchTimers(); - } - else - { - _recursion = value; - } - } - } - } - - public IReadOnlyCollection RecursionDeniedNetworks - { - get { return _recursionDeniedNetworks; } - set - { - if ((value is not null) && (value.Count > byte.MaxValue)) - throw new ArgumentOutOfRangeException(nameof(RecursionDeniedNetworks), "Networks cannot be more than 255."); - - _recursionDeniedNetworks = value; - } - } - - public IReadOnlyCollection RecursionAllowedNetworks - { - get { return _recursionAllowedNetworks; } - set - { - if ((value is not null) && (value.Count > byte.MaxValue)) - throw new ArgumentOutOfRangeException(nameof(RecursionAllowedNetworks), "Networks cannot be more than 255."); - - _recursionAllowedNetworks = value; - } - } - - public NetProxy Proxy - { - get { return _proxy; } - set { _proxy = value; } - } - - public IReadOnlyList Forwarders - { - get { return _forwarders; } - set { _forwarders = value; } - } + public StatsManager StatsManager + { get { return _stats; } } public bool PreferIPv6 { @@ -4289,24 +4322,6 @@ namespace DnsServerCore.Dns } } - public bool RandomizeName - { - get { return _randomizeName; } - set { _randomizeName = value; } - } - - public bool QnameMinimization - { - get { return _qnameMinimization; } - set { _qnameMinimization = value; } - } - - public bool NsRevalidation - { - get { return _nsRevalidation; } - set { _nsRevalidation = value; } - } - public bool DnssecValidation { get { return _dnssecValidation; } @@ -4445,18 +4460,206 @@ namespace DnsServerCore.Dns } } - public int ForwarderRetries + public int ClientTimeout { - get { return _forwarderRetries; } + get { return _clientTimeout; } set { - if ((value < 1) || (value > 10)) - throw new ArgumentOutOfRangeException(nameof(ForwarderRetries), "Valid range is from 1 to 10."); + if ((value < 1000) || (value > 10000)) + throw new ArgumentOutOfRangeException(nameof(ClientTimeout), "Valid range is from 1000 to 10000."); - _forwarderRetries = value; + _clientTimeout = value; } } + public int TcpSendTimeout + { + get { return _tcpSendTimeout; } + set + { + if ((value < 1000) || (value > 90000)) + throw new ArgumentOutOfRangeException(nameof(TcpSendTimeout), "Valid range is from 1000 to 90000."); + + _tcpSendTimeout = value; + } + } + + public int TcpReceiveTimeout + { + get { return _tcpReceiveTimeout; } + set + { + if ((value < 1000) || (value > 90000)) + throw new ArgumentOutOfRangeException(nameof(TcpReceiveTimeout), "Valid range is from 1000 to 90000."); + + _tcpReceiveTimeout = value; + } + } + + public int QuicIdleTimeout + { + get { return _quicIdleTimeout; } + set + { + if ((value < 1000) || (value > 90000)) + throw new ArgumentOutOfRangeException(nameof(QuicIdleTimeout), "Valid range is from 1000 to 90000."); + + _quicIdleTimeout = value; + } + } + + public int QuicMaxInboundStreams + { + get { return _quicMaxInboundStreams; } + set + { + if ((value < 0) || (value > 1000)) + throw new ArgumentOutOfRangeException(nameof(QuicMaxInboundStreams), "Valid range is from 1 to 1000."); + + _quicMaxInboundStreams = value; + } + } + + public int ListenBacklog + { + get { return _listenBacklog; } + set { _listenBacklog = value; } + } + + public bool EnableDnsOverHttp + { + get { return _enableDnsOverHttp; } + set { _enableDnsOverHttp = value; } + } + + public bool EnableDnsOverTls + { + get { return _enableDnsOverTls; } + set { _enableDnsOverTls = value; } + } + + public bool EnableDnsOverHttps + { + get { return _enableDnsOverHttps; } + set { _enableDnsOverHttps = value; } + } + + public bool EnableDnsOverHttpPort80 + { + get { return _enableDnsOverHttpPort80; } + set { _enableDnsOverHttpPort80 = value; } + } + + public bool EnableDnsOverQuic + { + get { return _enableDnsOverQuic; } + set { _enableDnsOverQuic = value; } + } + + public int DnsOverHttpPort + { + get { return _dnsOverHttpPort; } + set { _dnsOverHttpPort = value; } + } + + public int DnsOverTlsPort + { + get { return _dnsOverTlsPort; } + set { _dnsOverTlsPort = value; } + } + + public int DnsOverHttpsPort + { + get { return _dnsOverHttpsPort; } + set { _dnsOverHttpsPort = value; } + } + + public int DnsOverQuicPort + { + get { return _dnsOverQuicPort; } + set { _dnsOverQuicPort = value; } + } + + public X509Certificate2 Certificate + { + get { return _certificate; } + set + { + if ((value is not null) && !value.HasPrivateKey) + throw new ArgumentException("Tls certificate does not contain private key."); + + _certificate = value; + } + } + + public IReadOnlyDictionary TsigKeys + { + get { return _tsigKeys; } + set { _tsigKeys = value; } + } + + public DnsServerRecursion Recursion + { + get { return _recursion; } + set + { + if (_recursion != value) + { + if ((_recursion == DnsServerRecursion.Deny) || (value == DnsServerRecursion.Deny)) + { + _recursion = value; + ResetPrefetchTimers(); + } + else + { + _recursion = value; + } + } + } + } + + public IReadOnlyCollection RecursionDeniedNetworks + { + get { return _recursionDeniedNetworks; } + set + { + if ((value is not null) && (value.Count > byte.MaxValue)) + throw new ArgumentOutOfRangeException(nameof(RecursionDeniedNetworks), "Networks cannot be more than 255."); + + _recursionDeniedNetworks = value; + } + } + + public IReadOnlyCollection RecursionAllowedNetworks + { + get { return _recursionAllowedNetworks; } + set + { + if ((value is not null) && (value.Count > byte.MaxValue)) + throw new ArgumentOutOfRangeException(nameof(RecursionAllowedNetworks), "Networks cannot be more than 255."); + + _recursionAllowedNetworks = value; + } + } + + public bool RandomizeName + { + get { return _randomizeName; } + set { _randomizeName = value; } + } + + public bool QnameMinimization + { + get { return _qnameMinimization; } + set { _qnameMinimization = value; } + } + + public bool NsRevalidation + { + get { return _nsRevalidation; } + set { _nsRevalidation = value; } + } + public int ResolverRetries { get { return _resolverRetries; } @@ -4469,18 +4672,6 @@ namespace DnsServerCore.Dns } } - public int ForwarderTimeout - { - get { return _forwarderTimeout; } - set - { - if ((value < 1000) || (value > 10000)) - throw new ArgumentOutOfRangeException(nameof(ForwarderTimeout), "Valid range is from 1000 to 10000."); - - _forwarderTimeout = value; - } - } - public int ResolverTimeout { get { return _resolverTimeout; } @@ -4493,30 +4684,6 @@ namespace DnsServerCore.Dns } } - public int ClientTimeout - { - get { return _clientTimeout; } - set - { - if ((value < 1000) || (value > 10000)) - throw new ArgumentOutOfRangeException(nameof(ClientTimeout), "Valid range is from 1000 to 10000."); - - _clientTimeout = value; - } - } - - public int ForwarderConcurrency - { - get { return _forwarderConcurrency; } - set - { - if ((value < 1) || (value > 10)) - throw new ArgumentOutOfRangeException(nameof(ForwarderConcurrency), "Valid range is from 1 to 10."); - - _forwarderConcurrency = value; - } - } - public int ResolverMaxStackCount { get { return _resolverMaxStackCount; } @@ -4636,10 +4803,52 @@ namespace DnsServerCore.Dns } } - public LogManager LogManager + public NetProxy Proxy { - get { return _log; } - set { _log = value; } + get { return _proxy; } + set { _proxy = value; } + } + + public IReadOnlyList Forwarders + { + get { return _forwarders; } + set { _forwarders = value; } + } + + public int ForwarderRetries + { + get { return _forwarderRetries; } + set + { + if ((value < 1) || (value > 10)) + throw new ArgumentOutOfRangeException(nameof(ForwarderRetries), "Valid range is from 1 to 10."); + + _forwarderRetries = value; + } + } + + public int ForwarderTimeout + { + get { return _forwarderTimeout; } + set + { + if ((value < 1000) || (value > 10000)) + throw new ArgumentOutOfRangeException(nameof(ForwarderTimeout), "Valid range is from 1000 to 10000."); + + _forwarderTimeout = value; + } + } + + public int ForwarderConcurrency + { + get { return _forwarderConcurrency; } + set + { + if ((value < 1) || (value > 10)) + throw new ArgumentOutOfRangeException(nameof(ForwarderConcurrency), "Valid range is from 1 to 10."); + + _forwarderConcurrency = value; + } } public LogManager QueryLogManager @@ -4648,33 +4857,6 @@ namespace DnsServerCore.Dns set { _queryLog = value; } } - public StatsManager StatsManager - { get { return _stats; } } - - public int TcpSendTimeout - { - get { return _tcpSendTimeout; } - set - { - if ((value < 1000) || (value > 90000)) - throw new ArgumentOutOfRangeException(nameof(TcpSendTimeout), "Valid range is from 1000 to 60000."); - - _tcpSendTimeout = value; - } - } - - public int TcpReceiveTimeout - { - get { return _tcpReceiveTimeout; } - set - { - if ((value < 1000) || (value > 90000)) - throw new ArgumentOutOfRangeException(nameof(TcpReceiveTimeout), "Valid range is from 1000 to 60000."); - - _tcpReceiveTimeout = value; - } - } - #endregion class CacheRefreshSample @@ -4703,4 +4885,7 @@ namespace DnsServerCore.Dns public DnsDatagram CheckingDisabledResponse { get; } } } + +#pragma warning restore CA2252 // This API requires opting into preview features +#pragma warning restore CA1416 // Validate platform compatibility }