diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs index 8df05e8a07..ccfb7aaabe 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs +++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs @@ -1,4 +1,4 @@ - //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // @@ -1898,34 +1898,7 @@ public static string CloseGroup { return ResourceManager.GetString("CloseGroup", resourceCulture); } } - - /// - /// Looks up a localized string similar to Start group. - /// - public static string StartGroup { - get { - return ResourceManager.GetString("StartGroup", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Group actions. - /// - public static string GroupActions { - get { - return ResourceManager.GetString("GroupActions", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pause group. - /// - public static string PauseGroup { - get { - return ResourceManager.GetString("PauseGroup", resourceCulture); - } - } - + /// /// Looks up a localized string similar to Closing in {0} seconds.... /// @@ -2485,59 +2458,50 @@ public static string Crimson { } /// - /// Looks up a localized string similar to Ctrl+Alt+Del. - /// - public static string CtrlAltDel { - get { - return ResourceManager.GetString("CtrlAltDel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Task Manager (Ctrl+Shift+Esc). + /// Looks up a localized string similar to Imported from CSV file on {0}. /// - public static string TaskManager { + public static string Csv_ImportDescription { get { - return ResourceManager.GetString("TaskManager", resourceCulture); + return ResourceManager.GetString("Csv_ImportDescription", resourceCulture); } } - + /// - /// Looks up a localized string similar to Lock (Win+L). + /// Looks up a localized string similar to Expected CSV format — one profile per line:. /// - public static string Lock { + public static string CsvImportFormatHint { get { - return ResourceManager.GetString("Lock", resourceCulture); + return ResourceManager.GetString("CsvImportFormatHint", resourceCulture); } } - + /// - /// Looks up a localized string similar to Show Desktop (Win+D). + /// Looks up a localized string similar to A header row and the delimiter (semicolon, comma or tab) are detected automatically. The description column is optional. Entries without a host cannot be imported.. /// - public static string ShowDesktop { + public static string CsvImportFormatNote { get { - return ResourceManager.GetString("ShowDesktop", resourceCulture); + return ResourceManager.GetString("CsvImportFormatNote", resourceCulture); } } - + /// - /// Looks up a localized string similar to Explorer (Win+E). + /// Looks up a localized string similar to No entries were found in the CSV file.. /// - public static string Explorer { + public static string CsvNoEntriesFound { get { - return ResourceManager.GetString("Explorer", resourceCulture); + return ResourceManager.GetString("CsvNoEntriesFound", resourceCulture); } } - + /// - /// Looks up a localized string similar to Run dialog (Win+R). + /// Looks up a localized string similar to Ctrl+Alt+Del. /// - public static string RunDialog { + public static string CtrlAltDel { get { - return ResourceManager.GetString("RunDialog", resourceCulture); + return ResourceManager.GetString("CtrlAltDel", resourceCulture); } } - + /// /// Looks up a localized string similar to Currency. /// @@ -3045,43 +3009,7 @@ public static string Description { return ResourceManager.GetString("Description", resourceCulture); } } - - /// - /// Looks up a localized string similar to Placeholder. - /// - public static string Placeholder { - get { - return ResourceManager.GetString("Placeholder", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Path to the file or executable to run. The following placeholders can be used:. - /// - public static string HelpMessage_CustomCommandFilePath { - get { - return ResourceManager.GetString("HelpMessage_CustomCommandFilePath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Arguments passed to the file or executable. The following placeholders can be used:. - /// - public static string HelpMessage_CustomCommandArguments { - get { - return ResourceManager.GetString("HelpMessage_CustomCommandArguments", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The following placeholder can be used:. - /// - public static string HelpMessage_ProfileHostPlaceholder { - get { - return ResourceManager.GetString("HelpMessage_ProfileHostPlaceholder", resourceCulture); - } - } - + /// /// Looks up a localized string similar to Deselect all. /// @@ -3459,7 +3387,7 @@ public static string DontFragment { return ResourceManager.GetString("DontFragment", resourceCulture); } } - + /// /// Looks up a localized string similar to Down. /// @@ -3468,7 +3396,7 @@ public static string Down { return ResourceManager.GetString("Down", resourceCulture); } } - + /// /// Looks up a localized string similar to Download. /// @@ -4336,7 +4264,7 @@ public static string ExpandHostView { return ResourceManager.GetString("ExpandHostView", resourceCulture); } } - + /// /// Looks up a localized string similar to Expand map view. /// @@ -4345,7 +4273,7 @@ public static string ExpandMapView { return ResourceManager.GetString("ExpandMapView", resourceCulture); } } - + /// /// Looks up a localized string similar to Experience. /// @@ -4373,6 +4301,15 @@ public static string ExperimentalFeatures { } } + /// + /// Looks up a localized string similar to Explorer (Win+E). + /// + public static string Explorer { + get { + return ResourceManager.GetString("Explorer", resourceCulture); + } + } + /// /// Looks up a localized string similar to Export. /// @@ -4813,7 +4750,7 @@ public static string FlushDNSCache { return ResourceManager.GetString("FlushDNSCache", resourceCulture); } } - + /// /// Looks up a localized string similar to DNS cache flushed. /// @@ -5012,6 +4949,15 @@ public static string Group { } } + /// + /// Looks up a localized string similar to Group actions. + /// + public static string GroupActions { + get { + return ResourceManager.GetString("GroupActions", resourceCulture); + } + } + /// /// Looks up a localized string similar to Group / domain. /// @@ -5145,6 +5091,24 @@ public static string HelpMessage_Credentials { } } + /// + /// Looks up a localized string similar to Arguments passed to the file or executable. The following placeholders can be used:. + /// + public static string HelpMessage_CustomCommandArguments { + get { + return ResourceManager.GetString("HelpMessage_CustomCommandArguments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Path to the file or executable to run. The following placeholders can be used:. + /// + public static string HelpMessage_CustomCommandFilePath { + get { + return ResourceManager.GetString("HelpMessage_CustomCommandFilePath", resourceCulture); + } + } + /// /// Looks up a localized string similar to URL to a web service that can be reached via http or https and returns an IPv4 address e.g., "xx.xx.xx.xx" as response.. /// @@ -5235,6 +5199,15 @@ public static string HelpMessage_PasswordNotDisplayedCanBeOverwritten { } } + /// + /// Looks up a localized string similar to The following placeholder can be used:. + /// + public static string HelpMessage_ProfileHostPlaceholder { + get { + return ResourceManager.GetString("HelpMessage_ProfileHostPlaceholder", resourceCulture); + } + } + /// /// Looks up a localized string similar to Public IPv4 address reachable via ICMP.. /// @@ -5634,24 +5607,6 @@ public static string ImportProfiles_Method_ActiveDirectory { } } - /// - /// Looks up a localized string similar to Active Directory. - /// - public static string ImportProfiles_Source_ActiveDirectory { - get { - return ResourceManager.GetString("ImportProfiles_Source_ActiveDirectory", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CSV file. - /// - public static string ImportProfiles_Source_Csv { - get { - return ResourceManager.GetString("ImportProfiles_Source_Csv", resourceCulture); - } - } - /// /// Looks up a localized string similar to CSV file. /// @@ -5660,52 +5615,25 @@ public static string ImportProfiles_Method_Csv { return ResourceManager.GetString("ImportProfiles_Method_Csv", resourceCulture); } } - - /// - /// Looks up a localized string similar to Import profiles from CSV file. - /// - public static string ImportProfilesFromCsvFile { - get { - return ResourceManager.GetString("ImportProfilesFromCsvFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Imported from CSV file on {0}. - /// - public static string Csv_ImportDescription { - get { - return ResourceManager.GetString("Csv_ImportDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No entries were found in the CSV file.. - /// - public static string CsvNoEntriesFound { - get { - return ResourceManager.GetString("CsvNoEntriesFound", resourceCulture); - } - } - + /// - /// Looks up a localized string similar to Expected CSV format — one profile per line:. + /// Looks up a localized string similar to Active Directory. /// - public static string CsvImportFormatHint { + public static string ImportProfiles_Source_ActiveDirectory { get { - return ResourceManager.GetString("CsvImportFormatHint", resourceCulture); + return ResourceManager.GetString("ImportProfiles_Source_ActiveDirectory", resourceCulture); } } - + /// - /// Looks up a localized string similar to A header row and the delimiter (semicolon, comma or tab) are detected automatically. The description column is optional. Entries without a host cannot be imported.. + /// Looks up a localized string similar to CSV file. /// - public static string CsvImportFormatNote { + public static string ImportProfiles_Source_Csv { get { - return ResourceManager.GetString("CsvImportFormatNote", resourceCulture); + return ResourceManager.GetString("ImportProfiles_Source_Csv", resourceCulture); } } - + /// /// Looks up a localized string similar to Imported. /// @@ -5742,6 +5670,15 @@ public static string ImportProfilesDots { } } + /// + /// Looks up a localized string similar to Import profiles from CSV file. + /// + public static string ImportProfilesFromCsvFile { + get { + return ResourceManager.GetString("ImportProfilesFromCsvFile", resourceCulture); + } + } + /// /// Looks up a localized string similar to Import results. /// @@ -6012,7 +5949,7 @@ public static string IPv4Address { return ResourceManager.GetString("IPv4Address", resourceCulture); } } - + /// /// Looks up a localized string similar to IPv4 address added. /// @@ -6021,7 +5958,7 @@ public static string IPv4AddressAddedSuccessfully { return ResourceManager.GetString("IPv4AddressAddedSuccessfully", resourceCulture); } } - + /// /// Looks up a localized string similar to IPv4 address released and renewed. /// @@ -6030,7 +5967,7 @@ public static string IPv4AddressReleasedAndRenewedSuccessfully { return ResourceManager.GetString("IPv4AddressReleasedAndRenewedSuccessfully", resourceCulture); } } - + /// /// Looks up a localized string similar to IPv4 address released. /// @@ -6039,49 +5976,22 @@ public static string IPv4AddressReleasedSuccessfully { return ResourceManager.GetString("IPv4AddressReleasedSuccessfully", resourceCulture); } } - - /// - /// Looks up a localized string similar to IPv4 address renewed. - /// - public static string IPv4AddressRenewedSuccessfully { - get { - return ResourceManager.GetString("IPv4AddressRenewedSuccessfully", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to IPv6 address released and renewed. - /// - public static string IPv6AddressReleasedAndRenewedSuccessfully { - get { - return ResourceManager.GetString("IPv6AddressReleasedAndRenewedSuccessfully", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to IPv6 address released. - /// - public static string IPv6AddressReleasedSuccessfully { - get { - return ResourceManager.GetString("IPv6AddressReleasedSuccessfully", resourceCulture); - } - } - + /// - /// Looks up a localized string similar to IPv6 address renewed. + /// Looks up a localized string similar to IPv4 address removed. /// - public static string IPv6AddressRenewedSuccessfully { + public static string IPv4AddressRemovedSuccessfully { get { - return ResourceManager.GetString("IPv6AddressRenewedSuccessfully", resourceCulture); + return ResourceManager.GetString("IPv4AddressRemovedSuccessfully", resourceCulture); } } - + /// - /// Looks up a localized string similar to IPv4 address removed. + /// Looks up a localized string similar to IPv4 address renewed. /// - public static string IPv4AddressRemovedSuccessfully { + public static string IPv4AddressRenewedSuccessfully { get { - return ResourceManager.GetString("IPv4AddressRemovedSuccessfully", resourceCulture); + return ResourceManager.GetString("IPv4AddressRenewedSuccessfully", resourceCulture); } } @@ -6130,6 +6040,33 @@ public static string IPv6AddressLinkLocal { } } + /// + /// Looks up a localized string similar to IPv6 address released and renewed. + /// + public static string IPv6AddressReleasedAndRenewedSuccessfully { + get { + return ResourceManager.GetString("IPv6AddressReleasedAndRenewedSuccessfully", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to IPv6 address released. + /// + public static string IPv6AddressReleasedSuccessfully { + get { + return ResourceManager.GetString("IPv6AddressReleasedSuccessfully", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to IPv6 address renewed. + /// + public static string IPv6AddressRenewedSuccessfully { + get { + return ResourceManager.GetString("IPv6AddressRenewedSuccessfully", resourceCulture); + } + } + /// /// Looks up a localized string similar to IPv6-Default-Gateway. /// @@ -6688,6 +6625,15 @@ public static string LocationOfTheImport { } } + /// + /// Looks up a localized string similar to Lock (Win+L). + /// + public static string Lock { + get { + return ResourceManager.GetString("Lock", resourceCulture); + } + } + /// /// Looks up a localized string similar to Log. /// @@ -6805,6 +6751,15 @@ public static string Management { } } + /// + /// Looks up a localized string similar to Map. + /// + public static string Map { + get { + return ResourceManager.GetString("Map", resourceCulture); + } + } + /// /// Looks up a localized string similar to Master Password. /// @@ -6840,34 +6795,7 @@ public static string MaxHostThreads { return ResourceManager.GetString("MaxHostThreads", resourceCulture); } } - - /// - /// Looks up a localized string similar to Map. - /// - public static string Map { - get { - return ResourceManager.GetString("Map", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Scroll = Zoom, Drag = Pan. - /// - public static string ScrollToZoomDragToPan { - get { - return ResourceManager.GetString("ScrollToZoomDragToPan", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unknown. - /// - public static string Unknown { - get { - return ResourceManager.GetString("Unknown", resourceCulture); - } - } - + /// /// Looks up a localized string similar to Maximum. /// @@ -6912,6 +6840,7 @@ public static string MaxPortThreads { return ResourceManager.GetString("MaxPortThreads", resourceCulture); } } + /// /// Looks up a localized string similar to Measured time. /// @@ -7298,7 +7227,7 @@ public static string NetworkInterface { return ResourceManager.GetString("NetworkInterface", resourceCulture); } } - + /// /// Looks up a localized string similar to Configuration applied. /// @@ -7307,7 +7236,7 @@ public static string NetworkInterfaceConfigurationAppliedSuccessfully { return ResourceManager.GetString("NetworkInterfaceConfigurationAppliedSuccessfully", resourceCulture); } } - + /// /// Looks up a localized string similar to Configuring the network interface requires elevated rights!. /// @@ -7316,7 +7245,7 @@ public static string NetworkInterfaceConfigureAdminMessage { return ResourceManager.GetString("NetworkInterfaceConfigureAdminMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Network kind. /// @@ -7451,7 +7380,7 @@ public static string NewTab { return ResourceManager.GetString("NewTab", resourceCulture); } } - + /// /// Looks up a localized string similar to Next. /// @@ -7460,7 +7389,7 @@ public static string Next { return ResourceManager.GetString("Next", resourceCulture); } } - + /// /// Looks up a localized string similar to No. /// @@ -7642,6 +7571,15 @@ public static string NotificationSuccessThreshold { } } + /// + /// Looks up a localized string similar to Not set. + /// + public static string NotSet { + get { + return ResourceManager.GetString("NotSet", resourceCulture); + } + } + /// /// Looks up a localized string similar to No update available!. /// @@ -7823,23 +7761,23 @@ public static string On { } /// - /// Looks up a localized string similar to Only numbers can be entered!. + /// Looks up a localized string similar to Only CSV files are allowed!. /// - public static string OnlyNumbersCanBeEntered { + public static string OnlyCsvFilesAllowed { get { - return ResourceManager.GetString("OnlyNumbersCanBeEntered", resourceCulture); + return ResourceManager.GetString("OnlyCsvFilesAllowed", resourceCulture); } } /// - /// Looks up a localized string similar to Only CSV files are allowed!. + /// Looks up a localized string similar to Only numbers can be entered!. /// - public static string OnlyCsvFilesAllowed { + public static string OnlyNumbersCanBeEntered { get { - return ResourceManager.GetString("OnlyCsvFilesAllowed", resourceCulture); + return ResourceManager.GetString("OnlyNumbersCanBeEntered", resourceCulture); } } - + /// /// Looks up a localized string similar to Only when using the full screen. /// @@ -8065,6 +8003,15 @@ public static string Parameter { } } + /// + /// Looks up a localized string similar to Parse. + /// + public static string Parse { + get { + return ResourceManager.GetString("Parse", resourceCulture); + } + } + /// /// Looks up a localized string similar to Password. /// @@ -8083,15 +8030,6 @@ public static string PasswordsDoNotMatch { } } - /// - /// Looks up a localized string similar to Parse. - /// - public static string Parse { - get { - return ResourceManager.GetString("Parse", resourceCulture); - } - } - /// /// Looks up a localized string similar to Paste. /// @@ -8127,7 +8065,7 @@ public static string Pause { return ResourceManager.GetString("Pause", resourceCulture); } } - + /// /// Looks up a localized string similar to Paused. /// @@ -8136,7 +8074,16 @@ public static string Paused { return ResourceManager.GetString("Paused", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Pause group. + /// + public static string PauseGroup { + get { + return ResourceManager.GetString("PauseGroup", resourceCulture); + } + } + /// /// Looks up a localized string similar to Performance. /// @@ -8245,6 +8192,15 @@ public static string Pink { } } + /// + /// Looks up a localized string similar to Placeholder. + /// + public static string Placeholder { + get { + return ResourceManager.GetString("Placeholder", resourceCulture); + } + } + /// /// Looks up a localized string similar to Play sound on status change. /// @@ -8253,7 +8209,7 @@ public static string PlaySoundOnStatusChange { return ResourceManager.GetString("PlaySoundOnStatusChange", resourceCulture); } } - + /// /// Looks up a localized string similar to (+{0} more). /// @@ -8262,7 +8218,7 @@ public static string PlusXMore { return ResourceManager.GetString("PlusXMore", resourceCulture); } } - + /// /// Looks up a localized string similar to Port. /// @@ -10172,7 +10128,7 @@ public static string Resource_ListTLD_Description { return ResourceManager.GetString("Resource_ListTLD_Description", resourceCulture); } } - + /// /// Looks up a localized string similar to GeoJSON conversion of Natural Earth vector map data (CC0-1.0).. /// @@ -10181,7 +10137,7 @@ public static string Resource_NaturalEarth_Description { return ResourceManager.GetString("Resource_NaturalEarth_Description", resourceCulture); } } - + /// /// Looks up a localized string similar to OUI data from ieee.org.. /// @@ -10447,6 +10403,15 @@ public static string RunCommandDotsWithHotKey { } } + /// + /// Looks up a localized string similar to Run dialog (Win+R). + /// + public static string RunDialog { + get { + return ResourceManager.GetString("RunDialog", resourceCulture); + } + } + /// /// Looks up a localized string similar to Run speed test. /// @@ -10519,6 +10484,15 @@ public static string ScanPorts { } } + /// + /// Looks up a localized string similar to Scroll = Zoom, Drag = Pan. + /// + public static string ScrollToZoomDragToPan { + get { + return ResourceManager.GetString("ScrollToZoomDragToPan", resourceCulture); + } + } + /// /// Looks up a localized string similar to Search. /// @@ -10973,6 +10947,15 @@ public static string ShowCurrentApplicationTitle { } } + /// + /// Looks up a localized string similar to Show Desktop (Win+D). + /// + public static string ShowDesktop { + get { + return ResourceManager.GetString("ShowDesktop", resourceCulture); + } + } + /// /// Looks up a localized string similar to Show error message. /// @@ -10990,7 +10973,7 @@ public static string ShowLocalLicenses { return ResourceManager.GetString("ShowLocalLicenses", resourceCulture); } } - + /// /// Looks up a localized string similar to Show map. /// @@ -10999,7 +10982,7 @@ public static string ShowMap { return ResourceManager.GetString("ShowMap", resourceCulture); } } - + /// /// Looks up a localized string similar to Show notification popup on status change. /// @@ -11469,6 +11452,15 @@ public static string StarForkProjectOnGitHub { } } + /// + /// Looks up a localized string similar to Start group. + /// + public static string StartGroup { + get { + return ResourceManager.GetString("StartGroup", resourceCulture); + } + } + /// /// Looks up a localized string similar to Start minimized in tray. /// @@ -11676,6 +11668,15 @@ public static string Tags { } } + /// + /// Looks up a localized string similar to Task Manager (Ctrl+Shift+Esc). + /// + public static string TaskManager { + get { + return ResourceManager.GetString("TaskManager", resourceCulture); + } + } + /// /// Looks up a localized string similar to Taupe. /// @@ -12234,6 +12235,15 @@ public static string Unit { } } + /// + /// Looks up a localized string similar to Unknown. + /// + public static string Unknown { + get { + return ResourceManager.GetString("Unknown", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unkown error!. /// @@ -12278,7 +12288,7 @@ public static string UntrayBringWindowToForeground { return ResourceManager.GetString("UntrayBringWindowToForeground", resourceCulture); } } - + /// /// Looks up a localized string similar to Up. /// @@ -12287,7 +12297,7 @@ public static string Up { return ResourceManager.GetString("Up", resourceCulture); } } - + /// /// Looks up a localized string similar to Update. /// diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx index 5533818793..bdf839e1c5 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.resx +++ b/Source/NETworkManager.Localization/Resources/Strings.resx @@ -4496,4 +4496,7 @@ Cloudflare may log your IP address and network information. See Cloudflare's pri Stop + + Not set + \ No newline at end of file diff --git a/Source/NETworkManager.Models/Network/DNSLookup.cs b/Source/NETworkManager.Models/Network/DNSLookup.cs index da84a900d1..4c0ed2cc17 100644 --- a/Source/NETworkManager.Models/Network/DNSLookup.cs +++ b/Source/NETworkManager.Models/Network/DNSLookup.cs @@ -1,10 +1,10 @@ using DnsClient; using DnsClient.Protocol; +using NETworkManager.Utilities; using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Net.NetworkInformation; using System.Threading.Tasks; namespace NETworkManager.Models.Network; @@ -24,7 +24,7 @@ public DNSLookup(DNSLookupSettings settings, IEnumerable d { _suffix = _settings.UseCustomDNSSuffix ? _settings.CustomDNSSuffix - : IPGlobalProperties.GetIPGlobalProperties().DomainName; + : DNSClientHelper.DetectDNSSuffix(); _addSuffix = !string.IsNullOrEmpty(_suffix); } @@ -108,25 +108,28 @@ private void OnLookupComplete() /// List of DNS servers as . private IEnumerable GetDnsServer(IEnumerable dnsServers = null) { - List servers = []; + // Use Windows dns servers + List servers = + [ + .. dnsServers == null + ? NameServer.ResolveNameServers(true, false).Select(dnsServer => + new IPEndPoint(IPAddress.Parse(dnsServer.Address), dnsServer.Port)) + : dnsServers.Select(dnsServer => new IPEndPoint(IPAddress.Parse(dnsServer.Server), dnsServer.Port)) - // Use windows dns servers - servers.AddRange(dnsServers == null - ? NameServer.ResolveNameServers(true, false).Select(dnsServer => - new IPEndPoint(IPAddress.Parse(dnsServer.Address), dnsServer.Port)) - : dnsServers.Select(dnsServer => new IPEndPoint(IPAddress.Parse(dnsServer.Server), dnsServer.Port))); + ]; return servers; } /// - /// Append DNS suffix to hostname if not set. + /// Append DNS suffix to hostname if not set. IP literals are left unchanged (the Host input + /// accepts hostnames and IP addresses alike, for any query type). /// /// List of hosts /// List of host with DNS suffix - private IEnumerable GetHostWithSuffix(IEnumerable hosts) + private IEnumerable GetHostsWithSuffix(IEnumerable hosts) { - return hosts.Select(host => host.Contains('.') ? host : $"{host}.{_suffix}").ToList(); + return [.. hosts.Select(host => DNSClientHelper.IsBareHostname(host) ? $"{host}.{_suffix}" : host)]; } /// @@ -138,7 +141,7 @@ public void ResolveAsync(IEnumerable hosts) Task.Run(() => { // Append dns suffix to hostname, if the option is set, otherwise copy the list - var queries = _addSuffix && _settings.QueryType != QueryType.PTR ? GetHostWithSuffix(hosts) : hosts; + var queries = _addSuffix && _settings.QueryType != QueryType.PTR ? GetHostsWithSuffix(hosts) : hosts; // For each dns server Parallel.ForEach(_servers, dnsServer => diff --git a/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs b/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs index 0a402771a6..8f40f3e13a 100644 --- a/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs +++ b/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs @@ -166,11 +166,14 @@ public static ServerConnectionInfo Parse(string input, int defaultPort, Transpor } /// - /// Returns a string that represents the current object. + /// Returns a string that represents the current object. IPv6 addresses are bracketed + /// ([::1]:53) so the address and port remain distinguishable. /// - /// Server:Port + /// Server:Port, or [Server]:Port for an IPv6 address. public override string ToString() { - return $"{Server}:{Port}"; + return IPAddress.TryParse(Server, out var ip) && ip.AddressFamily == AddressFamily.InterNetworkV6 + ? $"[{Server}]:{Port}" + : $"{Server}:{Port}"; } } \ No newline at end of file diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs index c7b9c5adaf..24637a072d 100644 --- a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs +++ b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs @@ -30,6 +30,9 @@ public static class GlobalStaticConfiguration // Network config public static int NetworkChangeDetectionDelay => 5000; + // Delay before applying DNS server settings change. + public static TimeSpan NetworkConfigApplyDispatcherTimerTimeSpan => new(0, 0, 0, 2, 500); + // Notification config // Minimum interval (ms) between two notification sounds, so a burst of near-simultaneous // status changes collapses into a single sound instead of an overlapping cacophony. @@ -72,6 +75,7 @@ public static class GlobalStaticConfiguration public static bool Appearance_UseCustomTheme => false; // Settings: Network + public static bool Network_AddDNSSuffix => true; public static bool Network_ResolveHostnamePreferIPv4 => true; // Settings: Status diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs index ae78719a25..f2cce1b15f 100644 --- a/Source/NETworkManager.Settings/SettingsInfo.cs +++ b/Source/NETworkManager.Settings/SettingsInfo.cs @@ -374,6 +374,8 @@ public bool Network_UseCustomDNSServer } } + [Obsolete("Use Network_CustomDNSServers instead.")] + [field: Obsolete("Use Network_CustomDNSServers instead.")] public string Network_CustomDNSServer { get; @@ -387,6 +389,58 @@ public string Network_CustomDNSServer } } + public ObservableCollection Network_CustomDNSServers + { + get; + set + { + if (value == field) + return; + + field = value; + OnPropertyChanged(); + } + } = []; + + public bool Network_AddDNSSuffix + { + get; + set + { + if (value == field) + return; + + field = value; + OnPropertyChanged(); + } + } = GlobalStaticConfiguration.Network_AddDNSSuffix; + + public bool Network_UseCustomDNSSuffix + { + get; + set + { + if (value == field) + return; + + field = value; + OnPropertyChanged(); + } + } + + public string Network_CustomDNSSuffix + { + get; + set + { + if (value == field) + return; + + field = value; + OnPropertyChanged(); + } + } + public bool Network_ResolveHostnamePreferIPv4 { get; diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs index 5e293044fb..56eebfaa0e 100644 --- a/Source/NETworkManager.Settings/SettingsManager.cs +++ b/Source/NETworkManager.Settings/SettingsManager.cs @@ -3,6 +3,7 @@ using NETworkManager.Models.Network; using NETworkManager.Utilities; using System; +using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text.Json; @@ -638,7 +639,9 @@ private static void UpgradeTo_2026_2_22_0() // DNS Lookup Log.Info("Migrate DNS Lookup settings to new structure..."); +#pragma warning disable CS0618 // Type or member is obsolete Current.DNSLookup_SelectedDNSServer_v2 = Current.DNSLookup_SelectedDNSServer?.Name; +#pragma warning restore CS0618 // Type or member is obsolete Log.Info($"Selected DNS server set to \"{Current.DNSLookup_SelectedDNSServer_v2}\""); @@ -788,6 +791,26 @@ private static void UpgradeToLatest(Version version) Log.Info($"Add \"{portProfile.Name}\" to \"PortScanner_PortProfiles\"..."); Current.PortScanner_PortProfiles.Add(portProfile); } + + // Migrate custom DNS servers from a semicolon-separated string (no port) to a list of ServerConnectionInfo +#pragma warning disable CS0618 + if (!string.IsNullOrEmpty(Current.Network_CustomDNSServer)) + { + Log.Info("Migrate custom DNS servers to new structure..."); + + Current.Network_CustomDNSServers = + [ + .. Current + .Network_CustomDNSServer + .Split(';') + .Select(server => server.Trim()) + .Where(server => server.Length > 0) + .Select(server => new ServerConnectionInfo(server, 53, TransportProtocol.Udp)) + ]; + + Current.Network_CustomDNSServer = string.Empty; // Clear the old property to avoid multiple migrations with pre-release versions + } +#pragma warning restore CS0618 } #endregion } diff --git a/Source/NETworkManager.Utilities/DNSClient.cs b/Source/NETworkManager.Utilities/DNSClient.cs index 91156be90d..a72103face 100644 --- a/Source/NETworkManager.Utilities/DNSClient.cs +++ b/Source/NETworkManager.Utilities/DNSClient.cs @@ -18,9 +18,13 @@ public class DNSClient : SingletonBase private const string NotConfiguredMessage = "DNS client is not configured. Call Configure() first."; /// - /// Hold the current instance of the LookupClient. + /// Immutable snapshot of everything a resolve call needs (lookup client + settings it was configured + /// with), published as a single reference so concurrent resolves during a + /// call never observe a torn combination (e.g. the old suffix flag with the new lookup client). + /// Relies on not being mutated after being passed to + /// - see the note on itself. /// - private LookupClient _client; + private sealed record ResolverState(LookupClient Client, bool AddSuffix, DNSClientSettings Settings); /// /// Indicates if the DNS client is configured. @@ -28,9 +32,9 @@ public class DNSClient : SingletonBase private bool _isConfigured; /// - /// Store the current DNS settings. + /// Current resolver state (lookup client + DNS suffix behavior), swapped atomically on configure. /// - private DNSClientSettings _settings; + private ResolverState _state; /// /// Method to configure the DNS client. @@ -38,46 +42,43 @@ public class DNSClient : SingletonBase /// public void Configure(DNSClientSettings settings) { - _settings = settings; - Log.Debug("Configure - Configuring DNS client..."); - if (_settings.UseCustomDNSServers) + LookupClient client; + + if (settings.UseCustomDNSServers) { Log.Debug("Configure - Using custom DNS servers..."); // Setup custom DNS servers List servers = []; - foreach (var (server, port) in _settings.DNSServers) + foreach (var (server, port) in settings.DNSServers) { Log.Debug($"Configure - Adding custom DNS server: {server}:{port}"); servers.Add(new IPEndPoint(IPAddress.Parse(server), port)); } Log.Debug("Configure - Creating LookupClient with custom DNS servers..."); - _client = new LookupClient(new LookupClientOptions(servers.ToArray())); + client = new LookupClient(new LookupClientOptions([.. servers])); } else { Log.Debug("Configure - Creating LookupClient with Windows default DNS servers..."); - _client = new LookupClient(); + client = new LookupClient(); } + var addSuffix = settings.AddDNSSuffix && !string.IsNullOrEmpty(settings.DNSSuffix); + Log.Debug(addSuffix + ? $"Configure - DNS suffix will be added to hostnames without a dot: {settings.DNSSuffix}" + : "Configure - DNS suffix will NOT be added to hostnames without a dot."); + + _state = new ResolverState(client, addSuffix, settings); + Log.Debug("Configure - DNS client configured."); _isConfigured = true; } - /// - /// Method to update the (Windows) name servers of the DNS client - /// when they may have changed due to a network update. - /// - public void UpdateWindowsDNSSever() - { - Log.Debug("UpdateWindowsDNSSever - Recreating LookupClient with with Windows default DNS servers..."); - _client = new LookupClient(); - } - /// /// Resolve an IPv4 address from a hostname or FQDN. /// @@ -88,9 +89,13 @@ public async Task ResolveAAsync(string query) if (!_isConfigured) throw new DNSClientNotConfiguredException(NotConfiguredMessage); + var state = _state; + + query = AddDNSSuffixIfConfigured(query, state); + try { - var result = await _client.QueryAsync(query, QueryType.A); + var result = await state.Client.QueryAsync(query, QueryType.A); // Pass the error we got from the lookup client (dns server). // NXDOMAIN is not a real failure like a timeout - flag it via IsNotFound so callers can @@ -131,9 +136,13 @@ public async Task ResolveAaaaAsync(string query) if (!_isConfigured) throw new DNSClientNotConfiguredException(NotConfiguredMessage); + var state = _state; + + query = AddDNSSuffixIfConfigured(query, state); + try { - var result = await _client.QueryAsync(query, QueryType.AAAA); + var result = await state.Client.QueryAsync(query, QueryType.AAAA); // Pass the error we got from the lookup client (dns server). // NXDOMAIN is not a real failure like a timeout - flag it via IsNotFound so callers can @@ -176,7 +185,7 @@ public async Task ResolveCnameAsync(string query) try { - var result = await _client.QueryAsync(query, QueryType.CNAME); + var result = await _state.Client.QueryAsync(query, QueryType.CNAME); // Pass the error we got from the lookup client (dns server). // NXDOMAIN is not a real failure like a timeout - flag it via IsNotFound so callers can @@ -219,7 +228,7 @@ public async Task ResolvePtrAsync(IPAddress ipAddress) try { - var result = await _client.QueryReverseAsync(ipAddress); + var result = await _state.Client.QueryReverseAsync(ipAddress); // Pass the error we got from the lookup client (dns server). // NXDOMAIN is always a clean "no record". For private/ULA IP ranges (the common case for @@ -255,6 +264,20 @@ public async Task ResolvePtrAsync(IPAddress ipAddress) } } + /// + /// Appends the configured DNS suffix to a bare hostname (forward lookups only). + /// FQDNs and IP literals are returned unchanged. + /// + /// Hostname, FQDN, or IP address as string like "example.com". + /// Resolver state snapshot captured at the start of the resolve call. + /// Query with the DNS suffix appended, if configured and applicable. + private static string AddDNSSuffixIfConfigured(string query, ResolverState state) + { + return state.AddSuffix && DNSClientHelper.IsBareHostname(query) + ? $"{query}.{state.Settings.DNSSuffix}" + : query; + } + /// /// Determines whether a DNS response code means "no record" rather than a real failure. /// NXDOMAIN is always a clean "does not exist". SERVFAIL and REFUSED are only treated as diff --git a/Source/NETworkManager.Utilities/DNSClientHelper.cs b/Source/NETworkManager.Utilities/DNSClientHelper.cs index 6c420d8fe2..0d1913eba6 100644 --- a/Source/NETworkManager.Utilities/DNSClientHelper.cs +++ b/Source/NETworkManager.Utilities/DNSClientHelper.cs @@ -1,9 +1,85 @@ -using System.Threading.Tasks; +using log4net; +using System; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Threading.Tasks; namespace NETworkManager.Utilities; public static class DNSClientHelper { + private static readonly ILog Log = LogManager.GetLogger(typeof(DNSClientHelper)); + + /// + /// Determines whether a query is a bare hostname that a DNS suffix should be appended to: + /// not empty, has no dot (not already an FQDN), and isn't an IP address literal (e.g. a bare + /// IPv6 address like "2001:db8::1" has no dot and would otherwise be mistaken for a hostname). + /// + public static bool IsBareHostname(string query) + { + return !string.IsNullOrEmpty(query) && !query.Contains('.') && !IPAddress.TryParse(query, out _); + } + + /// + /// Detect the DNS suffix to use for suffix-appending. Prefers the Windows "Primary DNS Suffix" + /// (), falling back to the connection-specific suffix + /// of an active network adapter if no primary suffix is configured (e.g. machine is not domain-joined + /// or the primary suffix was cleared manually), which mirrors what shows up under "DNS Suffix Search + /// List" in ipconfig /all in that case. + /// + public static string DetectDNSSuffix() + { + var suffix = IPGlobalProperties.GetIPGlobalProperties().DomainName; + + if (!string.IsNullOrWhiteSpace(suffix)) + return suffix; + + try + { + // Rank candidates so the adapter most likely to be "the" relevant connection is tried first: + // VPN/tunnel > wired > wireless > other, then routed (has a gateway), then faster link speed. + // VPN ranks above wired/wireless because it's usually why suffix resolution matters in the first + // place (reaching internal/corporate hostnames), and a split-tunnel VPN commonly has no default + // gateway at all, so gateway-presence can't be the primary key without losing to e.g. a home + // Ethernet connection. A candidate is only ever picked if it has a real suffix (see below), so + // ranking a VPN/tunnel first doesn't risk picking up an irrelevant OS-internal pseudo-tunnel + // (Teredo, ISATAP, ...) - those essentially never carry a connection-specific DNS suffix. + var prioritizedAdapters = NetworkInterface.GetAllNetworkInterfaces() + .Where(nic => nic.OperationalStatus == OperationalStatus.Up && + nic.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .Select(nic => (Nic: nic, Properties: nic.GetIPProperties())) + .OrderByDescending(x => GetInterfaceTypePriority(x.Nic.NetworkInterfaceType)) + .ThenByDescending(x => x.Properties.GatewayAddresses.Count > 0) + .ThenByDescending(x => x.Nic.Speed); + + return prioritizedAdapters + .Select(x => x.Properties.DnsSuffix) + .FirstOrDefault(s => !string.IsNullOrWhiteSpace(s)) + ?? string.Empty; + } + catch (Exception ex) + { + // Best-effort fallback - an adapter can disappear (USB NIC unplugged, VPN tunnel torn down) + // while enumerating, throwing from GetIPProperties()/.Speed. Don't let that crash the caller. + Log.Warn("DetectDNSSuffix - Could not enumerate network adapters for DNS suffix detection.", ex); + return string.Empty; + } + } + + private static int GetInterfaceTypePriority(NetworkInterfaceType type) => type switch + { + NetworkInterfaceType.Tunnel => 3, + NetworkInterfaceType.Ppp => 3, + NetworkInterfaceType.Ethernet => 2, + NetworkInterfaceType.FastEthernetT => 2, + NetworkInterfaceType.FastEthernetFx => 2, + NetworkInterfaceType.GigabitEthernet => 2, + NetworkInterfaceType.Ethernet3Megabit => 2, + NetworkInterfaceType.Wireless80211 => 1, + _ => 0 + }; + public static async Task ResolveAorAaaaAsync(string query, bool preferIPv4) { DNSClientResultIPAddress firstResult = null; diff --git a/Source/NETworkManager.Utilities/DNSClientSettings.cs b/Source/NETworkManager.Utilities/DNSClientSettings.cs index d08a8191e3..cec407e7ca 100644 --- a/Source/NETworkManager.Utilities/DNSClientSettings.cs +++ b/Source/NETworkManager.Utilities/DNSClientSettings.cs @@ -4,6 +4,9 @@ namespace NETworkManager.Utilities; /// /// Class is used to store settings for the . +/// retains the instance passed to and reads +/// it from concurrent resolve calls without further synchronization - do not mutate an instance after +/// passing it to ; construct a new one instead. /// public class DNSClientSettings { @@ -23,4 +26,14 @@ public DNSClientSettings() /// List of name servers as Tuple (string Server, int Port). /// public IEnumerable<(string Server, int Port)> DNSServers { get; set; } + + /// + /// Add the DNS suffix to a hostname without a dot before resolving it (A/AAAA queries only). + /// + public bool AddDNSSuffix { get; set; } + + /// + /// DNS suffix to append. Either the custom DNS suffix from the settings or the Windows DNS suffix. + /// + public string DNSSuffix { get; set; } } \ No newline at end of file diff --git a/Source/NETworkManager.Utilities/RegexHelper.cs b/Source/NETworkManager.Utilities/RegexHelper.cs index 8462c2eedf..93a45adaa9 100644 --- a/Source/NETworkManager.Utilities/RegexHelper.cs +++ b/Source/NETworkManager.Utilities/RegexHelper.cs @@ -109,6 +109,14 @@ public static partial class RegexHelper [GeneratedRegex($@"^{HostnameOrDomainValues}$")] public static partial Regex HostnameOrDomainRegex(); + /// + /// Provides a compiled regular expression that matches a DNS suffix, with an optional leading dot, like + /// "example.com" or ".example.com". + /// + /// A instance that matches valid DNS suffixes. + [GeneratedRegex($@"^\.?{HostnameOrDomainValues}$")] + public static partial Regex DNSSuffixRegex(); + /// /// Creates a regular expression that matches a local directory path or one using environment variables, /// like "C:\Temp", "C:\My Settings", "%AppData%\settings". diff --git a/Source/NETworkManager.Validators/DNSSuffixValidator.cs b/Source/NETworkManager.Validators/DNSSuffixValidator.cs new file mode 100644 index 0000000000..1eca31fb68 --- /dev/null +++ b/Source/NETworkManager.Validators/DNSSuffixValidator.cs @@ -0,0 +1,19 @@ +using System.Globalization; +using System.Windows.Controls; +using NETworkManager.Localization.Resources; +using NETworkManager.Utilities; + +namespace NETworkManager.Validators; + +public class DNSSuffixValidator : ValidationRule +{ + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + if (value is not string suffix || string.IsNullOrEmpty(suffix)) + return new ValidationResult(false, Strings.FieldCannotBeEmpty); + + return RegexHelper.DNSSuffixRegex().IsMatch(suffix) + ? ValidationResult.ValidResult + : new ValidationResult(false, Strings.EnterValidDomain); + } +} diff --git a/Source/NETworkManager.Validators/MultipleIPAddressesValidator.cs b/Source/NETworkManager.Validators/MultipleIPAddressesValidator.cs deleted file mode 100644 index ed0d1a3d83..0000000000 --- a/Source/NETworkManager.Validators/MultipleIPAddressesValidator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using NETworkManager.Localization.Resources; -using NETworkManager.Utilities; -using System.Globalization; -using System.Text.RegularExpressions; -using System.Windows.Controls; - -namespace NETworkManager.Validators; - -public class MultipleIPAddressesValidator : ValidationRule -{ - public override ValidationResult Validate(object value, CultureInfo cultureInfo) - { - if (value == null) - return ValidationResult.ValidResult; - - for (var index = 0; index < ((string)value).Split(';').Length; index++) - { - var ipAddress = ((string)value).Split(';')[index].Trim(); - - if (!RegexHelper.IPv4AddressRegex().IsMatch(ipAddress) && - !Regex.IsMatch(ipAddress.Trim(), RegexHelper.IPv6AddressRegex)) - return new ValidationResult(false, Strings.EnterOneOrMoreValidIPAddresses); - } - - return ValidationResult.ValidResult; - } -} diff --git a/Source/NETworkManager/MainWindow.xaml.cs b/Source/NETworkManager/MainWindow.xaml.cs index 264269aec4..5b3385e14c 100644 --- a/Source/NETworkManager/MainWindow.xaml.cs +++ b/Source/NETworkManager/MainWindow.xaml.cs @@ -64,14 +64,32 @@ private void SettingsManager_PropertyChanged(object sender, PropertyChangedEvent break; // Update DNS server if changed in the settings + // Delayed via a timer, so rapid/successive changes (e.g. typing in the custom DNS + // suffix textbox) don't reconfigure the DNS client on every single change. case nameof(SettingsInfo.Network_UseCustomDNSServer): - case nameof(SettingsInfo.Network_CustomDNSServer): - ConfigureDNSServer(); + case nameof(SettingsInfo.Network_CustomDNSServers): + case nameof(SettingsInfo.Network_AddDNSSuffix): + case nameof(SettingsInfo.Network_UseCustomDNSSuffix): + case nameof(SettingsInfo.Network_CustomDNSSuffix): + // Stop + Start (instead of just Start) to reset the due time on every change -- + // DispatcherTimer.Start() is a no-op while it's already running. + _configureDNSServerDispatcherTimer.Stop(); + _configureDNSServerDispatcherTimer.Start(); break; } } + /// + /// Apply pending DNS server settings changes once they stopped changing for a short delay. + /// + private void ConfigureDNSServerDispatcherTimer_Tick(object sender, EventArgs e) + { + _configureDNSServerDispatcherTimer.Stop(); + + ConfigureDNSServer(); + } + #endregion #region Bugfixes @@ -101,6 +119,12 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null) private NotifyIcon _notifyIcon; private StatusWindow _statusWindow; + /// + /// Timer to delay while the related settings are still changing + /// (e.g. while typing in the custom DNS suffix textbox), so it's only applied once. + /// + private readonly DispatcherTimer _configureDNSServerDispatcherTimer = new(); + private readonly bool _isLoading; private bool _isProfileFilesLoading; private bool _isProfileFileUpdating; @@ -404,6 +428,10 @@ public MainWindow() // Load and change appearance AppearanceManager.Load(); + // Delay applying DNS server settings change + _configureDNSServerDispatcherTimer.Interval = GlobalStaticConfiguration.NetworkConfigApplyDispatcherTimerTimeSpan; + _configureDNSServerDispatcherTimer.Tick += ConfigureDNSServerDispatcherTimer_Tick; + // Load and configure DNS server ConfigureDNSServer(); @@ -1866,21 +1894,22 @@ private void ConfigureDNSServer() if (SettingsManager.Current.Network_UseCustomDNSServer) { - if (!string.IsNullOrEmpty(SettingsManager.Current.Network_CustomDNSServer)) + if (SettingsManager.Current.Network_CustomDNSServers.Count > 0) { - Log.Info($"Use custom DNS servers ({SettingsManager.Current.Network_CustomDNSServer})..."); - - List<(string Server, int Port)> dnsServers = SettingsManager.Current.Network_CustomDNSServer.Split(";") - .Select(dnsServer => (dnsServer, 53)) - .ToList(); + Log.Info( + $"Use custom DNS servers ({string.Join("; ", SettingsManager.Current.Network_CustomDNSServers)})..."); dnsSettings.UseCustomDNSServers = true; - dnsSettings.DNSServers = dnsServers; + dnsSettings.DNSServers = + [ + .. SettingsManager.Current.Network_CustomDNSServers + .Select(dnsServer => (dnsServer.Server, dnsServer.Port)) + ]; } else { Log.Info( - $"Custom DNS servers could not be set (Setting \"{nameof(SettingsManager.Current.Network_CustomDNSServer)}\" has value \"{SettingsManager.Current.Network_CustomDNSServer}\")! Fallback to Windows default DNS servers..."); + $"Custom DNS servers could not be set (Setting \"{nameof(SettingsManager.Current.Network_CustomDNSServers)}\" is empty)! Fallback to Windows default DNS servers..."); } } else @@ -1888,6 +1917,14 @@ private void ConfigureDNSServer() Log.Info("Use Windows default DNS servers..."); } + if (SettingsManager.Current.Network_AddDNSSuffix) + { + dnsSettings.AddDNSSuffix = true; + dnsSettings.DNSSuffix = SettingsManager.Current.Network_UseCustomDNSSuffix + ? SettingsManager.Current.Network_CustomDNSSuffix?.TrimStart('.') + : DNSClientHelper.DetectDNSSuffix(); + } + DNSClient.GetInstance().Configure(dnsSettings); } @@ -1912,12 +1949,10 @@ private async void OnNetworkHasChanged() Log.Info("Network availability or address has changed!"); - // Update DNS server if network changed - if (!SettingsManager.Current.Network_UseCustomDNSServer) - { - Log.Info("Update Windows default DNS servers..."); - DNSClient.GetInstance().UpdateWindowsDNSSever(); - } + // Reconfigure the DNS client if network changed - re-detects the Windows default DNS servers + // and, if enabled, the auto-detected DNS suffix (both of which can differ on the new network). + Log.Info("Update DNS server configuration..."); + ConfigureDNSServer(); // Show status window on network change if (SettingsManager.Current.Status_ShowWindowOnNetworkChange) diff --git a/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs b/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs index b0def83ee4..b654fa55a3 100644 --- a/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs +++ b/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs @@ -16,13 +16,14 @@ public class ServerConnectionInfoProfileViewModel : ViewModelBase public ServerConnectionInfoProfileViewModel(Action saveCommand, Action cancelHandler, (List UsedNames, bool IsEdited, bool allowOnlyIPAddress) options, ServerConnectionInfo defaultValues, - ServerConnectionInfoProfile info = null) + ServerConnectionInfoProfile info = null, bool isNameReadOnly = false) { SaveCommand = new RelayCommand(_ => saveCommand(this)); CancelCommand = new RelayCommand(_ => cancelHandler(this)); UsedNames = options.UsedNames; AllowOnlyIPAddress = options.allowOnlyIPAddress; + IsNameReadOnly = isNameReadOnly; _defaultValues = defaultValues; @@ -100,6 +101,19 @@ public bool AllowOnlyIPAddress } } + public bool IsNameReadOnly + { + get; + set + { + if (value == field) + return; + + field = value; + OnPropertyChanged(); + } + } + #endregion public string Name diff --git a/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs b/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs index 3ea8b6ce19..304511bfa7 100644 --- a/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs @@ -1,4 +1,12 @@ +using MahApps.Metro.SimpleChildWindow; +using NETworkManager.Localization.Resources; +using NETworkManager.Models.Network; using NETworkManager.Settings; +using NETworkManager.Utilities; +using NETworkManager.Views; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; namespace NETworkManager.ViewModels; @@ -8,6 +16,11 @@ public class SettingsNetworkViewModel : ViewModelBase private readonly bool _isLoading; + /// + /// Default values for the DNS server profile dialog. + /// + private readonly ServerConnectionInfo _profileDialogDefaultValues = new("1.1.1.1", 53, TransportProtocol.Udp); + public bool UseCustomDNSServer { get; @@ -24,8 +37,52 @@ public bool UseCustomDNSServer } } + public string CustomDNSServersDisplay + { + get; + private set + { + if (value == field) + return; + + field = value; + OnPropertyChanged(); + } + } + + public bool AddDNSSuffix + { + get; + set + { + if (value == field) + return; + + if (!_isLoading) + SettingsManager.Current.Network_AddDNSSuffix = value; + + field = value; + OnPropertyChanged(); + } + } + + public bool UseCustomDNSSuffix + { + get; + set + { + if (value == field) + return; + + if (!_isLoading) + SettingsManager.Current.Network_UseCustomDNSSuffix = value; - public string CustomDNSServer + field = value; + OnPropertyChanged(); + } + } + + public string CustomDNSSuffix { get; set @@ -34,7 +91,7 @@ public string CustomDNSServer return; if (!_isLoading) - SettingsManager.Current.Network_CustomDNSServer = value.Replace(" ", ""); + SettingsManager.Current.Network_CustomDNSSuffix = value; field = value; OnPropertyChanged(); @@ -87,8 +144,11 @@ private void LoadSettings() { UseCustomDNSServer = SettingsManager.Current.Network_UseCustomDNSServer; - if (SettingsManager.Current.Network_CustomDNSServer != null) - CustomDNSServer = string.Join("; ", SettingsManager.Current.Network_CustomDNSServer); + RefreshCustomDNSServersDisplay(); + + AddDNSSuffix = SettingsManager.Current.Network_AddDNSSuffix; + UseCustomDNSSuffix = SettingsManager.Current.Network_UseCustomDNSSuffix; + CustomDNSSuffix = SettingsManager.Current.Network_CustomDNSSuffix; if (SettingsManager.Current.Network_ResolveHostnamePreferIPv4) ResolveHostnamePreferIPv4 = true; @@ -96,5 +156,61 @@ private void LoadSettings() ResolveHostnamePreferIPv6 = true; } + private void RefreshCustomDNSServersDisplay() + { + CustomDNSServersDisplay = SettingsManager.Current.Network_CustomDNSServers.Count == 0 ? Strings.NotSet : string.Join("; ", SettingsManager.Current.Network_CustomDNSServers); + } + + #endregion + + #region Commands + + public ICommand EditCustomDNSServersCommand => new RelayCommand(_ => EditCustomDNSServersAction()); + + private void EditCustomDNSServersAction() + { + _ = EditCustomDNSServers(); + } + + #endregion + + #region Methods + + /// + /// Opens the DNS server profile dialog to edit the global custom DNS servers. The profile name + /// is fixed and read-only because there is only a single global list, not multiple named profiles. + /// + private async Task EditCustomDNSServers() + { + var childWindow = new ServerConnectionInfoProfileChildWindow(); + + var info = new ServerConnectionInfoProfile(Strings.Default, + [.. SettingsManager.Current.Network_CustomDNSServers]); + + var childWindowViewModel = new ServerConnectionInfoProfileViewModel(instance => + { + childWindow.IsOpen = false; + ConfigurationManager.Current.IsChildWindowOpen = false; + + SettingsManager.Current.Network_CustomDNSServers = new(instance.Servers); + + RefreshCustomDNSServersDisplay(); + }, _ => + { + childWindow.IsOpen = false; + ConfigurationManager.Current.IsChildWindowOpen = false; + }, + ([], true, true), + _profileDialogDefaultValues, info, true); + + childWindow.Title = Strings.EditDNSServer; + + childWindow.DataContext = childWindowViewModel; + + ConfigurationManager.Current.IsChildWindowOpen = true; + + await Application.Current.MainWindow.ShowChildWindowAsync(childWindow); + } + #endregion -} \ No newline at end of file +} diff --git a/Source/NETworkManager/Views/CustomCommandChildWindow.xaml.cs b/Source/NETworkManager/Views/CustomCommandChildWindow.xaml.cs index 9e4de57d7b..26698d7c44 100644 --- a/Source/NETworkManager/Views/CustomCommandChildWindow.xaml.cs +++ b/Source/NETworkManager/Views/CustomCommandChildWindow.xaml.cs @@ -15,6 +15,7 @@ private void ChildWindow_OnLoaded(object sender, System.Windows.RoutedEventArgs Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(delegate { TextBoxName.Focus(); + TextBoxName.CaretIndex = TextBoxName.Text.Length; })); } } diff --git a/Source/NETworkManager/Views/DNSLookupSettingsView.xaml b/Source/NETworkManager/Views/DNSLookupSettingsView.xaml index f1f1fb802f..702bc0165d 100644 --- a/Source/NETworkManager/Views/DNSLookupSettingsView.xaml +++ b/Source/NETworkManager/Views/DNSLookupSettingsView.xaml @@ -116,7 +116,7 @@ - + diff --git a/Source/NETworkManager/Views/GroupChildWindow.xaml.cs b/Source/NETworkManager/Views/GroupChildWindow.xaml.cs index 47375ed158..c2118f42b2 100644 --- a/Source/NETworkManager/Views/GroupChildWindow.xaml.cs +++ b/Source/NETworkManager/Views/GroupChildWindow.xaml.cs @@ -30,6 +30,7 @@ private void ChildWindow_OnLoaded(object sender, RoutedEventArgs e) Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(delegate { TextBoxName.Focus(); + TextBoxName.CaretIndex = TextBoxName.Text.Length; })); } diff --git a/Source/NETworkManager/Views/IPScannerView.xaml b/Source/NETworkManager/Views/IPScannerView.xaml index d328b7eb8f..36e784063d 100644 --- a/Source/NETworkManager/Views/IPScannerView.xaml +++ b/Source/NETworkManager/Views/IPScannerView.xaml @@ -530,7 +530,10 @@ - + @@ -106,7 +107,8 @@ + mah:TextBoxHelper.Watermark="{Binding ServerWatermark}" + PreviewKeyDown="TextBoxServerOrPort_PreviewKeyDown"> @@ -125,7 +127,8 @@ + mah:TextBoxHelper.Watermark="{Binding PortWatermark}" + PreviewKeyDown="TextBoxServerOrPort_PreviewKeyDown"> @@ -190,6 +193,7 @@ + diff --git a/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml.cs b/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml.cs index cc61fbd0b5..cdd803ec44 100644 --- a/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml.cs +++ b/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml.cs @@ -2,6 +2,7 @@ using System; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; using System.Windows.Threading; namespace NETworkManager.Views; @@ -15,9 +16,22 @@ public ServerConnectionInfoProfileChildWindow() private void ChildWindow_OnLoaded(object sender, RoutedEventArgs e) { + var isNameReadOnly = (DataContext as ServerConnectionInfoProfileViewModel)?.IsNameReadOnly ?? false; + Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(delegate { - TextBoxName.Focus(); + // Focus() alone doesn't move the caret - place it after any existing text (e.g. when + // editing an existing profile) instead of leaving it at the start. + if (isNameReadOnly) + { + TextBoxServer.Focus(); + TextBoxServer.CaretIndex = TextBoxServer.Text.Length; + } + else + { + TextBoxName.Focus(); + TextBoxName.CaretIndex = TextBoxName.Text.Length; + } })); } @@ -26,4 +40,31 @@ private void ContextMenu_Opened(object sender, RoutedEventArgs e) if (sender is ContextMenu menu) menu.DataContext = (ServerConnectionInfoProfileViewModel)DataContext; } + + /// + /// Pressing enter in the server or port textbox adds the server (like clicking the Add button) + /// instead of triggering the window's default button (Save), as long as a server is entered. + /// If the server field is empty, falls through to the default behavior (Save, if enabled) instead. + /// Note: once a server is entered, enter is always intercepted here - even if currently invalid - + /// so it can never fall through to Save, which is enabled independently of the server/port + /// textboxes' validity and would otherwise silently discard an in-progress, invalid entry. + /// + private void TextBoxServerOrPort_PreviewKeyDown(object sender, KeyEventArgs e) + { + if (e.Key != Key.Enter) + return; + + if (DataContext is not ServerConnectionInfoProfileViewModel viewModel) + return; + + if (string.IsNullOrEmpty(viewModel.Server)) + return; + + e.Handled = true; + + if (Validation.GetHasError(TextBoxServer) || Validation.GetHasError(TextBoxPort)) + return; + + viewModel.AddServerCommand.Execute(null); + } } \ No newline at end of file diff --git a/Source/NETworkManager/Views/SettingsNetworkView.xaml b/Source/NETworkManager/Views/SettingsNetworkView.xaml index 52dd23a682..b8599cbad5 100644 --- a/Source/NETworkManager/Views/SettingsNetworkView.xaml +++ b/Source/NETworkManager/Views/SettingsNetworkView.xaml @@ -1,4 +1,4 @@ - - + - + - + + + + + + + + - + - + @@ -35,4 +83,4 @@ --> - \ No newline at end of file + diff --git a/Website/docs/application/dns-lookup.md b/Website/docs/application/dns-lookup.md index c6a4a17461..3203422f66 100644 --- a/Website/docs/application/dns-lookup.md +++ b/Website/docs/application/dns-lookup.md @@ -119,7 +119,7 @@ You can also use the Hotkeys `F2` (`edit`) or `Del` (`delete`) on a selected DNS ### Add DNS suffix (primary) to hostname -Add the primary DNS suffix to the hostname. +Add the primary DNS suffix to the hostname. If no primary DNS suffix is configured (e.g. the computer is not domain-joined), the connection-specific DNS suffix of the active network adapter is used instead. **Type:** `Boolean` diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md index 33e29c9986..ff7e9e00da 100644 --- a/Website/docs/changelog/next-release.md +++ b/Website/docs/changelog/next-release.md @@ -29,6 +29,11 @@ Release date: **xx.xx.2026** - New **Map** view below the hop list, visualizing each resolved hop's geolocation on an offline world map. Consecutive hops are connected with curved, directional arrows; hovering a marker shows its location, ISP/ASN, hostname, IP address and average round-trip time, while hovering an arrow shows the source and destination location of that segment. The map supports mouse-wheel zoom and drag-to-pan, and can be collapsed via a toggle button on the map itself, similar to the Profiles panel. The map is only shown if **Check IP geolocation** and the new **Show map** setting are both enabled, since hops need a resolved geolocation to be plotted. [#3520](https://github.com/BornToBeRoot/NETworkManager/pull/3520) +**Settings** + +- New global **DNS suffix** options in [Settings > Network](../settings/network.md): **Add DNS suffix (primary) to hostname** appends the Windows primary (or, if none is set, the active network adapter's connection-specific) DNS suffix to a bare hostname before resolving it, and **Use custom DNS suffix** lets you override it with a suffix of your own. Previously only available in DNS Lookup, this now also applies to every other tool that resolves hostnames through NETworkManager's shared DNS resolver (e.g. Ping, Traceroute, Port Scanner, IP Scanner, SNTP Lookup) — DNS Lookup stays independent. [#3591](https://github.com/BornToBeRoot/NETworkManager/pull/3591) +- **DNS server(s)** in [Settings > Network](../settings/network.md) is now a list of servers (IP address + port), configured via an **Edit DNS server** dialog, replacing the previous single semicolon-separated, port-53-only text field. [#3591](https://github.com/BornToBeRoot/NETworkManager/pull/3591) + ## Improvements - The collapsed/expanded state of profile groups (e.g. **linux-server**) is now remembered per profile file and shared across all tools, instead of resetting every time you switch tools or restart the application. [#3539](https://github.com/BornToBeRoot/NETworkManager/pull/3539) @@ -68,6 +73,10 @@ Release date: **xx.xx.2026** - Fixed the DNS status (Computer/Router/Internet) in the **Network Connection** widget showing as an error when no PTR record exists for the address, which is common and expected for private IP ranges. This is now shown as informational instead of critical. [#3553](https://github.com/BornToBeRoot/NETworkManager/pull/3553) - Fixed a race condition in the **Network Connection** widget where results from a superseded check could overwrite the results of a newer, still-running check after quickly reopening the widget. [#3553](https://github.com/BornToBeRoot/NETworkManager/pull/3553) +**DNS Lookup** + +- Fixed **Add DNS suffix (primary) to hostname** silently appending no suffix at all on machines without a Windows primary DNS suffix configured (e.g. not domain-joined), even though a usable suffix was available via the active network adapter's connection-specific DNS suffix (shown under **DNS Suffix Search List** in `ipconfig /all`). It now falls back to that adapter's suffix in this case. [#3591](https://github.com/BornToBeRoot/NETworkManager/pull/3591) + **IP Scanner** - Fixed NetBIOS lookups (computer name, domain/workgroup, user name) not starting until a host's entire port scan had finished, since the port scan wasn't actually running asynchronously despite being awaited alongside it. Ping, port scan, and NetBIOS resolution now genuinely run concurrently for every host. [#3564](https://github.com/BornToBeRoot/NETworkManager/pull/3564) diff --git a/Website/docs/settings/network.md b/Website/docs/settings/network.md index 9505c6cd45..f7cc49f5dc 100644 --- a/Website/docs/settings/network.md +++ b/Website/docs/settings/network.md @@ -1,7 +1,7 @@ --- sidebar_position: 4 -description: "Configure custom DNS servers used by NETworkManager for name resolution across all network tools." -keywords: [NETworkManager, network settings, DNS server configuration, custom DNS, name resolution] +description: "Configure the global network settings for NETworkManager, including custom DNS servers, DNS suffix behavior, and hostname resolution protocol preference." +keywords: [NETworkManager, network settings, DNS server configuration, custom DNS, DNS suffix, name resolution] --- # Network @@ -16,13 +16,39 @@ Enables or disables the custom DNS server(s) for all DNS queries. If disabled, t ### DNS server(s) -A semicolon-separated list of IP addresses of DNS servers to be used for DNS queries when [Use custom DNS server](#use-custom-dns-server) is enabled. +The IP addresses (and ports) of the DNS servers to be used for DNS queries when [Use custom DNS server](#use-custom-dns-server) is enabled. Configured via the **Edit DNS server** button. + +**Type:** `List of ServerConnectionInfo (IP address + port)` + +**Default:** `Empty` + +**Example:** `1.1.1.1:53; 1.0.0.1:53` + +### Add DNS suffix (primary) to hostname + +Enables or disables appending a DNS suffix to a bare hostname (one without a dot) before it is resolved. This applies to all tools that resolve hostnames through NETworkManager's DNS resolver (e.g. Ping, Traceroute, Port Scanner, IP Scanner, SNTP Lookup). Fully-qualified hostnames and reverse (PTR) lookups are not affected. If disabled, hostnames are resolved as entered. If enabled, either the primary DNS suffix configured in Windows or the [custom DNS suffix](#use-custom-dns-suffix) is appended. If no primary DNS suffix is configured (e.g. the computer is not domain-joined), the connection-specific DNS suffix of the active network adapter is used instead. + +**Type:** `Boolean` + +**Default:** `Enabled` + +### Use custom DNS suffix + +Enables or disables the use of a custom DNS suffix instead of the primary DNS suffix configured in Windows, when [Add DNS suffix (primary) to hostname](#add-dns-suffix-primary-to-hostname) is enabled. + +**Type:** `Boolean` + +**Default:** `Disabled` + +### DNS suffix + +The custom DNS suffix to append to a hostname when [Use custom DNS suffix](#use-custom-dns-suffix) is enabled. **Type:** `String` **Default:** `Empty` -**Example:** `1.1.1.1; 1.0.0.1` +**Example:** `example.com` ### Preffered protocol when resolving hostnames: