/* Technitium DNS Server 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 the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using DnsServerCore.Auth; using DnsServerCore.Dhcp; using DnsServerCore.Dns; using DnsServerCore.Dns.ZoneManagers; using DnsServerCore.Dns.Zones; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Diagnostics; 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.Generic; using System.IO; using System.Net; using System.Net.Quic; using System.Net.Sockets; using System.Reflection; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using TechnitiumLibrary; using TechnitiumLibrary.IO; using TechnitiumLibrary.Net; using TechnitiumLibrary.Net.Dns; using TechnitiumLibrary.Net.Dns.ResourceRecords; using TechnitiumLibrary.Net.Proxy; namespace DnsServerCore { public sealed class DnsWebService : IAsyncDisposable, IDisposable { #region variables internal readonly Version _currentVersion; readonly string _appFolder; internal readonly string _configFolder; internal readonly LogManager _log; internal readonly AuthManager _authManager; readonly WebServiceApi _api; readonly WebServiceDashboardApi _dashboardApi; internal readonly WebServiceZonesApi _zonesApi; readonly WebServiceOtherZonesApi _otherZonesApi; internal readonly WebServiceAppsApi _appsApi; readonly WebServiceSettingsApi _settingsApi; readonly WebServiceDhcpApi _dhcpApi; readonly WebServiceAuthApi _authApi; readonly WebServiceLogsApi _logsApi; WebApplication _webService; X509Certificate2 _webServiceTlsCertificate; DnsServer _dnsServer; DhcpServer _dhcpServer; //web service internal IReadOnlyList _webServiceLocalAddresses = new IPAddress[] { IPAddress.Any, IPAddress.IPv6Any }; internal int _webServiceHttpPort = 5380; internal int _webServiceTlsPort = 53443; internal bool _webServiceEnableTls; internal bool _webServiceHttpToTlsRedirect; internal bool _webServiceUseSelfSignedTlsCertificate; internal string _webServiceTlsCertificatePath; internal string _webServiceTlsCertificatePassword; DateTime _webServiceTlsCertificateLastModifiedOn; //optional protocols internal string _dnsTlsCertificatePath; internal string _dnsTlsCertificatePassword; DateTime _dnsTlsCertificateLastModifiedOn; //cache internal bool _saveCache = true; Timer _tlsCertificateUpdateTimer; const int TLS_CERTIFICATE_UPDATE_TIMER_INITIAL_INTERVAL = 60000; const int TLS_CERTIFICATE_UPDATE_TIMER_INTERVAL = 60000; List _configDisabledZones; #endregion #region constructor public DnsWebService(string configFolder = null, Uri updateCheckUri = null, Uri appStoreUri = null) { Assembly assembly = Assembly.GetExecutingAssembly(); _currentVersion = assembly.GetName().Version; _appFolder = Path.GetDirectoryName(assembly.Location); if (configFolder is null) _configFolder = Path.Combine(_appFolder, "config"); else _configFolder = configFolder; Directory.CreateDirectory(_configFolder); Directory.CreateDirectory(Path.Combine(_configFolder, "blocklists")); _log = new LogManager(_configFolder); _authManager = new AuthManager(_configFolder, _log); _api = new WebServiceApi(this, updateCheckUri); _dashboardApi = new WebServiceDashboardApi(this); _zonesApi = new WebServiceZonesApi(this); _otherZonesApi = new WebServiceOtherZonesApi(this); _appsApi = new WebServiceAppsApi(this, appStoreUri); _settingsApi = new WebServiceSettingsApi(this); _dhcpApi = new WebServiceDhcpApi(this); _authApi = new WebServiceAuthApi(this); _logsApi = new WebServiceLogsApi(this); } #endregion #region IDisposable bool _disposed; public async ValueTask DisposeAsync() { if (_disposed) return; await StopAsync(); if (_appsApi is not null) _appsApi.Dispose(); if (_settingsApi is not null) _settingsApi.Dispose(); if (_authManager is not null) _authManager.Dispose(); if (_log is not null) _log.Dispose(); _disposed = true; } public void Dispose() { DisposeAsync().Sync(); } #endregion #region server version internal string GetServerVersion() { return GetCleanVersion(_currentVersion); } internal static string GetCleanVersion(Version version) { string strVersion = version.Major + "." + version.Minor; if (version.Build > 0) strVersion += "." + version.Build; if (version.Revision > 0) strVersion += "." + version.Revision; return strVersion; } #endregion #region web service internal async Task TryStartWebServiceAsync() { try { _webServiceLocalAddresses = DnsServer.GetValidKestralLocalAddresses(_webServiceLocalAddresses); await StartWebServiceAsync(false); } catch (Exception ex) { _log.Write("Web Service failed to start: " + ex.ToString()); _log.Write("Attempting to start Web Service on ANY (0.0.0.0) fallback address..."); try { _webServiceLocalAddresses = new IPAddress[] { IPAddress.Any }; await StartWebServiceAsync(false); } catch (Exception ex2) { _log.Write("Web Service failed to start: " + ex2.ToString()); _log.Write("Attempting to start Web Service on loopback (127.0.0.1) fallback address..."); _webServiceLocalAddresses = new IPAddress[] { IPAddress.Loopback }; await StartWebServiceAsync(true); } } } private async Task StartWebServiceAsync(bool safeMode) { WebApplicationBuilder builder = WebApplication.CreateBuilder(); builder.Environment.ContentRootFileProvider = new PhysicalFileProvider(_appFolder) { UseActivePolling = true, UsePollingFileWatcher = true }; builder.Environment.WebRootFileProvider = new PhysicalFileProvider(Path.Combine(_appFolder, "www")) { UseActivePolling = true, UsePollingFileWatcher = true }; builder.WebHost.ConfigureKestrel(delegate (WebHostBuilderContext context, KestrelServerOptions serverOptions) { //http foreach (IPAddress webServiceLocalAddress in _webServiceLocalAddresses) serverOptions.Listen(webServiceLocalAddress, _webServiceHttpPort); //https if (!safeMode && _webServiceEnableTls && (_webServiceTlsCertificate is not null)) { serverOptions.ConfigureHttpsDefaults(delegate (HttpsConnectionAdapterOptions configureOptions) { configureOptions.ServerCertificateSelector = delegate (ConnectionContext context, string dnsName) { return _webServiceTlsCertificate; }; }); foreach (IPAddress webServiceLocalAddress in _webServiceLocalAddresses) { serverOptions.Listen(webServiceLocalAddress, _webServiceTlsPort, delegate (ListenOptions listenOptions) { listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; listenOptions.UseHttps(); }); } } serverOptions.AddServerHeader = false; serverOptions.Limits.MaxRequestBodySize = null; }); builder.Logging.ClearProviders(); _webService = builder.Build(); if (_webServiceHttpToTlsRedirect && !safeMode && _webServiceEnableTls && (_webServiceTlsCertificate is not null)) _webService.UseHttpsRedirection(); _webService.UseDefaultFiles(); _webService.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"); }, ServeUnknownFileTypes = true }); ConfigureWebServiceRoutes(); try { await _webService.StartAsync(); foreach (IPAddress webServiceLocalAddress in _webServiceLocalAddresses) { _log?.Write(new IPEndPoint(webServiceLocalAddress, _webServiceHttpPort), "Http", "Web Service was bound successfully."); if (!safeMode && _webServiceEnableTls && (_webServiceTlsCertificate is not null)) _log?.Write(new IPEndPoint(webServiceLocalAddress, _webServiceHttpPort), "Https", "Web Service was bound successfully."); } } catch { await StopWebServiceAsync(); foreach (IPAddress webServiceLocalAddress in _webServiceLocalAddresses) { _log?.Write(new IPEndPoint(webServiceLocalAddress, _webServiceHttpPort), "Http", "Web Service failed to bind."); if (!safeMode && _webServiceEnableTls && (_webServiceTlsCertificate is not null)) _log?.Write(new IPEndPoint(webServiceLocalAddress, _webServiceHttpPort), "Https", "Web Service failed to bind."); } throw; } } internal async Task StopWebServiceAsync() { if (_webService is not null) { await _webService.DisposeAsync(); _webService = null; } } private void ConfigureWebServiceRoutes() { _webService.UseExceptionHandler(WebServiceExceptionHandler); _webService.Use(WebServiceApiMiddleware); _webService.UseRouting(); //user auth _webService.MapGetAndPost("/api/user/login", delegate (HttpContext context) { return _authApi.LoginAsync(context, UserSessionType.Standard); }); _webService.MapGetAndPost("/api/user/createToken", delegate (HttpContext context) { return _authApi.LoginAsync(context, UserSessionType.ApiToken); }); _webService.MapGetAndPost("/api/user/logout", _authApi.Logout); //user _webService.MapGetAndPost("/api/user/session/get", _authApi.GetCurrentSessionDetails); _webService.MapGetAndPost("/api/user/session/delete", delegate (HttpContext context) { _authApi.DeleteSession(context, false); }); _webService.MapGetAndPost("/api/user/changePassword", _authApi.ChangePassword); _webService.MapGetAndPost("/api/user/profile/get", _authApi.GetProfile); _webService.MapGetAndPost("/api/user/profile/set", _authApi.SetProfile); _webService.MapGetAndPost("/api/user/checkForUpdate", _api.CheckForUpdateAsync); //dashboard _webService.MapGetAndPost("/api/dashboard/stats/get", _dashboardApi.GetStats); _webService.MapGetAndPost("/api/dashboard/stats/getTop", _dashboardApi.GetTopStats); _webService.MapGetAndPost("/api/dashboard/stats/deleteAll", _logsApi.DeleteAllStats); //zones _webService.MapGetAndPost("/api/zones/list", _zonesApi.ListZones); _webService.MapGetAndPost("/api/zones/create", _zonesApi.CreateZoneAsync); _webService.MapGetAndPost("/api/zones/convert", _zonesApi.ConvertZone); _webService.MapGetAndPost("/api/zones/enable", _zonesApi.EnableZone); _webService.MapGetAndPost("/api/zones/disable", _zonesApi.DisableZone); _webService.MapGetAndPost("/api/zones/delete", _zonesApi.DeleteZone); _webService.MapGetAndPost("/api/zones/resync", _zonesApi.ResyncZone); _webService.MapGetAndPost("/api/zones/options/get", _zonesApi.GetZoneOptions); _webService.MapGetAndPost("/api/zones/options/set", _zonesApi.SetZoneOptions); _webService.MapGetAndPost("/api/zones/permissions/get", delegate (HttpContext context) { _authApi.GetPermissionDetails(context, PermissionSection.Zones); }); _webService.MapGetAndPost("/api/zones/permissions/set", delegate (HttpContext context) { _authApi.SetPermissionsDetails(context, PermissionSection.Zones); }); _webService.MapGetAndPost("/api/zones/dnssec/sign", _zonesApi.SignPrimaryZone); _webService.MapGetAndPost("/api/zones/dnssec/unsign", _zonesApi.UnsignPrimaryZone); _webService.MapGetAndPost("/api/zones/dnssec/properties/get", _zonesApi.GetPrimaryZoneDnssecProperties); _webService.MapGetAndPost("/api/zones/dnssec/properties/convertToNSEC", _zonesApi.ConvertPrimaryZoneToNSEC); _webService.MapGetAndPost("/api/zones/dnssec/properties/convertToNSEC3", _zonesApi.ConvertPrimaryZoneToNSEC3); _webService.MapGetAndPost("/api/zones/dnssec/properties/updateNSEC3Params", _zonesApi.UpdatePrimaryZoneNSEC3Parameters); _webService.MapGetAndPost("/api/zones/dnssec/properties/updateDnsKeyTtl", _zonesApi.UpdatePrimaryZoneDnssecDnsKeyTtl); _webService.MapGetAndPost("/api/zones/dnssec/properties/generatePrivateKey", _zonesApi.GenerateAndAddPrimaryZoneDnssecPrivateKey); _webService.MapGetAndPost("/api/zones/dnssec/properties/updatePrivateKey", _zonesApi.UpdatePrimaryZoneDnssecPrivateKey); _webService.MapGetAndPost("/api/zones/dnssec/properties/deletePrivateKey", _zonesApi.DeletePrimaryZoneDnssecPrivateKey); _webService.MapGetAndPost("/api/zones/dnssec/properties/publishAllPrivateKeys", _zonesApi.PublishAllGeneratedPrimaryZoneDnssecPrivateKeys); _webService.MapGetAndPost("/api/zones/dnssec/properties/rolloverDnsKey", _zonesApi.RolloverPrimaryZoneDnsKey); _webService.MapGetAndPost("/api/zones/dnssec/properties/retireDnsKey", _zonesApi.RetirePrimaryZoneDnsKey); _webService.MapGetAndPost("/api/zones/records/add", _zonesApi.AddRecord); _webService.MapGetAndPost("/api/zones/records/get", _zonesApi.GetRecords); _webService.MapGetAndPost("/api/zones/records/update", _zonesApi.UpdateRecord); _webService.MapGetAndPost("/api/zones/records/delete", _zonesApi.DeleteRecord); //cache _webService.MapGetAndPost("/api/cache/list", _otherZonesApi.ListCachedZones); _webService.MapGetAndPost("/api/cache/delete", _otherZonesApi.DeleteCachedZone); _webService.MapGetAndPost("/api/cache/flush", _otherZonesApi.FlushCache); //allowed _webService.MapGetAndPost("/api/allowed/list", _otherZonesApi.ListAllowedZones); _webService.MapGetAndPost("/api/allowed/add", _otherZonesApi.AllowZone); _webService.MapGetAndPost("/api/allowed/delete", _otherZonesApi.DeleteAllowedZone); _webService.MapGetAndPost("/api/allowed/flush", _otherZonesApi.FlushAllowedZone); _webService.MapGetAndPost("/api/allowed/import", _otherZonesApi.ImportAllowedZones); _webService.MapGetAndPost("/api/allowed/export", _otherZonesApi.ExportAllowedZonesAsync); //blocked _webService.MapGetAndPost("/api/blocked/list", _otherZonesApi.ListBlockedZones); _webService.MapGetAndPost("/api/blocked/add", _otherZonesApi.BlockZone); _webService.MapGetAndPost("/api/blocked/delete", _otherZonesApi.DeleteBlockedZone); _webService.MapGetAndPost("/api/blocked/flush", _otherZonesApi.FlushBlockedZone); _webService.MapGetAndPost("/api/blocked/import", _otherZonesApi.ImportBlockedZones); _webService.MapGetAndPost("/api/blocked/export", _otherZonesApi.ExportBlockedZonesAsync); //apps _webService.MapGetAndPost("/api/apps/list", _appsApi.ListInstalledAppsAsync); _webService.MapGetAndPost("/api/apps/listStoreApps", _appsApi.ListStoreApps); _webService.MapGetAndPost("/api/apps/downloadAndInstall", _appsApi.DownloadAndInstallAppAsync); _webService.MapGetAndPost("/api/apps/downloadAndUpdate", _appsApi.DownloadAndUpdateAppAsync); _webService.MapPost("/api/apps/install", _appsApi.InstallAppAsync); _webService.MapPost("/api/apps/update", _appsApi.UpdateAppAsync); _webService.MapGetAndPost("/api/apps/uninstall", _appsApi.UninstallApp); _webService.MapGetAndPost("/api/apps/config/get", _appsApi.GetAppConfigAsync); _webService.MapGetAndPost("/api/apps/config/set", _appsApi.SetAppConfigAsync); //dns client _webService.MapGetAndPost("/api/dnsClient/resolve", _api.ResolveQueryAsync); //settings _webService.MapGetAndPost("/api/settings/get", _settingsApi.GetDnsSettings); _webService.MapGetAndPost("/api/settings/set", _settingsApi.SetDnsSettings); _webService.MapGetAndPost("/api/settings/getTsigKeyNames", _settingsApi.GetTsigKeyNames); _webService.MapGetAndPost("/api/settings/forceUpdateBlockLists", _settingsApi.ForceUpdateBlockLists); _webService.MapGetAndPost("/api/settings/temporaryDisableBlocking", _settingsApi.TemporaryDisableBlocking); _webService.MapGetAndPost("/api/settings/backup", _settingsApi.BackupSettingsAsync); _webService.MapPost("/api/settings/restore", _settingsApi.RestoreSettingsAsync); //dhcp _webService.MapGetAndPost("/api/dhcp/leases/list", _dhcpApi.ListDhcpLeases); _webService.MapGetAndPost("/api/dhcp/leases/remove", _dhcpApi.RemoveDhcpLease); _webService.MapGetAndPost("/api/dhcp/leases/convertToReserved", _dhcpApi.ConvertToReservedLease); _webService.MapGetAndPost("/api/dhcp/leases/convertToDynamic", _dhcpApi.ConvertToDynamicLease); _webService.MapGetAndPost("/api/dhcp/scopes/list", _dhcpApi.ListDhcpScopes); _webService.MapGetAndPost("/api/dhcp/scopes/get", _dhcpApi.GetDhcpScope); _webService.MapGetAndPost("/api/dhcp/scopes/set", _dhcpApi.SetDhcpScopeAsync); _webService.MapGetAndPost("/api/dhcp/scopes/addReservedLease", _dhcpApi.AddReservedLease); _webService.MapGetAndPost("/api/dhcp/scopes/removeReservedLease", _dhcpApi.RemoveReservedLease); _webService.MapGetAndPost("/api/dhcp/scopes/enable", _dhcpApi.EnableDhcpScopeAsync); _webService.MapGetAndPost("/api/dhcp/scopes/disable", _dhcpApi.DisableDhcpScope); _webService.MapGetAndPost("/api/dhcp/scopes/delete", _dhcpApi.DeleteDhcpScope); //administration _webService.MapGetAndPost("/api/admin/sessions/list", _authApi.ListSessions); _webService.MapGetAndPost("/api/admin/sessions/createToken", _authApi.CreateApiToken); _webService.MapGetAndPost("/api/admin/sessions/delete", delegate (HttpContext context) { _authApi.DeleteSession(context, true); }); _webService.MapGetAndPost("/api/admin/users/list", _authApi.ListUsers); _webService.MapGetAndPost("/api/admin/users/create", _authApi.CreateUser); _webService.MapGetAndPost("/api/admin/users/get", _authApi.GetUserDetails); _webService.MapGetAndPost("/api/admin/users/set", _authApi.SetUserDetails); _webService.MapGetAndPost("/api/admin/users/delete", _authApi.DeleteUser); _webService.MapGetAndPost("/api/admin/groups/list", _authApi.ListGroups); _webService.MapGetAndPost("/api/admin/groups/create", _authApi.CreateGroup); _webService.MapGetAndPost("/api/admin/groups/get", _authApi.GetGroupDetails); _webService.MapGetAndPost("/api/admin/groups/set", _authApi.SetGroupDetails); _webService.MapGetAndPost("/api/admin/groups/delete", _authApi.DeleteGroup); _webService.MapGetAndPost("/api/admin/permissions/list", _authApi.ListPermissions); _webService.MapGetAndPost("/api/admin/permissions/get", delegate (HttpContext context) { _authApi.GetPermissionDetails(context, PermissionSection.Unknown); }); _webService.MapGetAndPost("/api/admin/permissions/set", delegate (HttpContext context) { _authApi.SetPermissionsDetails(context, PermissionSection.Unknown); }); //logs _webService.MapGetAndPost("/api/logs/list", _logsApi.ListLogs); _webService.MapGetAndPost("/api/logs/download", _logsApi.DownloadLogAsync); _webService.MapGetAndPost("/api/logs/delete", _logsApi.DeleteLog); _webService.MapGetAndPost("/api/logs/deleteAll", _logsApi.DeleteAllLogs); _webService.MapGetAndPost("/api/logs/query", _logsApi.QueryLogsAsync); } private async Task WebServiceApiMiddleware(HttpContext context, RequestDelegate next) { bool needsJsonResponseObject; switch (context.Request.Path) { case "/api/user/login": case "/api/user/createToken": case "/api/user/logout": needsJsonResponseObject = false; break; case "/api/user/session/get": { if (!TryGetSession(context, out UserSession session)) throw new InvalidTokenWebServiceException("Invalid token or session expired."); context.Items["session"] = session; needsJsonResponseObject = false; } break; case "/api/allowed/export": case "/api/blocked/export": case "/api/settings/backup": case "/api/logs/download": { if (!TryGetSession(context, out UserSession session)) throw new InvalidTokenWebServiceException("Invalid token or session expired."); context.Items["session"] = session; await next(context); } return; default: { if (!TryGetSession(context, out UserSession session)) throw new InvalidTokenWebServiceException("Invalid token or session expired."); context.Items["session"] = session; needsJsonResponseObject = true; } break; } using (MemoryStream mS = new MemoryStream()) { Utf8JsonWriter jsonWriter = new Utf8JsonWriter(mS); context.Items["jsonWriter"] = jsonWriter; jsonWriter.WriteStartObject(); if (needsJsonResponseObject) { jsonWriter.WritePropertyName("response"); jsonWriter.WriteStartObject(); await next(context); jsonWriter.WriteEndObject(); } else { await next(context); } jsonWriter.WriteString("status", "ok"); jsonWriter.WriteEndObject(); jsonWriter.Flush(); mS.Position = 0; HttpResponse response = context.Response; response.StatusCode = StatusCodes.Status200OK; response.ContentType = "application/json; charset=utf-8"; response.ContentLength = mS.Length; await mS.CopyToAsync(response.Body); } } private static void WebServiceExceptionHandler(IApplicationBuilder exceptionHandlerApp) { exceptionHandlerApp.Run(async delegate (HttpContext context) { IExceptionHandlerPathFeature exceptionHandlerPathFeature = context.Features.Get(); if (exceptionHandlerPathFeature.Path.StartsWith("/api/")) { Exception ex = exceptionHandlerPathFeature.Error; context.Response.StatusCode = StatusCodes.Status200OK; context.Response.ContentType = "application/json; charset=utf-8"; await using (Utf8JsonWriter jsonWriter = new Utf8JsonWriter(context.Response.Body)) { jsonWriter.WriteStartObject(); if (ex is InvalidTokenWebServiceException) { jsonWriter.WriteString("status", "invalid-token"); jsonWriter.WriteString("errorMessage", ex.Message); } else { jsonWriter.WriteString("status", "error"); jsonWriter.WriteString("errorMessage", ex.Message); jsonWriter.WriteString("stackTrace", ex.StackTrace); if (ex.InnerException is not null) jsonWriter.WriteString("innerErrorMessage", ex.InnerException.Message); } jsonWriter.WriteEndObject(); } } }); } private bool TryGetSession(HttpContext context, out UserSession session) { string token = context.Request.GetQueryOrForm("token"); session = _authManager.GetSession(token); if ((session is null) || session.User.Disabled) return false; if (session.HasExpired()) { _authManager.DeleteSession(session.Token); _authManager.SaveConfigFile(); return false; } IPEndPoint remoteEP = context.GetRemoteEndPoint(); session.UpdateLastSeen(remoteEP.Address, context.Request.Headers.UserAgent); return true; } #endregion #region tls internal void StartTlsCertificateUpdateTimer() { if (_tlsCertificateUpdateTimer is null) { _tlsCertificateUpdateTimer = new Timer(delegate (object state) { if (!string.IsNullOrEmpty(_webServiceTlsCertificatePath)) { try { FileInfo fileInfo = new FileInfo(_webServiceTlsCertificatePath); if (fileInfo.Exists && (fileInfo.LastWriteTimeUtc != _webServiceTlsCertificateLastModifiedOn)) LoadWebServiceTlsCertificate(_webServiceTlsCertificatePath, _webServiceTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while updating Web Service TLS Certificate: " + _webServiceTlsCertificatePath + "\r\n" + ex.ToString()); } } if (!string.IsNullOrEmpty(_dnsTlsCertificatePath)) { try { FileInfo fileInfo = new FileInfo(_dnsTlsCertificatePath); if (fileInfo.Exists && (fileInfo.LastWriteTimeUtc != _dnsTlsCertificateLastModifiedOn)) LoadDnsTlsCertificate(_dnsTlsCertificatePath, _dnsTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while updating DNS Server TLS Certificate: " + _dnsTlsCertificatePath + "\r\n" + ex.ToString()); } } }, null, TLS_CERTIFICATE_UPDATE_TIMER_INITIAL_INTERVAL, TLS_CERTIFICATE_UPDATE_TIMER_INTERVAL); } } internal void StopTlsCertificateUpdateTimer() { if (_tlsCertificateUpdateTimer is not null) { _tlsCertificateUpdateTimer.Dispose(); _tlsCertificateUpdateTimer = null; } } internal void LoadWebServiceTlsCertificate(string tlsCertificatePath, string tlsCertificatePassword) { FileInfo fileInfo = new FileInfo(tlsCertificatePath); if (!fileInfo.Exists) throw new ArgumentException("Web Service TLS certificate file does not exists: " + tlsCertificatePath); if (Path.GetExtension(tlsCertificatePath) != ".pfx") throw new ArgumentException("Web Service TLS certificate file must be PKCS #12 formatted with .pfx extension: " + tlsCertificatePath); _webServiceTlsCertificate = new X509Certificate2(tlsCertificatePath, tlsCertificatePassword); _webServiceTlsCertificateLastModifiedOn = fileInfo.LastWriteTimeUtc; _log.Write("Web Service TLS certificate was loaded: " + tlsCertificatePath); } internal void LoadDnsTlsCertificate(string tlsCertificatePath, string tlsCertificatePassword) { FileInfo fileInfo = new FileInfo(tlsCertificatePath); if (!fileInfo.Exists) throw new ArgumentException("DNS Server TLS certificate file does not exists: " + tlsCertificatePath); if (Path.GetExtension(tlsCertificatePath) != ".pfx") throw new ArgumentException("DNS Server TLS certificate file must be PKCS #12 formatted with .pfx extension: " + tlsCertificatePath); _dnsServer.Certificate = new X509Certificate2(tlsCertificatePath, tlsCertificatePassword); _dnsTlsCertificateLastModifiedOn = fileInfo.LastWriteTimeUtc; _log.Write("DNS Server TLS certificate was loaded: " + tlsCertificatePath); } internal void SelfSignedCertCheck(bool generateNew, bool throwException) { string selfSignedCertificateFilePath = Path.Combine(_configFolder, "cert.pfx"); if (_webServiceUseSelfSignedTlsCertificate) { if (generateNew || !File.Exists(selfSignedCertificateFilePath)) { RSA rsa = RSA.Create(2048); CertificateRequest req = new CertificateRequest("cn=" + _dnsServer.ServerDomain, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); X509Certificate2 cert = req.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(5)); File.WriteAllBytes(selfSignedCertificateFilePath, cert.Export(X509ContentType.Pkcs12, null as string)); } if (_webServiceEnableTls && string.IsNullOrEmpty(_webServiceTlsCertificatePath)) { try { LoadWebServiceTlsCertificate(selfSignedCertificateFilePath, null); } catch (Exception ex) { _log.Write("DNS Server encountered an error while loading self signed Web Service TLS certificate: " + selfSignedCertificateFilePath + "\r\n" + ex.ToString()); if (throwException) throw; } } } else { File.Delete(selfSignedCertificateFilePath); } } #endregion #region quic internal static void ValidateQuicSupport() { #pragma warning disable CA2252 // This API requires opting into preview features #pragma warning disable CA1416 // Validate platform compatibility if (!QuicConnection.IsSupported) throw new DnsWebServiceException("DNS-over-QUIC is supported only on Windows 11, Windows Server 2022, and Linux. On Linux, you must install 'libmsquic' and OpenSSL v1.1.1 manually."); #pragma warning restore CA1416 // Validate platform compatibility #pragma warning restore CA2252 // This API requires opting into preview features } #endregion #region config internal void LoadConfigFile() { string configFile = Path.Combine(_configFolder, "dns.config"); try { int version; using (FileStream fS = new FileStream(configFile, FileMode.Open, FileAccess.Read)) { version = ReadConfigFrom(new BinaryReader(fS)); } _log.Write("DNS Server config file was loaded: " + configFile); if (version <= 27) SaveConfigFile(); //save as new config version to avoid loading old version next time } catch (FileNotFoundException) { _log.Write("DNS Server config file was not found: " + configFile); _log.Write("DNS Server is restoring default config file."); //general string serverDomain = Environment.GetEnvironmentVariable("DNS_SERVER_DOMAIN"); if (!string.IsNullOrEmpty(serverDomain)) _dnsServer.ServerDomain = serverDomain; _appsApi.EnableAutomaticUpdate = true; string strPreferIPv6 = Environment.GetEnvironmentVariable("DNS_SERVER_PREFER_IPV6"); if (!string.IsNullOrEmpty(strPreferIPv6)) _dnsServer.PreferIPv6 = bool.Parse(strPreferIPv6); _dnsServer.DnssecValidation = true; CreateForwarderZoneToDisableDnssecForNTP(); //web service string strWebServiceHttpPort = Environment.GetEnvironmentVariable("DNS_SERVER_WEB_SERVICE_HTTP_PORT"); if (!string.IsNullOrEmpty(strWebServiceHttpPort)) _webServiceHttpPort = int.Parse(strWebServiceHttpPort); string webServiceTlsPort = Environment.GetEnvironmentVariable("DNS_SERVER_WEB_SERVICE_HTTPS_PORT"); if (!string.IsNullOrEmpty(webServiceTlsPort)) _webServiceTlsPort = int.Parse(webServiceTlsPort); string webServiceEnableTls = Environment.GetEnvironmentVariable("DNS_SERVER_WEB_SERVICE_ENABLE_HTTPS"); if (!string.IsNullOrEmpty(webServiceEnableTls)) _webServiceEnableTls = bool.Parse(webServiceEnableTls); string webServiceUseSelfSignedTlsCertificate = Environment.GetEnvironmentVariable("DNS_SERVER_WEB_SERVICE_USE_SELF_SIGNED_CERT"); if (!string.IsNullOrEmpty(webServiceUseSelfSignedTlsCertificate)) _webServiceUseSelfSignedTlsCertificate = bool.Parse(webServiceUseSelfSignedTlsCertificate); //optional protocols string strDnsOverHttp = Environment.GetEnvironmentVariable("DNS_SERVER_OPTIONAL_PROTOCOL_DNS_OVER_HTTP"); if (!string.IsNullOrEmpty(strDnsOverHttp)) _dnsServer.EnableDnsOverHttp = bool.Parse(strDnsOverHttp); //recursion string strRecursion = Environment.GetEnvironmentVariable("DNS_SERVER_RECURSION"); if (!string.IsNullOrEmpty(strRecursion)) _dnsServer.Recursion = Enum.Parse(strRecursion, true); else _dnsServer.Recursion = DnsServerRecursion.AllowOnlyForPrivateNetworks; //default for security reasons string strRecursionDeniedNetworks = Environment.GetEnvironmentVariable("DNS_SERVER_RECURSION_DENIED_NETWORKS"); if (!string.IsNullOrEmpty(strRecursionDeniedNetworks)) _dnsServer.RecursionDeniedNetworks = strRecursionDeniedNetworks.Split(NetworkAddress.Parse, ','); string strRecursionAllowedNetworks = Environment.GetEnvironmentVariable("DNS_SERVER_RECURSION_ALLOWED_NETWORKS"); if (!string.IsNullOrEmpty(strRecursionAllowedNetworks)) _dnsServer.RecursionAllowedNetworks = strRecursionAllowedNetworks.Split(NetworkAddress.Parse, ','); _dnsServer.RandomizeName = true; //default true to enable security feature _dnsServer.QnameMinimization = true; //default true to enable privacy feature _dnsServer.NsRevalidation = true; //default true for security reasons //cache _dnsServer.CacheZoneManager.MaximumEntries = 10000; //blocking string strEnableBlocking = Environment.GetEnvironmentVariable("DNS_SERVER_ENABLE_BLOCKING"); if (!string.IsNullOrEmpty(strEnableBlocking)) _dnsServer.EnableBlocking = bool.Parse(strEnableBlocking); string strAllowTxtBlockingReport = Environment.GetEnvironmentVariable("DNS_SERVER_ALLOW_TXT_BLOCKING_REPORT"); if (!string.IsNullOrEmpty(strAllowTxtBlockingReport)) _dnsServer.AllowTxtBlockingReport = bool.Parse(strAllowTxtBlockingReport); string strBlockListUrls = Environment.GetEnvironmentVariable("DNS_SERVER_BLOCK_LIST_URLS"); if (!string.IsNullOrEmpty(strBlockListUrls)) { string[] strBlockListUrlList = strBlockListUrls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string strBlockListUrl in strBlockListUrlList) { if (strBlockListUrl.StartsWith('!')) { Uri allowListUrl = new Uri(strBlockListUrl.Substring(1)); if (!_dnsServer.BlockListZoneManager.AllowListUrls.Contains(allowListUrl)) _dnsServer.BlockListZoneManager.AllowListUrls.Add(allowListUrl); } else { Uri blockListUrl = new Uri(strBlockListUrl); if (!_dnsServer.BlockListZoneManager.BlockListUrls.Contains(blockListUrl)) _dnsServer.BlockListZoneManager.BlockListUrls.Add(blockListUrl); } } } //proxy & forwarders string strForwarders = Environment.GetEnvironmentVariable("DNS_SERVER_FORWARDERS"); if (!string.IsNullOrEmpty(strForwarders)) { DnsTransportProtocol forwarderProtocol; string strForwarderProtocol = Environment.GetEnvironmentVariable("DNS_SERVER_FORWARDER_PROTOCOL"); if (string.IsNullOrEmpty(strForwarderProtocol)) { forwarderProtocol = DnsTransportProtocol.Udp; } else { forwarderProtocol = Enum.Parse(strForwarderProtocol, true); if (forwarderProtocol == DnsTransportProtocol.HttpsJson) forwarderProtocol = DnsTransportProtocol.Https; } _dnsServer.Forwarders = strForwarders.Split(delegate (string value) { NameServerAddress forwarder = NameServerAddress.Parse(value); if (forwarder.Protocol != forwarderProtocol) forwarder = forwarder.ChangeProtocol(forwarderProtocol); return forwarder; }, ','); } //logging string strUseLocalTime = Environment.GetEnvironmentVariable("DNS_SERVER_LOG_USING_LOCAL_TIME"); if (!string.IsNullOrEmpty(strUseLocalTime)) _log.UseLocalTime = bool.Parse(strUseLocalTime); SaveConfigFile(); } catch (Exception ex) { _log.Write("DNS Server encountered an error while loading config file: " + configFile + "\r\n" + ex.ToString()); _log.Write("Note: You may try deleting the config file to fix this issue. However, you will lose DNS settings but, zone data wont be affected."); throw; } } private void CreateForwarderZoneToDisableDnssecForNTP() { if (Environment.OSVersion.Platform == PlatformID.Unix) { //adding a conditional forwarder zone for disabling DNSSEC validation for ntp.org so that systems with no real-time clock can sync time string ntpDomain = "ntp.org"; string fwdRecordComments = "This forwarder zone was automatically created to disable DNSSEC validation for ntp.org to allow systems with no real-time clock (e.g. Raspberry Pi) to sync time via NTP when booting."; if (_dnsServer.AuthZoneManager.CreateForwarderZone(ntpDomain, DnsTransportProtocol.Udp, "this-server", false, NetProxyType.None, null, 0, null, null, fwdRecordComments) is not null) { //set permissions _authManager.SetPermission(PermissionSection.Zones, ntpDomain, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); _authManager.SetPermission(PermissionSection.Zones, ntpDomain, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); _authManager.SaveConfigFile(); Directory.CreateDirectory(Path.Combine(_dnsServer.ConfigFolder, "zones")); _dnsServer.AuthZoneManager.SaveZoneFile(ntpDomain); } } } internal void SaveConfigFile() { string configFile = Path.Combine(_configFolder, "dns.config"); using (MemoryStream mS = new MemoryStream()) { //serialize config WriteConfigTo(new BinaryWriter(mS)); //write config mS.Position = 0; using (FileStream fS = new FileStream(configFile, FileMode.Create, FileAccess.Write)) { mS.CopyTo(fS); } } _log.Write("DNS Server config file was saved: " + configFile); } internal void InspectAndFixZonePermissions() { Permission permission = _authManager.GetPermission(PermissionSection.Zones); IReadOnlyDictionary subItemPermissions = permission.SubItemPermissions; //remove ghost permissions foreach (KeyValuePair subItemPermission in subItemPermissions) { string zoneName = subItemPermission.Key; if (_dnsServer.AuthZoneManager.GetAuthZoneInfo(zoneName) is null) permission.RemoveAllSubItemPermissions(zoneName); //no such zone exists; remove permissions } //add missing admin permissions IReadOnlyList zones = _dnsServer.AuthZoneManager.GetAllZones(); Group admins = _authManager.GetGroup(Group.ADMINISTRATORS); Group dnsAdmins = _authManager.GetGroup(Group.DNS_ADMINISTRATORS); foreach (AuthZoneInfo zone in zones) { if (zone.Internal) { _authManager.SetPermission(PermissionSection.Zones, zone.Name, admins, PermissionFlag.View); _authManager.SetPermission(PermissionSection.Zones, zone.Name, dnsAdmins, PermissionFlag.View); } else { _authManager.SetPermission(PermissionSection.Zones, zone.Name, admins, PermissionFlag.ViewModifyDelete); _authManager.SetPermission(PermissionSection.Zones, zone.Name, dnsAdmins, PermissionFlag.ViewModifyDelete); } } _authManager.SaveConfigFile(); } private int ReadConfigFrom(BinaryReader bR) { if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DS") //format throw new InvalidDataException("DNS Server config file format is invalid."); int version = bR.ReadByte(); if ((version >= 28) && (version <= 31)) { ReadConfigFrom(bR, version); } else if ((version >= 2) && (version <= 27)) { ReadOldConfigFrom(bR, version); //new default settings _appsApi.EnableAutomaticUpdate = true; } else { throw new InvalidDataException("DNS Server config version not supported."); } return version; } private void ReadConfigFrom(BinaryReader bR, int version) { //web service { _webServiceHttpPort = bR.ReadInt32(); _webServiceTlsPort = bR.ReadInt32(); { int count = bR.ReadByte(); if (count > 0) { IPAddress[] localAddresses = new IPAddress[count]; for (int i = 0; i < count; i++) localAddresses[i] = IPAddressExtensions.ReadFrom(bR); _webServiceLocalAddresses = localAddresses; } } _webServiceEnableTls = bR.ReadBoolean(); _webServiceHttpToTlsRedirect = bR.ReadBoolean(); _webServiceUseSelfSignedTlsCertificate = bR.ReadBoolean(); _webServiceTlsCertificatePath = bR.ReadShortString(); _webServiceTlsCertificatePassword = bR.ReadShortString(); if (_webServiceTlsCertificatePath.Length == 0) _webServiceTlsCertificatePath = null; if (_webServiceTlsCertificatePath != null) { try { LoadWebServiceTlsCertificate(_webServiceTlsCertificatePath, _webServiceTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while loading Web Service TLS certificate: " + _webServiceTlsCertificatePath + "\r\n" + ex.ToString()); } StartTlsCertificateUpdateTimer(); } SelfSignedCertCheck(false, false); } //dns { //general _dnsServer.ServerDomain = bR.ReadShortString(); { int count = bR.ReadByte(); if (count > 0) { IPEndPoint[] localEndPoints = new IPEndPoint[count]; for (int i = 0; i < count; i++) localEndPoints[i] = (IPEndPoint)EndPointExtensions.ReadFrom(bR); _dnsServer.LocalEndPoints = localEndPoints; } } _zonesApi.DefaultRecordTtl = bR.ReadUInt32(); _appsApi.EnableAutomaticUpdate = bR.ReadBoolean(); _dnsServer.PreferIPv6 = bR.ReadBoolean(); _dnsServer.UdpPayloadSize = bR.ReadUInt16(); _dnsServer.DnssecValidation = bR.ReadBoolean(); if (version >= 29) { _dnsServer.EDnsClientSubnet = bR.ReadBoolean(); _dnsServer.EDnsClientSubnetIPv4PrefixLength = bR.ReadByte(); _dnsServer.EDnsClientSubnetIPv6PrefixLength = bR.ReadByte(); } else { _dnsServer.EDnsClientSubnet = false; _dnsServer.EDnsClientSubnetIPv4PrefixLength = 24; _dnsServer.EDnsClientSubnetIPv6PrefixLength = 56; } _dnsServer.QpmLimitRequests = bR.ReadInt32(); _dnsServer.QpmLimitErrors = bR.ReadInt32(); _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); _dnsServer.QpmLimitIPv4PrefixLength = bR.ReadInt32(); _dnsServer.QpmLimitIPv6PrefixLength = bR.ReadInt32(); _dnsServer.ClientTimeout = bR.ReadInt32(); _dnsServer.TcpSendTimeout = bR.ReadInt32(); _dnsServer.TcpReceiveTimeout = bR.ReadInt32(); if (version >= 30) { _dnsServer.QuicIdleTimeout = bR.ReadInt32(); _dnsServer.QuicMaxInboundStreams = bR.ReadInt32(); _dnsServer.ListenBacklog = bR.ReadInt32(); } else { _dnsServer.QuicIdleTimeout = 60000; _dnsServer.QuicMaxInboundStreams = 100; _dnsServer.ListenBacklog = 100; } //optional protocols _dnsServer.EnableDnsOverHttp = bR.ReadBoolean(); _dnsServer.EnableDnsOverTls = bR.ReadBoolean(); _dnsServer.EnableDnsOverHttps = bR.ReadBoolean(); if (version >= 31) { _dnsServer.EnableDnsOverQuic = bR.ReadBoolean(); _dnsServer.DnsOverHttpPort = bR.ReadInt32(); _dnsServer.DnsOverTlsPort = bR.ReadInt32(); _dnsServer.DnsOverHttpsPort = bR.ReadInt32(); _dnsServer.DnsOverQuicPort = bR.ReadInt32(); } else if (version >= 30) { _ = bR.ReadBoolean(); //removed EnableDnsOverHttpPort80 value _dnsServer.EnableDnsOverQuic = bR.ReadBoolean(); _dnsServer.DnsOverHttpPort = bR.ReadInt32(); _dnsServer.DnsOverTlsPort = bR.ReadInt32(); _dnsServer.DnsOverHttpsPort = bR.ReadInt32(); _dnsServer.DnsOverQuicPort = bR.ReadInt32(); } else { _dnsServer.EnableDnsOverQuic = false; if (_dnsServer.EnableDnsOverHttps) { _dnsServer.EnableDnsOverHttp = true; _dnsServer.DnsOverHttpPort = 80; } else if (_dnsServer.EnableDnsOverHttp) { _dnsServer.DnsOverHttpPort = 8053; } else { _dnsServer.DnsOverHttpPort = 80; } _dnsServer.DnsOverTlsPort = 853; _dnsServer.DnsOverHttpsPort = 443; _dnsServer.DnsOverQuicPort = 853; } _dnsTlsCertificatePath = bR.ReadShortString(); _dnsTlsCertificatePassword = bR.ReadShortString(); if (_dnsTlsCertificatePath.Length == 0) _dnsTlsCertificatePath = null; if (_dnsTlsCertificatePath != null) { try { LoadDnsTlsCertificate(_dnsTlsCertificatePath, _dnsTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while loading DNS Server TLS certificate: " + _dnsTlsCertificatePath + "\r\n" + ex.ToString()); } StartTlsCertificateUpdateTimer(); } //tsig { int count = bR.ReadByte(); Dictionary tsigKeys = new Dictionary(count); for (int i = 0; i < count; i++) { string keyName = bR.ReadShortString(); string sharedSecret = bR.ReadShortString(); TsigAlgorithm algorithm = (TsigAlgorithm)bR.ReadByte(); tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, algorithm)); } _dnsServer.TsigKeys = tsigKeys; } //recursion _dnsServer.Recursion = (DnsServerRecursion)bR.ReadByte(); { int count = bR.ReadByte(); if (count > 0) { NetworkAddress[] networks = new NetworkAddress[count]; for (int i = 0; i < count; i++) networks[i] = NetworkAddress.ReadFrom(bR); _dnsServer.RecursionDeniedNetworks = networks; } } { int count = bR.ReadByte(); if (count > 0) { NetworkAddress[] networks = new NetworkAddress[count]; for (int i = 0; i < count; i++) networks[i] = NetworkAddress.ReadFrom(bR); _dnsServer.RecursionAllowedNetworks = networks; } } _dnsServer.RandomizeName = bR.ReadBoolean(); _dnsServer.QnameMinimization = bR.ReadBoolean(); _dnsServer.NsRevalidation = bR.ReadBoolean(); _dnsServer.ResolverRetries = bR.ReadInt32(); _dnsServer.ResolverTimeout = bR.ReadInt32(); _dnsServer.ResolverMaxStackCount = bR.ReadInt32(); //cache if (version >= 30) _saveCache = bR.ReadBoolean(); else _saveCache = true; _dnsServer.ServeStale = bR.ReadBoolean(); _dnsServer.CacheZoneManager.ServeStaleTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.MaximumEntries = bR.ReadInt64(); _dnsServer.CacheZoneManager.MinimumRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.MaximumRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.NegativeRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.FailureRecordTtl = bR.ReadUInt32(); _dnsServer.CachePrefetchEligibility = bR.ReadInt32(); _dnsServer.CachePrefetchTrigger = bR.ReadInt32(); _dnsServer.CachePrefetchSampleIntervalInMinutes = bR.ReadInt32(); _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = bR.ReadInt32(); //blocking _dnsServer.EnableBlocking = bR.ReadBoolean(); _dnsServer.AllowTxtBlockingReport = bR.ReadBoolean(); _dnsServer.BlockingType = (DnsServerBlockingType)bR.ReadByte(); { //read custom blocking addresses int count = bR.ReadByte(); if (count > 0) { List dnsARecords = new List(); List dnsAAAARecords = new List(); for (int i = 0; i < count; i++) { IPAddress customAddress = IPAddressExtensions.ReadFrom(bR); switch (customAddress.AddressFamily) { case AddressFamily.InterNetwork: dnsARecords.Add(new DnsARecordData(customAddress)); break; case AddressFamily.InterNetworkV6: dnsAAAARecords.Add(new DnsAAAARecordData(customAddress)); break; } } _dnsServer.CustomBlockingARecords = dnsARecords; _dnsServer.CustomBlockingAAAARecords = dnsAAAARecords; } } { //read block list urls int count = bR.ReadByte(); for (int i = 0; i < count; i++) { string listUrl = bR.ReadShortString(); if (listUrl.StartsWith('!')) _dnsServer.BlockListZoneManager.AllowListUrls.Add(new Uri(listUrl.Substring(1))); else _dnsServer.BlockListZoneManager.BlockListUrls.Add(new Uri(listUrl)); } _settingsApi.BlockListUpdateIntervalHours = bR.ReadInt32(); _settingsApi.BlockListLastUpdatedOn = bR.ReadDateTime(); } //proxy & forwarders NetProxyType proxyType = (NetProxyType)bR.ReadByte(); if (proxyType != NetProxyType.None) { string address = bR.ReadShortString(); int port = bR.ReadInt32(); NetworkCredential credential = null; if (bR.ReadBoolean()) //credential set credential = new NetworkCredential(bR.ReadShortString(), bR.ReadShortString()); _dnsServer.Proxy = NetProxy.CreateProxy(proxyType, address, port, credential); int count = bR.ReadByte(); List bypassList = new List(count); for (int i = 0; i < count; i++) bypassList.Add(new NetProxyBypassItem(bR.ReadShortString())); _dnsServer.Proxy.BypassList = bypassList; } else { _dnsServer.Proxy = null; } { int count = bR.ReadByte(); if (count > 0) { NameServerAddress[] forwarders = new NameServerAddress[count]; for (int i = 0; i < count; i++) { forwarders[i] = new NameServerAddress(bR); if (forwarders[i].Protocol == DnsTransportProtocol.HttpsJson) forwarders[i] = forwarders[i].ChangeProtocol(DnsTransportProtocol.Https); } _dnsServer.Forwarders = forwarders; } } _dnsServer.ForwarderRetries = bR.ReadInt32(); _dnsServer.ForwarderTimeout = bR.ReadInt32(); _dnsServer.ForwarderConcurrency = bR.ReadInt32(); //logging if (bR.ReadBoolean()) //log all queries _dnsServer.QueryLogManager = _log; else _dnsServer.QueryLogManager = null; _dnsServer.StatsManager.MaxStatFileDays = bR.ReadInt32(); } if ((_webServiceTlsCertificatePath == null) && (_dnsTlsCertificatePath == null)) StopTlsCertificateUpdateTimer(); } private void ReadOldConfigFrom(BinaryReader bR, int version) { _dnsServer.ServerDomain = bR.ReadShortString(); _webServiceHttpPort = bR.ReadInt32(); if (version >= 13) { { int count = bR.ReadByte(); if (count > 0) { IPAddress[] localAddresses = new IPAddress[count]; for (int i = 0; i < count; i++) localAddresses[i] = IPAddressExtensions.ReadFrom(bR); _webServiceLocalAddresses = localAddresses; } } _webServiceTlsPort = bR.ReadInt32(); _webServiceEnableTls = bR.ReadBoolean(); _webServiceHttpToTlsRedirect = bR.ReadBoolean(); _webServiceTlsCertificatePath = bR.ReadShortString(); _webServiceTlsCertificatePassword = bR.ReadShortString(); if (_webServiceTlsCertificatePath.Length == 0) _webServiceTlsCertificatePath = null; if (_webServiceTlsCertificatePath != null) { try { LoadWebServiceTlsCertificate(_webServiceTlsCertificatePath, _webServiceTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while loading Web Service TLS certificate: " + _webServiceTlsCertificatePath + "\r\n" + ex.ToString()); } StartTlsCertificateUpdateTimer(); } } else { _webServiceLocalAddresses = new IPAddress[] { IPAddress.Any, IPAddress.IPv6Any }; _webServiceTlsPort = 53443; _webServiceEnableTls = false; _webServiceHttpToTlsRedirect = false; _webServiceTlsCertificatePath = string.Empty; _webServiceTlsCertificatePassword = string.Empty; } _dnsServer.PreferIPv6 = bR.ReadBoolean(); if (bR.ReadBoolean()) //logQueries _dnsServer.QueryLogManager = _log; if (version >= 14) _dnsServer.StatsManager.MaxStatFileDays = bR.ReadInt32(); else _dnsServer.StatsManager.MaxStatFileDays = 0; if (version >= 17) { _dnsServer.Recursion = (DnsServerRecursion)bR.ReadByte(); { int count = bR.ReadByte(); if (count > 0) { NetworkAddress[] networks = new NetworkAddress[count]; for (int i = 0; i < count; i++) networks[i] = NetworkAddress.ReadFrom(bR); _dnsServer.RecursionDeniedNetworks = networks; } } { int count = bR.ReadByte(); if (count > 0) { NetworkAddress[] networks = new NetworkAddress[count]; for (int i = 0; i < count; i++) networks[i] = NetworkAddress.ReadFrom(bR); _dnsServer.RecursionAllowedNetworks = networks; } } } else { bool allowRecursion = bR.ReadBoolean(); bool allowRecursionOnlyForPrivateNetworks; if (version >= 4) allowRecursionOnlyForPrivateNetworks = bR.ReadBoolean(); else allowRecursionOnlyForPrivateNetworks = true; //default true for security reasons if (allowRecursion) { if (allowRecursionOnlyForPrivateNetworks) _dnsServer.Recursion = DnsServerRecursion.AllowOnlyForPrivateNetworks; else _dnsServer.Recursion = DnsServerRecursion.Allow; } else { _dnsServer.Recursion = DnsServerRecursion.Deny; } } if (version >= 12) _dnsServer.RandomizeName = bR.ReadBoolean(); else _dnsServer.RandomizeName = true; //default true to enable security feature if (version >= 15) _dnsServer.QnameMinimization = bR.ReadBoolean(); else _dnsServer.QnameMinimization = true; //default true to enable privacy feature if (version >= 20) { _dnsServer.QpmLimitRequests = bR.ReadInt32(); _dnsServer.QpmLimitErrors = bR.ReadInt32(); _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); _dnsServer.QpmLimitIPv4PrefixLength = bR.ReadInt32(); _dnsServer.QpmLimitIPv6PrefixLength = bR.ReadInt32(); } else if (version >= 17) { _dnsServer.QpmLimitRequests = bR.ReadInt32(); _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); _ = bR.ReadInt32(); //read obsolete value _dnsServer.QpmLimitSamplingIntervalInMinutes } else { _dnsServer.QpmLimitRequests = 0; _dnsServer.QpmLimitErrors = 0; _dnsServer.QpmLimitSampleMinutes = 1; _dnsServer.QpmLimitIPv4PrefixLength = 24; _dnsServer.QpmLimitIPv6PrefixLength = 56; } if (version >= 13) { _dnsServer.ServeStale = bR.ReadBoolean(); _dnsServer.CacheZoneManager.ServeStaleTtl = bR.ReadUInt32(); } else { _dnsServer.ServeStale = true; _dnsServer.CacheZoneManager.ServeStaleTtl = CacheZoneManager.SERVE_STALE_TTL; } if (version >= 9) { _dnsServer.CachePrefetchEligibility = bR.ReadInt32(); _dnsServer.CachePrefetchTrigger = bR.ReadInt32(); _dnsServer.CachePrefetchSampleIntervalInMinutes = bR.ReadInt32(); _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = bR.ReadInt32(); } else { _dnsServer.CachePrefetchEligibility = 2; _dnsServer.CachePrefetchTrigger = 9; _dnsServer.CachePrefetchSampleIntervalInMinutes = 5; _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = 30; } NetProxyType proxyType = (NetProxyType)bR.ReadByte(); if (proxyType != NetProxyType.None) { string address = bR.ReadShortString(); int port = bR.ReadInt32(); NetworkCredential credential = null; if (bR.ReadBoolean()) //credential set credential = new NetworkCredential(bR.ReadShortString(), bR.ReadShortString()); _dnsServer.Proxy = NetProxy.CreateProxy(proxyType, address, port, credential); if (version >= 10) { int count = bR.ReadByte(); List bypassList = new List(count); for (int i = 0; i < count; i++) bypassList.Add(new NetProxyBypassItem(bR.ReadShortString())); _dnsServer.Proxy.BypassList = bypassList; } else { _dnsServer.Proxy.BypassList = null; } } else { _dnsServer.Proxy = null; } { int count = bR.ReadByte(); if (count > 0) { NameServerAddress[] forwarders = new NameServerAddress[count]; for (int i = 0; i < count; i++) { forwarders[i] = new NameServerAddress(bR); if (forwarders[i].Protocol == DnsTransportProtocol.HttpsJson) forwarders[i] = forwarders[i].ChangeProtocol(DnsTransportProtocol.Https); } _dnsServer.Forwarders = forwarders; } } if (version <= 10) { DnsTransportProtocol forwarderProtocol = (DnsTransportProtocol)bR.ReadByte(); if (forwarderProtocol == DnsTransportProtocol.HttpsJson) forwarderProtocol = DnsTransportProtocol.Https; if (_dnsServer.Forwarders != null) { List forwarders = new List(); foreach (NameServerAddress forwarder in _dnsServer.Forwarders) { if (forwarder.Protocol == forwarderProtocol) forwarders.Add(forwarder); else forwarders.Add(forwarder.ChangeProtocol(forwarderProtocol)); } _dnsServer.Forwarders = forwarders; } } { int count = bR.ReadByte(); if (count > 0) { if (version > 2) { for (int i = 0; i < count; i++) { string username = bR.ReadShortString(); string passwordHash = bR.ReadShortString(); if (username.Equals("admin", StringComparison.OrdinalIgnoreCase)) { _authManager.LoadOldConfig(passwordHash, true); break; } } } else { for (int i = 0; i < count; i++) { string username = bR.ReadShortString(); string password = bR.ReadShortString(); if (username.Equals("admin", StringComparison.OrdinalIgnoreCase)) { _authManager.LoadOldConfig(password, false); break; } } } } } if (version <= 6) { int count = bR.ReadInt32(); _configDisabledZones = new List(count); for (int i = 0; i < count; i++) { string domain = bR.ReadShortString(); _configDisabledZones.Add(domain); } } if (version >= 18) _dnsServer.EnableBlocking = bR.ReadBoolean(); else _dnsServer.EnableBlocking = true; if (version >= 18) _dnsServer.BlockingType = (DnsServerBlockingType)bR.ReadByte(); else if (version >= 16) _dnsServer.BlockingType = bR.ReadBoolean() ? DnsServerBlockingType.NxDomain : DnsServerBlockingType.AnyAddress; else _dnsServer.BlockingType = DnsServerBlockingType.AnyAddress; if (version >= 18) { //read custom blocking addresses int count = bR.ReadByte(); if (count > 0) { List dnsARecords = new List(); List dnsAAAARecords = new List(); for (int i = 0; i < count; i++) { IPAddress customAddress = IPAddressExtensions.ReadFrom(bR); switch (customAddress.AddressFamily) { case AddressFamily.InterNetwork: dnsARecords.Add(new DnsARecordData(customAddress)); break; case AddressFamily.InterNetworkV6: dnsAAAARecords.Add(new DnsAAAARecordData(customAddress)); break; } } _dnsServer.CustomBlockingARecords = dnsARecords; _dnsServer.CustomBlockingAAAARecords = dnsAAAARecords; } } else { _dnsServer.CustomBlockingARecords = null; _dnsServer.CustomBlockingAAAARecords = null; } if (version > 4) { //read block list urls int count = bR.ReadByte(); for (int i = 0; i < count; i++) { string listUrl = bR.ReadShortString(); if (listUrl.StartsWith('!')) _dnsServer.BlockListZoneManager.AllowListUrls.Add(new Uri(listUrl.Substring(1))); else _dnsServer.BlockListZoneManager.BlockListUrls.Add(new Uri(listUrl)); } _settingsApi.BlockListLastUpdatedOn = bR.ReadDateTime(); if (version >= 13) _settingsApi.BlockListUpdateIntervalHours = bR.ReadInt32(); } else { _dnsServer.BlockListZoneManager.AllowListUrls.Clear(); _dnsServer.BlockListZoneManager.BlockListUrls.Clear(); _settingsApi.BlockListLastUpdatedOn = DateTime.MinValue; _settingsApi.BlockListUpdateIntervalHours = 24; } if (version >= 11) { int count = bR.ReadByte(); if (count > 0) { IPEndPoint[] localEndPoints = new IPEndPoint[count]; for (int i = 0; i < count; i++) localEndPoints[i] = (IPEndPoint)EndPointExtensions.ReadFrom(bR); _dnsServer.LocalEndPoints = localEndPoints; } } else if (version >= 6) { int count = bR.ReadByte(); if (count > 0) { IPEndPoint[] localEndPoints = new IPEndPoint[count]; for (int i = 0; i < count; i++) localEndPoints[i] = new IPEndPoint(IPAddressExtensions.ReadFrom(bR), 53); _dnsServer.LocalEndPoints = localEndPoints; } } else { _dnsServer.LocalEndPoints = new IPEndPoint[] { new IPEndPoint(IPAddress.Any, 53), new IPEndPoint(IPAddress.IPv6Any, 53) }; } if (version >= 8) { _dnsServer.EnableDnsOverHttp = bR.ReadBoolean(); _dnsServer.EnableDnsOverTls = bR.ReadBoolean(); _dnsServer.EnableDnsOverHttps = bR.ReadBoolean(); _dnsTlsCertificatePath = bR.ReadShortString(); _dnsTlsCertificatePassword = bR.ReadShortString(); if (_dnsTlsCertificatePath.Length == 0) _dnsTlsCertificatePath = null; if (_dnsTlsCertificatePath != null) { try { LoadDnsTlsCertificate(_dnsTlsCertificatePath, _dnsTlsCertificatePassword); } catch (Exception ex) { _log.Write("DNS Server encountered an error while loading DNS Server TLS certificate: " + _dnsTlsCertificatePath + "\r\n" + ex.ToString()); } StartTlsCertificateUpdateTimer(); } } else { _dnsServer.EnableDnsOverHttp = false; _dnsServer.EnableDnsOverTls = false; _dnsServer.EnableDnsOverHttps = false; _dnsTlsCertificatePath = string.Empty; _dnsTlsCertificatePassword = string.Empty; } if (version >= 19) { _dnsServer.CacheZoneManager.MinimumRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.MaximumRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.NegativeRecordTtl = bR.ReadUInt32(); _dnsServer.CacheZoneManager.FailureRecordTtl = bR.ReadUInt32(); } else { _dnsServer.CacheZoneManager.MinimumRecordTtl = CacheZoneManager.MINIMUM_RECORD_TTL; _dnsServer.CacheZoneManager.MaximumRecordTtl = CacheZoneManager.MAXIMUM_RECORD_TTL; _dnsServer.CacheZoneManager.NegativeRecordTtl = CacheZoneManager.NEGATIVE_RECORD_TTL; _dnsServer.CacheZoneManager.FailureRecordTtl = CacheZoneManager.FAILURE_RECORD_TTL; } if (version >= 21) { int count = bR.ReadByte(); Dictionary tsigKeys = new Dictionary(count); for (int i = 0; i < count; i++) { string keyName = bR.ReadShortString(); string sharedSecret = bR.ReadShortString(); TsigAlgorithm algorithm = (TsigAlgorithm)bR.ReadByte(); tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, algorithm)); } _dnsServer.TsigKeys = tsigKeys; } else if (version >= 20) { int count = bR.ReadByte(); Dictionary tsigKeys = new Dictionary(count); for (int i = 0; i < count; i++) { string keyName = bR.ReadShortString(); string sharedSecret = bR.ReadShortString(); tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, TsigAlgorithm.HMAC_SHA256)); } _dnsServer.TsigKeys = tsigKeys; } else { _dnsServer.TsigKeys = null; } if (version >= 22) _dnsServer.NsRevalidation = bR.ReadBoolean(); else _dnsServer.NsRevalidation = true; //default true for security reasons if (version >= 23) { _dnsServer.AllowTxtBlockingReport = bR.ReadBoolean(); _zonesApi.DefaultRecordTtl = bR.ReadUInt32(); } else { _dnsServer.AllowTxtBlockingReport = true; _zonesApi.DefaultRecordTtl = 3600; } if (version >= 24) { _webServiceUseSelfSignedTlsCertificate = bR.ReadBoolean(); SelfSignedCertCheck(false, false); } else { _webServiceUseSelfSignedTlsCertificate = false; } if (version >= 25) _dnsServer.UdpPayloadSize = bR.ReadUInt16(); else _dnsServer.UdpPayloadSize = DnsDatagram.EDNS_DEFAULT_UDP_PAYLOAD_SIZE; if (version >= 26) { _dnsServer.DnssecValidation = bR.ReadBoolean(); _dnsServer.ResolverRetries = bR.ReadInt32(); _dnsServer.ResolverTimeout = bR.ReadInt32(); _dnsServer.ResolverMaxStackCount = bR.ReadInt32(); _dnsServer.ForwarderRetries = bR.ReadInt32(); _dnsServer.ForwarderTimeout = bR.ReadInt32(); _dnsServer.ForwarderConcurrency = bR.ReadInt32(); _dnsServer.ClientTimeout = bR.ReadInt32(); _dnsServer.TcpSendTimeout = bR.ReadInt32(); _dnsServer.TcpReceiveTimeout = bR.ReadInt32(); } else { _dnsServer.DnssecValidation = true; CreateForwarderZoneToDisableDnssecForNTP(); _dnsServer.ResolverRetries = 2; _dnsServer.ResolverTimeout = 2000; _dnsServer.ResolverMaxStackCount = 16; _dnsServer.ForwarderRetries = 3; _dnsServer.ForwarderTimeout = 2000; _dnsServer.ForwarderConcurrency = 2; _dnsServer.ClientTimeout = 4000; _dnsServer.TcpSendTimeout = 10000; _dnsServer.TcpReceiveTimeout = 10000; } if (version >= 27) _dnsServer.CacheZoneManager.MaximumEntries = bR.ReadInt32(); else _dnsServer.CacheZoneManager.MaximumEntries = 10000; } private void WriteConfigTo(BinaryWriter bW) { bW.Write(Encoding.ASCII.GetBytes("DS")); //format bW.Write((byte)31); //version //web service { bW.Write(_webServiceHttpPort); bW.Write(_webServiceTlsPort); { bW.Write(Convert.ToByte(_webServiceLocalAddresses.Count)); foreach (IPAddress localAddress in _webServiceLocalAddresses) localAddress.WriteTo(bW); } bW.Write(_webServiceEnableTls); bW.Write(_webServiceHttpToTlsRedirect); bW.Write(_webServiceUseSelfSignedTlsCertificate); if (_webServiceTlsCertificatePath is null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_webServiceTlsCertificatePath); if (_webServiceTlsCertificatePassword is null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_webServiceTlsCertificatePassword); } //dns { //general bW.WriteShortString(_dnsServer.ServerDomain); { bW.Write(Convert.ToByte(_dnsServer.LocalEndPoints.Count)); foreach (IPEndPoint localEP in _dnsServer.LocalEndPoints) localEP.WriteTo(bW); } bW.Write(_zonesApi.DefaultRecordTtl); bW.Write(_appsApi.EnableAutomaticUpdate); bW.Write(_dnsServer.PreferIPv6); bW.Write(_dnsServer.UdpPayloadSize); bW.Write(_dnsServer.DnssecValidation); bW.Write(_dnsServer.EDnsClientSubnet); bW.Write(_dnsServer.EDnsClientSubnetIPv4PrefixLength); bW.Write(_dnsServer.EDnsClientSubnetIPv6PrefixLength); bW.Write(_dnsServer.QpmLimitRequests); bW.Write(_dnsServer.QpmLimitErrors); bW.Write(_dnsServer.QpmLimitSampleMinutes); bW.Write(_dnsServer.QpmLimitIPv4PrefixLength); bW.Write(_dnsServer.QpmLimitIPv6PrefixLength); bW.Write(_dnsServer.ClientTimeout); bW.Write(_dnsServer.TcpSendTimeout); bW.Write(_dnsServer.TcpReceiveTimeout); bW.Write(_dnsServer.QuicIdleTimeout); bW.Write(_dnsServer.QuicMaxInboundStreams); bW.Write(_dnsServer.ListenBacklog); //optional protocols bW.Write(_dnsServer.EnableDnsOverHttp); bW.Write(_dnsServer.EnableDnsOverTls); bW.Write(_dnsServer.EnableDnsOverHttps); bW.Write(_dnsServer.EnableDnsOverQuic); bW.Write(_dnsServer.DnsOverHttpPort); bW.Write(_dnsServer.DnsOverTlsPort); bW.Write(_dnsServer.DnsOverHttpsPort); bW.Write(_dnsServer.DnsOverQuicPort); if (_dnsTlsCertificatePath == null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_dnsTlsCertificatePath); if (_dnsTlsCertificatePassword == null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_dnsTlsCertificatePassword); //tsig if (_dnsServer.TsigKeys is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_dnsServer.TsigKeys.Count)); foreach (KeyValuePair tsigKey in _dnsServer.TsigKeys) { bW.WriteShortString(tsigKey.Key); bW.WriteShortString(tsigKey.Value.SharedSecret); bW.Write((byte)tsigKey.Value.Algorithm); } } //recursion bW.Write((byte)_dnsServer.Recursion); if (_dnsServer.RecursionDeniedNetworks is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_dnsServer.RecursionDeniedNetworks.Count)); foreach (NetworkAddress networkAddress in _dnsServer.RecursionDeniedNetworks) networkAddress.WriteTo(bW); } if (_dnsServer.RecursionAllowedNetworks is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_dnsServer.RecursionAllowedNetworks.Count)); foreach (NetworkAddress networkAddress in _dnsServer.RecursionAllowedNetworks) networkAddress.WriteTo(bW); } bW.Write(_dnsServer.RandomizeName); bW.Write(_dnsServer.QnameMinimization); bW.Write(_dnsServer.NsRevalidation); bW.Write(_dnsServer.ResolverRetries); bW.Write(_dnsServer.ResolverTimeout); bW.Write(_dnsServer.ResolverMaxStackCount); //cache bW.Write(_saveCache); bW.Write(_dnsServer.ServeStale); bW.Write(_dnsServer.CacheZoneManager.ServeStaleTtl); bW.Write(_dnsServer.CacheZoneManager.MaximumEntries); bW.Write(_dnsServer.CacheZoneManager.MinimumRecordTtl); bW.Write(_dnsServer.CacheZoneManager.MaximumRecordTtl); bW.Write(_dnsServer.CacheZoneManager.NegativeRecordTtl); bW.Write(_dnsServer.CacheZoneManager.FailureRecordTtl); bW.Write(_dnsServer.CachePrefetchEligibility); bW.Write(_dnsServer.CachePrefetchTrigger); bW.Write(_dnsServer.CachePrefetchSampleIntervalInMinutes); bW.Write(_dnsServer.CachePrefetchSampleEligibilityHitsPerHour); //blocking bW.Write(_dnsServer.EnableBlocking); bW.Write(_dnsServer.AllowTxtBlockingReport); bW.Write((byte)_dnsServer.BlockingType); { bW.Write(Convert.ToByte(_dnsServer.CustomBlockingARecords.Count + _dnsServer.CustomBlockingAAAARecords.Count)); foreach (DnsARecordData record in _dnsServer.CustomBlockingARecords) record.Address.WriteTo(bW); foreach (DnsAAAARecordData record in _dnsServer.CustomBlockingAAAARecords) record.Address.WriteTo(bW); } { bW.Write(Convert.ToByte(_dnsServer.BlockListZoneManager.AllowListUrls.Count + _dnsServer.BlockListZoneManager.BlockListUrls.Count)); foreach (Uri allowListUrl in _dnsServer.BlockListZoneManager.AllowListUrls) bW.WriteShortString("!" + allowListUrl.AbsoluteUri); foreach (Uri blockListUrl in _dnsServer.BlockListZoneManager.BlockListUrls) bW.WriteShortString(blockListUrl.AbsoluteUri); bW.Write(_settingsApi.BlockListUpdateIntervalHours); bW.Write(_settingsApi.BlockListLastUpdatedOn); } //proxy & forwarders if (_dnsServer.Proxy == null) { bW.Write((byte)NetProxyType.None); } else { bW.Write((byte)_dnsServer.Proxy.Type); bW.WriteShortString(_dnsServer.Proxy.Address); bW.Write(_dnsServer.Proxy.Port); NetworkCredential credential = _dnsServer.Proxy.Credential; if (credential == null) { bW.Write(false); } else { bW.Write(true); bW.WriteShortString(credential.UserName); bW.WriteShortString(credential.Password); } //bypass list { bW.Write(Convert.ToByte(_dnsServer.Proxy.BypassList.Count)); foreach (NetProxyBypassItem item in _dnsServer.Proxy.BypassList) bW.WriteShortString(item.Value); } } if (_dnsServer.Forwarders == null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_dnsServer.Forwarders.Count)); foreach (NameServerAddress forwarder in _dnsServer.Forwarders) forwarder.WriteTo(bW); } bW.Write(_dnsServer.ForwarderRetries); bW.Write(_dnsServer.ForwarderTimeout); bW.Write(_dnsServer.ForwarderConcurrency); //logging bW.Write(_dnsServer.QueryLogManager is not null); //log all queries bW.Write(_dnsServer.StatsManager.MaxStatFileDays); } } #endregion #region public public async Task StartAsync() { if (_disposed) throw new ObjectDisposedException(nameof(DnsWebService)); try { //get initial server domain string dnsServerDomain = Environment.MachineName.ToLower(); if (!DnsClient.IsDomainNameValid(dnsServerDomain)) dnsServerDomain = "dns-server-1"; //use this name instead since machine name is not a valid domain name //init dns server _dnsServer = new DnsServer(dnsServerDomain, _configFolder, Path.Combine(_appFolder, "dohwww"), _log); //init dhcp server _dhcpServer = new DhcpServer(Path.Combine(_configFolder, "scopes"), _log); _dhcpServer.DnsServer = _dnsServer; _dhcpServer.AuthManager = _authManager; //load auth config _authManager.LoadConfigFile(); //load config LoadConfigFile(); //load all dns applications _dnsServer.DnsApplicationManager.LoadAllApplications(); //load all zones files _dnsServer.AuthZoneManager.LoadAllZoneFiles(); InspectAndFixZonePermissions(); //disable zones from old config format if (_configDisabledZones != null) { foreach (string domain in _configDisabledZones) { AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.GetAuthZoneInfo(domain); if (zoneInfo is not null) { zoneInfo.Disabled = true; _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } } } //load allowed zone and blocked zone _dnsServer.AllowedZoneManager.LoadAllowedZoneFile(); _dnsServer.BlockedZoneManager.LoadBlockedZoneFile(); //load block list zone async if (_dnsServer.BlockListZoneManager.BlockListUrls.Count > 0) { ThreadPool.QueueUserWorkItem(delegate (object state) { try { _dnsServer.BlockListZoneManager.LoadBlockLists(); } catch (Exception ex) { _log.Write(ex); } }); } if (_settingsApi.BlockListUpdateIntervalHours > 0) _settingsApi.StartBlockListUpdateTimer(); //load dns cache async if (_saveCache) { ThreadPool.QueueUserWorkItem(delegate (object state) { try { _dnsServer.CacheZoneManager.LoadCacheZoneFile(); } catch (Exception ex) { _log.Write(ex); } }); } //start web service await TryStartWebServiceAsync(); //start dns and dhcp await _dnsServer.StartAsync(); _dhcpServer.Start(); _log.Write("DNS Server (v" + _currentVersion.ToString() + ") was started successfully."); } catch (Exception ex) { _log.Write("Failed to start DNS Server (v" + _currentVersion.ToString() + ")\r\n" + ex.ToString()); throw; } } public async Task StopAsync() { if (_disposed || (_dnsServer is null)) return; try { //stop dns if (_dnsServer is not null) await _dnsServer.DisposeAsync(); //stop dhcp if (_dhcpServer is not null) _dhcpServer.Dispose(); //stop web service if (_settingsApi is not null) { _settingsApi.StopBlockListUpdateTimer(); _settingsApi.StopTemporaryDisableBlockingTimer(); } StopTlsCertificateUpdateTimer(); await StopWebServiceAsync(); if (_saveCache) { try { _dnsServer.CacheZoneManager.SaveCacheZoneFile(); } catch (Exception ex) { _log.Write(ex); } } _log?.Write("DNS Server (v" + _currentVersion.ToString() + ") was stopped successfully."); _dnsServer = null; } catch (Exception ex) { _log?.Write("Failed to stop DNS Server (v" + _currentVersion.ToString() + ")\r\n" + ex.ToString()); throw; } } public void Start() { StartAsync().Sync(); } public void Stop() { StopAsync().Sync(); } #endregion #region properties internal DnsServer DnsServer { get { return _dnsServer; } } internal DhcpServer DhcpServer { get { return _dhcpServer; } } public string ConfigFolder { get { return _configFolder; } } public int WebServiceHttpPort { get { return _webServiceHttpPort; } } public int WebServiceTlsPort { get { return _webServiceTlsPort; } } #endregion } }