From 9b500dbf697645dc640498a65228b4aee85e8e60 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Thu, 4 Dec 2025 00:32:32 +0100
Subject: [PATCH 01/11] Feature: DNS Lookup - Input server
---
.../DNSServerConnectionInfoProfileToString.cs | 23 -------
.../Network/DNSServer.cs | 36 +++++------
.../Network/DNSServerConnectionInfoProfile.cs | 3 +-
.../Network/ServerConnectionInfoProfile.cs | 7 +-
.../NETworkManager.Settings/SettingsInfo.cs | 2 +-
.../ViewModels/DNSLookupSettingsViewModel.cs | 2 +-
.../ViewModels/DNSLookupViewModel.cs | 64 +++++++++++++++++--
.../NETworkManager/Views/DNSLookupView.xaml | 17 ++---
8 files changed, 92 insertions(+), 62 deletions(-)
delete mode 100644 Source/NETworkManager.Converters/DNSServerConnectionInfoProfileToString.cs
diff --git a/Source/NETworkManager.Converters/DNSServerConnectionInfoProfileToString.cs b/Source/NETworkManager.Converters/DNSServerConnectionInfoProfileToString.cs
deleted file mode 100644
index feb47ef7db..0000000000
--- a/Source/NETworkManager.Converters/DNSServerConnectionInfoProfileToString.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Globalization;
-using System.Windows.Data;
-using NETworkManager.Localization.Resources;
-using NETworkManager.Models.Network;
-
-namespace NETworkManager.Converters;
-
-public sealed class DNSServerConnectionInfoProfileToString : IValueConverter
-{
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- if (value is not DNSServerConnectionInfoProfile dnsServerInfo)
- return "-/-";
-
- return dnsServerInfo.UseWindowsDNSServer ? $"[{Strings.WindowsDNSSettings}]" : dnsServerInfo.Name;
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
-}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Network/DNSServer.cs b/Source/NETworkManager.Models/Network/DNSServer.cs
index e654d973cb..13cb3d7cd4 100644
--- a/Source/NETworkManager.Models/Network/DNSServer.cs
+++ b/Source/NETworkManager.Models/Network/DNSServer.cs
@@ -13,34 +13,34 @@ public static class DNSServer
/// List of common dns servers.
public static List GetDefaultList()
{
- return new List
- {
+ return
+ [
new(), // Windows DNS server
- new("Cloudflare", new List
- {
+ new("Cloudflare",
+ [
new("1.1.1.1", 53, TransportProtocol.Udp),
new("1.0.0.1", 53, TransportProtocol.Udp)
- }),
- new("DNS.Watch", new List
- {
+ ]),
+ new("DNS.Watch",
+ [
new("84.200.69.80", 53, TransportProtocol.Udp),
new("84.200.70.40", 53, TransportProtocol.Udp)
- }),
- new("Google Public DNS", new List
- {
+ ]),
+ new("Google Public DNS",
+ [
new("8.8.8.8", 53, TransportProtocol.Udp),
new("8.8.4.4", 53, TransportProtocol.Udp)
- }),
- new("Level3", new List
- {
+ ]),
+ new("Level3",
+ [
new("209.244.0.3", 53, TransportProtocol.Udp),
new("209.244.0.4", 53, TransportProtocol.Udp)
- }),
- new("Verisign", new List
- {
+ ]),
+ new("Verisign",
+ [
new("64.6.64.6", 53, TransportProtocol.Udp),
new("64.6.65.6", 53, TransportProtocol.Udp)
- })
- };
+ ])
+ ];
}
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs b/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs
index 7e06301e30..32737e9f71 100644
--- a/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs
+++ b/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs
@@ -13,6 +13,7 @@ public class DNSServerConnectionInfoProfile : ServerConnectionInfoProfile
///
public DNSServerConnectionInfoProfile()
{
+ Name = "[Windows DNS]";
UseWindowsDNSServer = true;
}
@@ -28,5 +29,5 @@ public DNSServerConnectionInfoProfile(string name, List se
///
/// Use the DNS server from Windows.
///
- public bool UseWindowsDNSServer { get; set; }
+ public bool UseWindowsDNSServer { get; set; }
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Network/ServerConnectionInfoProfile.cs b/Source/NETworkManager.Models/Network/ServerConnectionInfoProfile.cs
index 8d4848d83c..3c9afed439 100644
--- a/Source/NETworkManager.Models/Network/ServerConnectionInfoProfile.cs
+++ b/Source/NETworkManager.Models/Network/ServerConnectionInfoProfile.cs
@@ -33,5 +33,10 @@ public ServerConnectionInfoProfile(string name, List serve
///
/// List of servers as .
///
- public List Servers { get; set; } = new();
+ public List Servers { get; set; } = [];
+
+ public override string ToString()
+ {
+ return Name;
+ }
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index a60d8bcf01..428cd23715 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -1753,7 +1753,7 @@ public ObservableCollection DNSLookup_DNSServers
}
}
- private DNSServerConnectionInfoProfile _dnsLookup_SelectedDNSServer = new();
+ private DNSServerConnectionInfoProfile _dnsLookup_SelectedDNSServer = null;
public DNSServerConnectionInfoProfile DNSLookup_SelectedDNSServer
{
diff --git a/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs
index 1bcfe63d1d..32e5431643 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs
@@ -28,7 +28,7 @@ public class DNSLookupSettingsViewModel : ViewModelBase
public ICollectionView DNSServers { get; }
- private DNSServerConnectionInfoProfile _selectedDNSServer = new();
+ private DNSServerConnectionInfoProfile _selectedDNSServer = null;
public DNSServerConnectionInfoProfile SelectedDNSServer
{
diff --git a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
index 288cef0176..d70fbde655 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
@@ -16,6 +16,7 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
+using System.Net;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
@@ -57,24 +58,69 @@ public string Host
public ICollectionView DNSServers { get; }
private DNSServerConnectionInfoProfile _dnsServer = new();
-
public DNSServerConnectionInfoProfile DNSServer
{
get => _dnsServer;
- set
+ private set
{
- if (value == _dnsServer)
+ if (_dnsServer == value)
return;
+
+ _dnsServer = value ?? new DNSServerConnectionInfoProfile();
if (!_isLoading)
- SettingsManager.Current.DNSLookup_SelectedDNSServer = value;
+ SettingsManager.Current.DNSLookup_SelectedDNSServer = _dnsServer;
+
+ OnPropertyChanged();
+ }
+ }
+
+ private DNSServerConnectionInfoProfile _selectedListProfile;
+ public DNSServerConnectionInfoProfile SelectedListProfile
+ {
+ get => _selectedListProfile;
+ set
+ {
+ if (_selectedListProfile == value)
+ return;
+
+ if (value != null)
+ {
+ DNSServer = value;
+ DNSServerQuickInput = value.ToString(); // uses your override
+ }
- _dnsServer = value;
+ _selectedListProfile = value;
OnPropertyChanged();
}
}
- private List _queryTypes = new();
+ // Text box content
+ private string _dnsServerQuickInput = string.Empty;
+ public string DNSServerQuickInput
+ {
+ get => _dnsServerQuickInput;
+ set
+ {
+ if (_dnsServerQuickInput == value)
+ return;
+
+ _dnsServerQuickInput = value?.Trim() ?? string.Empty;
+ OnPropertyChanged();
+
+ // As soon as user types → deselect any list item
+ SelectedListProfile = null;
+
+ // Create custom profile from raw IP
+ if (IPAddress.TryParse(_dnsServerQuickInput, out IPAddress x))
+ {
+ // Temporarily switch to this custom profile
+ DNSServer = new DNSServerConnectionInfoProfile("CUSTOM", [new ServerConnectionInfo(x.ToString(), 53)]);
+ }
+ }
+ }
+
+ private List _queryTypes = [];
public List QueryTypes
{
@@ -220,10 +266,14 @@ public DNSLookupViewModel(IDialogCoordinator instance, Guid tabId, string host)
ListSortDirection.Descending));
DNSServers.SortDescriptions.Add(new SortDescription(nameof(DNSServerConnectionInfoProfile.Name),
ListSortDirection.Ascending));
- DNSServer = DNSServers.SourceCollection.Cast()
+ var initialDNSServer = DNSServers.SourceCollection.Cast()
.FirstOrDefault(x => x.Name == SettingsManager.Current.DNSLookup_SelectedDNSServer.Name) ??
DNSServers.SourceCollection.Cast().First();
+ DNSServer = initialDNSServer;
+ SelectedListProfile = initialDNSServer;
+ DNSServerQuickInput = initialDNSServer.ToString();
+
ResultsView = CollectionViewSource.GetDefaultView(Results);
ResultsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(DNSLookupRecordInfo.NameServerAsString)));
ResultsView.SortDescriptions.Add(new SortDescription(nameof(DNSLookupRecordInfo.NameServerIPAddress),
diff --git a/Source/NETworkManager/Views/DNSLookupView.xaml b/Source/NETworkManager/Views/DNSLookupView.xaml
index 7917c324aa..faab72a4eb 100644
--- a/Source/NETworkManager/Views/DNSLookupView.xaml
+++ b/Source/NETworkManager/Views/DNSLookupView.xaml
@@ -19,7 +19,6 @@
-
@@ -71,15 +70,13 @@
-
-
-
-
-
-
+
From 027b11b6cfdfa8b0b08fd63550155a983e39687d Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Wed, 10 Dec 2025 22:49:26 +0100
Subject: [PATCH 02/11] Feature: Enter dns server in ui
---
Source/GlobalAssemblyInfo.cs | 4 +-
.../Network/DNSLookupErrorArgs.cs | 1 +
.../Network/DNSServerConnectionInfoProfile.cs | 1 +
.../NETworkManager.Settings/SettingsInfo.cs | 28 ++--
.../SettingsManager.cs | 10 ++
.../ViewModels/DNSLookupViewModel.cs | 142 +++++++++++-------
.../NETworkManager/Views/DNSLookupView.xaml | 8 +-
7 files changed, 117 insertions(+), 77 deletions(-)
diff --git a/Source/GlobalAssemblyInfo.cs b/Source/GlobalAssemblyInfo.cs
index 1b58f5b727..f756fd756f 100644
--- a/Source/GlobalAssemblyInfo.cs
+++ b/Source/GlobalAssemblyInfo.cs
@@ -6,5 +6,5 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-[assembly: AssemblyVersion("2025.11.16.0")]
-[assembly: AssemblyFileVersion("2025.11.16.0")]
+[assembly: AssemblyVersion("2025.12.10.0")]
+[assembly: AssemblyFileVersion("2025.12.10.0")]
diff --git a/Source/NETworkManager.Models/Network/DNSLookupErrorArgs.cs b/Source/NETworkManager.Models/Network/DNSLookupErrorArgs.cs
index ee773b65ca..59d93615d0 100644
--- a/Source/NETworkManager.Models/Network/DNSLookupErrorArgs.cs
+++ b/Source/NETworkManager.Models/Network/DNSLookupErrorArgs.cs
@@ -6,6 +6,7 @@ public class DNSLookupErrorArgs : EventArgs
{
public DNSLookupErrorArgs()
{
+
}
public DNSLookupErrorArgs(string query, string server, string ipEndPoint, string errorMessage)
diff --git a/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs b/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs
index 32737e9f71..c1ba8003f2 100644
--- a/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs
+++ b/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs
@@ -24,6 +24,7 @@ public DNSServerConnectionInfoProfile()
/// List of servers as .
public DNSServerConnectionInfoProfile(string name, List servers) : base(name, servers)
{
+
}
///
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index 428cd23715..7f195085d2 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -1723,7 +1723,7 @@ public ExportFileType Traceroute_ExportFileType
#region DNS Lookup
- private ObservableCollection _dnsLookup_HostHistory = new();
+ private ObservableCollection _dnsLookup_HostHistory = [];
public ObservableCollection DNSLookup_HostHistory
{
@@ -1738,7 +1738,7 @@ public ObservableCollection DNSLookup_HostHistory
}
}
- private ObservableCollection _dnsLookup_DNSServers = new();
+ private ObservableCollection _dnsLookup_DNSServers = [];
public ObservableCollection DNSLookup_DNSServers
{
@@ -1753,8 +1753,10 @@ public ObservableCollection DNSLookup_DNSServers
}
}
+ [Obsolete("Use DNSLookup_SelectedDNSServer_v2 instead.")]
private DNSServerConnectionInfoProfile _dnsLookup_SelectedDNSServer = null;
+ [Obsolete("Use DNSLookup_SelectedDNSServer_v2 instead.")]
public DNSServerConnectionInfoProfile DNSLookup_SelectedDNSServer
{
get => _dnsLookup_SelectedDNSServer;
@@ -1768,37 +1770,35 @@ public DNSServerConnectionInfoProfile DNSLookup_SelectedDNSServer
}
}
- private QueryClass _dnsLookup_QueryClass = GlobalStaticConfiguration.DNSLookup_QueryClass;
+ private string _dnsLookup_SelectedDNSServer_v2;
- public QueryClass DNSLookup_QueryClass
+ public string DNSLookup_SelectedDNSServer_v2
{
- get => _dnsLookup_QueryClass;
+ get => _dnsLookup_SelectedDNSServer_v2;
set
{
- if (value == _dnsLookup_QueryClass)
+ if (value == _dnsLookup_SelectedDNSServer_v2)
return;
- _dnsLookup_QueryClass = value;
+ _dnsLookup_SelectedDNSServer_v2 = value;
OnPropertyChanged();
}
}
- /*
- private bool _dnsLookup_ShowOnlyMostCommonQueryTypes = true;
+ private QueryClass _dnsLookup_QueryClass = GlobalStaticConfiguration.DNSLookup_QueryClass;
- public bool DNSLookup_ShowOnlyMostCommonQueryTypes
+ public QueryClass DNSLookup_QueryClass
{
- get => _dnsLookup_ShowOnlyMostCommonQueryTypes;
+ get => _dnsLookup_QueryClass;
set
{
- if (value == _dnsLookup_ShowOnlyMostCommonQueryTypes)
+ if (value == _dnsLookup_QueryClass)
return;
- _dnsLookup_ShowOnlyMostCommonQueryTypes = value;
+ _dnsLookup_QueryClass = value;
OnPropertyChanged();
}
}
- */
private QueryType _dnsLookup_QueryType = GlobalStaticConfiguration.DNSLookup_QueryType;
diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs
index 2d3146aab9..cbd20d5ff4 100644
--- a/Source/NETworkManager.Settings/SettingsManager.cs
+++ b/Source/NETworkManager.Settings/SettingsManager.cs
@@ -337,6 +337,16 @@ private static void UpgradeToLatest(Version version)
{
Log.Info($"Apply upgrade to {version}...");
+ // DNS Lookup
+
+ Log.Info("Migrate DNS Lookup settings to new structure...");
+
+ Current.DNSLookup_SelectedDNSServer_v2 = Current.DNSLookup_SelectedDNSServer?.Name;
+
+ Log.Info($"Selected DNS server set to \"{Current.DNSLookup_SelectedDNSServer_v2}\"");
+
+ // AWS Session Manager
+
Log.Info("Removing deprecated app \"AWS Session Manager\", if it exists...");
var appToRemove = Current.General_ApplicationList
diff --git a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
index fb45193397..ad963abb08 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
@@ -81,77 +81,50 @@ public string Host
///
/// Backing field for .
///
- private DNSServerConnectionInfoProfile _dnsServer = new();
+ private string _dnsServer;
///
/// Gets or sets the selected DNS server.
+ /// This can either be an ip/host:port or a profile name.
///
- public DNSServerConnectionInfoProfile DNSServer
+ public string DNSServer
{
get => _dnsServer;
- private set
+ set
{
if (_dnsServer == value)
return;
-
- _dnsServer = value ?? new DNSServerConnectionInfoProfile();
- if (!_isLoading)
- SettingsManager.Current.DNSLookup_SelectedDNSServer = _dnsServer;
-
- OnPropertyChanged();
- }
- }
+ // Try finding matching dns server profile by name, otherwise set to null (de-select)
+ SelectedDNSServer = SettingsManager.Current.DNSLookup_DNSServers
+ .FirstOrDefault(x => x.Name == value);
- private DNSServerConnectionInfoProfile _selectedListProfile;
- public DNSServerConnectionInfoProfile SelectedListProfile
- {
- get => _selectedListProfile;
- set
- {
- if (_selectedListProfile == value)
- return;
-
- if (value != null)
- {
- DNSServer = value;
- DNSServerQuickInput = value.ToString(); // uses your override
- }
+ if (!_isLoading)
+ SettingsManager.Current.DNSLookup_SelectedDNSServer_v2 = value;
- _selectedListProfile = value;
+ _dnsServer = value;
OnPropertyChanged();
}
}
- // Text box content
- private string _dnsServerQuickInput = string.Empty;
- public string DNSServerQuickInput
+ private DNSServerConnectionInfoProfile _selectedDNSServer;
+ public DNSServerConnectionInfoProfile SelectedDNSServer
{
- get => _dnsServerQuickInput;
+ get => _selectedDNSServer;
set
{
- if (_dnsServerQuickInput == value)
+ if (_selectedDNSServer == value)
return;
- _dnsServerQuickInput = value?.Trim() ?? string.Empty;
+ _selectedDNSServer = value;
OnPropertyChanged();
-
- // As soon as user types → deselect any list item
- SelectedListProfile = null;
-
- // Create custom profile from raw IP
- if (IPAddress.TryParse(_dnsServerQuickInput, out IPAddress x))
- {
- // Temporarily switch to this custom profile
- DNSServer = new DNSServerConnectionInfoProfile("CUSTOM", [new ServerConnectionInfo(x.ToString(), 53)]);
- }
}
}
///
/// Backing field for .
///
- private List _queryTypes = new();
+ private List _queryTypes = [];
///
/// Gets the list of available query types.
@@ -217,7 +190,7 @@ public bool IsRunning
///
/// Backing field for .
///
- private ObservableCollection _results = new();
+ private ObservableCollection _results = [];
///
/// Gets or sets the collection of lookup results.
@@ -351,13 +324,10 @@ public DNSLookupViewModel(IDialogCoordinator instance, Guid tabId, string host)
ListSortDirection.Descending));
DNSServers.SortDescriptions.Add(new SortDescription(nameof(DNSServerConnectionInfoProfile.Name),
ListSortDirection.Ascending));
- var initialDNSServer = DNSServers.SourceCollection.Cast()
- .FirstOrDefault(x => x.Name == SettingsManager.Current.DNSLookup_SelectedDNSServer.Name) ??
- DNSServers.SourceCollection.Cast().First();
-
- DNSServer = initialDNSServer;
- SelectedListProfile = initialDNSServer;
- DNSServerQuickInput = initialDNSServer.ToString();
+
+ DNSServer = string.IsNullOrEmpty(SettingsManager.Current.DNSLookup_SelectedDNSServer_v2)
+ ? SettingsManager.Current.DNSLookup_DNSServers.FirstOrDefault()?.Name
+ : SettingsManager.Current.DNSLookup_SelectedDNSServer_v2;
ResultsView = CollectionViewSource.GetDefaultView(Results);
ResultsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(DNSLookupRecordInfo.NameServerAsString)));
@@ -381,7 +351,7 @@ public void OnLoaded()
return;
if (!string.IsNullOrEmpty(Host))
- Query();
+ QueryAsync();
_firstLoad = false;
}
@@ -441,7 +411,7 @@ private bool Query_CanExecute(object parameter)
private void QueryAction()
{
if (!IsRunning)
- Query();
+ QueryAsync();
}
///
@@ -464,7 +434,7 @@ private void ExportAction()
///
/// Performs the DNS query.
///
- private void Query()
+ private async Task QueryAsync()
{
IsStatusMessageDisplayed = false;
StatusMessage = string.Empty;
@@ -496,9 +466,67 @@ private void Query()
dnsSettings.CustomDNSSuffix = SettingsManager.Current.DNSLookup_CustomDNSSuffix?.TrimStart('.');
}
- var dnsLookup = DNSServer.UseWindowsDNSServer
- ? new DNSLookup(dnsSettings)
- : new DNSLookup(dnsSettings, DNSServer.Servers);
+
+ DNSLookup dnsLookup;
+
+ // Try find existing dns server profile
+ var dnsServerProfile = SettingsManager.Current.DNSLookup_DNSServers
+ .FirstOrDefault(x => x.Name == DNSServer);
+
+ // Use profile if found
+ if (dnsServerProfile != null)
+ {
+ dnsLookup = dnsServerProfile.UseWindowsDNSServer
+ ? new DNSLookup(dnsSettings)
+ : new DNSLookup(dnsSettings, dnsServerProfile.Servers);
+ }
+ // Otherwise try to parse custom server string
+ else
+ {
+ List customDNSServers = [];
+
+ foreach (var customDNSServer in DNSServer.Split(';').Select(x => x.Trim()))
+ {
+ var customDNSServerArgs = customDNSServer.Split(':');
+
+ var server = customDNSServerArgs[0];
+ var port = customDNSServerArgs.Length == 2 && int.TryParse(customDNSServerArgs[1], out var p) ? p : 53;
+
+ // Resolve hostname to IP address
+ if (!IPAddress.TryParse(server, out _))
+ {
+ var dnsResult = await DNSClientHelper.ResolveAorAaaaAsync(server,
+ SettingsManager.Current.Network_ResolveHostnamePreferIPv4);
+
+ if (dnsResult.HasError)
+ {
+ var dnsErrorMessage = DNSClientHelper.FormatDNSClientResultError(server, dnsResult);
+
+ if(!string.IsNullOrEmpty(StatusMessage))
+ StatusMessage += Environment.NewLine;
+
+ StatusMessage += $"{Strings.DNSServer}: {dnsErrorMessage}";
+ IsStatusMessageDisplayed = true;
+
+ continue; // Skip this server, try next one
+ }
+
+ server = dnsResult.Value.ToString();
+ }
+
+ customDNSServers.Add(new ServerConnectionInfo(server, port, TransportProtocol.Udp));
+ }
+
+ // Check if we have any valid custom dns servers
+ if (customDNSServers.Count == 0)
+ {
+ IsRunning = false;
+
+ return;
+ }
+
+ dnsLookup = new DNSLookup(dnsSettings, customDNSServers);
+ }
dnsLookup.RecordReceived += DNSLookup_RecordReceived;
dnsLookup.LookupError += DNSLookup_LookupError;
diff --git a/Source/NETworkManager/Views/DNSLookupView.xaml b/Source/NETworkManager/Views/DNSLookupView.xaml
index faab72a4eb..fc2efbd7f6 100644
--- a/Source/NETworkManager/Views/DNSLookupView.xaml
+++ b/Source/NETworkManager/Views/DNSLookupView.xaml
@@ -71,10 +71,10 @@
From 37d77447a450dc35f290f5305d4edf946a29fe0c Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Wed, 10 Dec 2025 23:07:24 +0100
Subject: [PATCH 03/11] Update DNSLookupView.xaml
---
Source/NETworkManager/Views/DNSLookupView.xaml | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/Source/NETworkManager/Views/DNSLookupView.xaml b/Source/NETworkManager/Views/DNSLookupView.xaml
index fc2efbd7f6..904c9ef194 100644
--- a/Source/NETworkManager/Views/DNSLookupView.xaml
+++ b/Source/NETworkManager/Views/DNSLookupView.xaml
@@ -73,10 +73,15 @@
+ SelectedItem="{Binding SelectedDNSServer}"
+ IsEditable="True">
+
+
+
+
+
+
+
@@ -93,7 +98,7 @@
Value="True">
-
From 20bd604d06b86b07751befb18d6ab8eaaaf1085f Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Wed, 10 Dec 2025 23:35:05 +0100
Subject: [PATCH 04/11] Chore: Adjust regex for IPv4
---
...lOrEmptyOrIPv4AddressToBooleanConverter.cs | 7 ++--
.../Network/HostRangeHelper.cs | 9 +++--
.../Network/SNTPLookup.cs | 7 ++--
.../NETworkManager.Utilities/RegexHelper.cs | 40 ++++++++++++++-----
.../EmptyOrIPv4AddressValidator.cs | 9 +++--
.../IPAddressOrHostnameAsRangeValidator.cs | 2 +-
.../IPAddressOrHostnameValidator.cs | 2 +-
.../IPv4AddressValidator.cs | 9 ++---
.../MultipleHostsRangeValidator.cs | 12 +++---
.../MultipleIPAddressesValidator.cs | 12 +++---
.../ServerValidator.cs | 4 +-
.../NetworkConnectionWidgetViewModel.cs | 2 +-
.../ServerConnectionInfoProfileDialog.xaml | 2 +-
13 files changed, 68 insertions(+), 49 deletions(-)
diff --git a/Source/NETworkManager.Converters/StringIsNotNullOrEmptyOrIPv4AddressToBooleanConverter.cs b/Source/NETworkManager.Converters/StringIsNotNullOrEmptyOrIPv4AddressToBooleanConverter.cs
index 31e3e5a3d8..a118a33030 100644
--- a/Source/NETworkManager.Converters/StringIsNotNullOrEmptyOrIPv4AddressToBooleanConverter.cs
+++ b/Source/NETworkManager.Converters/StringIsNotNullOrEmptyOrIPv4AddressToBooleanConverter.cs
@@ -1,8 +1,7 @@
-using System;
+using NETworkManager.Utilities;
+using System;
using System.Globalization;
-using System.Text.RegularExpressions;
using System.Windows.Data;
-using NETworkManager.Utilities;
namespace NETworkManager.Converters;
@@ -10,7 +9,7 @@ public sealed class StringIsNotNullOrEmptyOrIPv4AddressToBooleanConverter : IVal
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- return !string.IsNullOrEmpty(value as string) && !Regex.IsMatch((string)value, RegexHelper.IPv4AddressRegex);
+ return !string.IsNullOrEmpty(value as string) && !RegexHelper.IPv4AddressRegex().IsMatch((string)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
diff --git a/Source/NETworkManager.Models/Network/HostRangeHelper.cs b/Source/NETworkManager.Models/Network/HostRangeHelper.cs
index 9017e5ce80..37928f8368 100644
--- a/Source/NETworkManager.Models/Network/HostRangeHelper.cs
+++ b/Source/NETworkManager.Models/Network/HostRangeHelper.cs
@@ -1,4 +1,6 @@
-using System.Collections.Concurrent;
+using ControlzEx.Standard;
+using NETworkManager.Utilities;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@@ -6,7 +8,6 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
-using NETworkManager.Utilities;
namespace NETworkManager.Models.Network;
@@ -46,7 +47,7 @@ private static (List<(IPAddress ipAddress, string hostname)> hosts, List
switch (host)
{
// 192.168.0.1
- case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressRegex):
+ case var _ when RegexHelper.IPv4AddressRegex().IsMatch(host):
// 2001:db8:85a3::8a2e:370:7334
case var _ when Regex.IsMatch(host, RegexHelper.IPv6AddressRegex):
hostsBag.Add((IPAddress.Parse(host), string.Empty));
@@ -71,7 +72,7 @@ private static (List<(IPAddress ipAddress, string hostname)> hosts, List
break;
// 192.168.0.0 - 192.168.0.100
- case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressRangeRegex):
+ case var _ when RegexHelper.IPv4AddressRangeRegex().IsMatch(host):
var range = host.Split('-');
Parallel.For(IPv4Address.ToInt32(IPAddress.Parse(range[0])),
diff --git a/Source/NETworkManager.Models/Network/SNTPLookup.cs b/Source/NETworkManager.Models/Network/SNTPLookup.cs
index 6f6b8d351c..0a20f369c0 100644
--- a/Source/NETworkManager.Models/Network/SNTPLookup.cs
+++ b/Source/NETworkManager.Models/Network/SNTPLookup.cs
@@ -1,10 +1,11 @@
-using System;
+using ControlzEx.Standard;
+using NETworkManager.Utilities;
+using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
-using NETworkManager.Utilities;
namespace NETworkManager.Models.Network;
@@ -102,7 +103,7 @@ public void QueryAsync(IEnumerable servers, bool dnsResolv
// NTP requires an IP address to connect to
IPAddress serverIP = null;
- if (Regex.IsMatch(server.Server, RegexHelper.IPv4AddressRegex) ||
+ if (RegexHelper.IPv4AddressRegex().IsMatch(server.Server) ||
Regex.IsMatch(server.Server, RegexHelper.IPv6AddressRegex))
{
serverIP = IPAddress.Parse(server.Server);
diff --git a/Source/NETworkManager.Utilities/RegexHelper.cs b/Source/NETworkManager.Utilities/RegexHelper.cs
index c1445c70a7..ba09fd7ce2 100644
--- a/Source/NETworkManager.Utilities/RegexHelper.cs
+++ b/Source/NETworkManager.Utilities/RegexHelper.cs
@@ -1,6 +1,8 @@
-namespace NETworkManager.Utilities;
+using System.Text.RegularExpressions;
-public static class RegexHelper
+namespace NETworkManager.Utilities;
+
+public static partial class RegexHelper
{
///
/// Match an IPv4-Address like 192.168.178.1
@@ -10,20 +12,36 @@ public static class RegexHelper
@"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
///
- /// Match exactly an IPv4-Address like 192.168.178.1
+ /// Provides a compiled regular expression that matches valid IPv4 addresses in dot-decimal notation.
///
- // ReSharper disable once InconsistentNaming
- public const string IPv4AddressRegex = $"^{IPv4AddressValues}$";
+ /// The returned regular expression is compiled for performance. Use this regex to validate or
+ /// extract IPv4 addresses from text. The pattern enforces correct octet ranges and dot separators.
+ /// A instance that matches IPv4 addresses in the format "x.x.x.x", where each x is a number
+ /// from 0 to 255.
+ [GeneratedRegex($"^{IPv4AddressValues}$")]
+ public static partial Regex IPv4AddressRegex();
///
- /// Match IPv4-Address within a string
+ /// Provides a compiled regular expression that matches valid IPv4 addresses within input text.
///
- // ReSharper disable once InconsistentNaming
- public const string IPv4AddressExtractRegex = IPv4AddressValues;
+ /// The returned regular expression matches IPv4 addresses in standard dotted-decimal notation
+ /// (e.g., "192.168.1.1"). The regular expression is compiled for improved performance when used
+ /// repeatedly.
+ /// A instance that can be used to extract IPv4 addresses from strings.
+ [GeneratedRegex(IPv4AddressValues)]
+ public static partial Regex IPv4AddressExtractRegex();
- // Match IPv4-Address Range like 192.168.178.1-192.168.178.127
- // ReSharper disable once InconsistentNaming
- public const string IPv4AddressRangeRegex = $"^{IPv4AddressValues}-{IPv4AddressValues}$";
+ ///
+ /// Gets a regular expression that matches an IPv4 address range in the format "start-end", where both start and end
+ /// are valid IPv4 addresses.
+ ///
+ /// The regular expression expects the input to consist of two IPv4 addresses separated by a
+ /// hyphen, with no additional whitespace or characters. Both addresses must be valid IPv4 addresses. This can be
+ /// used to validate or parse address range strings in network configuration scenarios.
+ /// A instance that matches strings representing IPv4 address ranges, such as
+ /// "192.168.1.1-192.168.1.100".
+ [GeneratedRegex($"^{IPv4AddressValues}-{IPv4AddressValues}$")]
+ public static partial Regex IPv4AddressRangeRegex();
// Match a MAC-Address 000000000000 00:00:00:00:00:00, 00-00-00-00-00-00-00 or 0000.0000.0000
public const string MACAddressRegex =
diff --git a/Source/NETworkManager.Validators/EmptyOrIPv4AddressValidator.cs b/Source/NETworkManager.Validators/EmptyOrIPv4AddressValidator.cs
index 471a02fc5a..53acea2a99 100644
--- a/Source/NETworkManager.Validators/EmptyOrIPv4AddressValidator.cs
+++ b/Source/NETworkManager.Validators/EmptyOrIPv4AddressValidator.cs
@@ -1,8 +1,9 @@
-using System.Globalization;
-using System.Text.RegularExpressions;
-using System.Windows.Controls;
+using ControlzEx.Standard;
using NETworkManager.Localization.Resources;
using NETworkManager.Utilities;
+using System.Globalization;
+using System.Text.RegularExpressions;
+using System.Windows.Controls;
namespace NETworkManager.Validators;
@@ -13,7 +14,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
if (string.IsNullOrEmpty(value as string))
return ValidationResult.ValidResult;
- return Regex.IsMatch((string)value, RegexHelper.IPv4AddressRegex)
+ return RegexHelper.IPv4AddressRegex().IsMatch((string)value)
? ValidationResult.ValidResult
: new ValidationResult(false, Strings.EnterValidIPv4Address);
}
diff --git a/Source/NETworkManager.Validators/IPAddressOrHostnameAsRangeValidator.cs b/Source/NETworkManager.Validators/IPAddressOrHostnameAsRangeValidator.cs
index 8392c450dc..ffb76cbb75 100644
--- a/Source/NETworkManager.Validators/IPAddressOrHostnameAsRangeValidator.cs
+++ b/Source/NETworkManager.Validators/IPAddressOrHostnameAsRangeValidator.cs
@@ -20,7 +20,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
var localItem = item.Trim();
// Check if it is a valid IPv4 address like 192.168.0.1, a valid IPv6 address like ::1 or a valid hostname like server-01 or server-01.example.com
- var isValid = Regex.IsMatch(localItem, RegexHelper.IPv4AddressRegex) ||
+ var isValid = RegexHelper.IPv4AddressRegex().IsMatch(localItem) ||
Regex.IsMatch(localItem, RegexHelper.IPv6AddressRegex) ||
Regex.IsMatch(localItem, RegexHelper.HostnameOrDomainRegex);
diff --git a/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs b/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs
index 3aa1980789..3126b87a16 100644
--- a/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs
+++ b/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs
@@ -16,7 +16,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
return new ValidationResult(false, Strings.EnterValidHostnameOrIPAddress);
// Check if it is a valid IPv4 address like 192.168.0.1
- if (Regex.IsMatch(input, RegexHelper.IPv4AddressRegex))
+ if (RegexHelper.IPv4AddressRegex().IsMatch(input))
return ValidationResult.ValidResult;
// Check if it is a valid IPv6 address like ::1
diff --git a/Source/NETworkManager.Validators/IPv4AddressValidator.cs b/Source/NETworkManager.Validators/IPv4AddressValidator.cs
index 9149f54977..2ed8c3060a 100644
--- a/Source/NETworkManager.Validators/IPv4AddressValidator.cs
+++ b/Source/NETworkManager.Validators/IPv4AddressValidator.cs
@@ -1,8 +1,7 @@
-using System.Globalization;
-using System.Text.RegularExpressions;
-using System.Windows.Controls;
-using NETworkManager.Localization.Resources;
+using NETworkManager.Localization.Resources;
using NETworkManager.Utilities;
+using System.Globalization;
+using System.Windows.Controls;
namespace NETworkManager.Validators;
@@ -15,7 +14,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
if (string.IsNullOrEmpty(ipAddress))
return new ValidationResult(false, Strings.EnterValidIPv4Address);
- return Regex.IsMatch(ipAddress, RegexHelper.IPv4AddressRegex)
+ return RegexHelper.IPv4AddressRegex().IsMatch(ipAddress)
? ValidationResult.ValidResult
: new ValidationResult(false, Strings.EnterValidIPv4Address);
}
diff --git a/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs b/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs
index aa6f4bba96..5b3bdf9bc3 100644
--- a/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs
+++ b/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs
@@ -1,10 +1,10 @@
-using System.Globalization;
+using NETworkManager.Localization.Resources;
+using NETworkManager.Models.Network;
+using NETworkManager.Utilities;
+using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
using System.Windows.Controls;
-using NETworkManager.Localization.Resources;
-using NETworkManager.Models.Network;
-using NETworkManager.Utilities;
namespace NETworkManager.Validators;
@@ -20,7 +20,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
foreach (var ipHostOrRange in ((string)value).Replace(" ", "").Split(';'))
{
// 192.168.0.1
- if (Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressRegex))
+ if (RegexHelper.IPv4AddressRegex().IsMatch(ipHostOrRange))
continue;
// 192.168.0.0/24
@@ -32,7 +32,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
continue;
// 192.168.0.0 - 192.168.0.100
- if (Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressRangeRegex))
+ if (RegexHelper.IPv4AddressRangeRegex().IsMatch(ipHostOrRange))
{
var range = ipHostOrRange.Split('-');
diff --git a/Source/NETworkManager.Validators/MultipleIPAddressesValidator.cs b/Source/NETworkManager.Validators/MultipleIPAddressesValidator.cs
index 9089606473..ed0d1a3d83 100644
--- a/Source/NETworkManager.Validators/MultipleIPAddressesValidator.cs
+++ b/Source/NETworkManager.Validators/MultipleIPAddressesValidator.cs
@@ -1,8 +1,8 @@
-using System.Globalization;
+using NETworkManager.Localization.Resources;
+using NETworkManager.Utilities;
+using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
-using NETworkManager.Localization.Resources;
-using NETworkManager.Utilities;
namespace NETworkManager.Validators;
@@ -15,13 +15,13 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
for (var index = 0; index < ((string)value).Split(';').Length; index++)
{
- var ipAddress = ((string)value).Split(';')[index];
+ var ipAddress = ((string)value).Split(';')[index].Trim();
- if (!Regex.IsMatch(ipAddress.Trim(), RegexHelper.IPv4AddressRegex) &&
+ if (!RegexHelper.IPv4AddressRegex().IsMatch(ipAddress) &&
!Regex.IsMatch(ipAddress.Trim(), RegexHelper.IPv6AddressRegex))
return new ValidationResult(false, Strings.EnterOneOrMoreValidIPAddresses);
}
return ValidationResult.ValidResult;
}
-}
\ No newline at end of file
+}
diff --git a/Source/NETworkManager.Validators/ServerValidator.cs b/Source/NETworkManager.Validators/ServerValidator.cs
index bd82d4f88c..ca2d381cd3 100644
--- a/Source/NETworkManager.Validators/ServerValidator.cs
+++ b/Source/NETworkManager.Validators/ServerValidator.cs
@@ -6,7 +6,7 @@
namespace NETworkManager.Validators;
-public class ServerValidator : ValidationRule
+public partial class ServerValidator : ValidationRule
{
public ServerDependencyObjectWrapper Wrapper { get; set; }
@@ -22,7 +22,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
return new ValidationResult(false, genericErrorResult);
// Check if it is a valid IPv4 address like 192.168.0.1
- if (Regex.IsMatch(input, RegexHelper.IPv4AddressRegex))
+ if (RegexHelper.IPv4AddressRegex().IsMatch(input))
return ValidationResult.ValidResult;
// Check if it is a valid IPv6 address like ::1
diff --git a/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs b/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs
index 50cee9098d..5a0dc1e3bc 100644
--- a/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs
+++ b/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs
@@ -1071,7 +1071,7 @@ private Task CheckConnectionInternetAsync(CancellationToken ct)
var result = await httpResponse.Content.ReadAsStringAsync(ct);
- var match = Regex.Match(result, RegexHelper.IPv4AddressExtractRegex);
+ var match = RegexHelper.IPv4AddressExtractRegex().Match(result);
if (match.Success)
{
diff --git a/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml b/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml
index 9333725433..db26ad245e 100644
--- a/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml
+++ b/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml
@@ -198,4 +198,4 @@
Style="{StaticResource DefaultButton}" />
-
\ No newline at end of file
+
From eddba620ea6c7a9a11b5ddeba7820da6c7ad1b93 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Wed, 10 Dec 2025 23:55:37 +0100
Subject: [PATCH 05/11] Chore: Migrate regex to generatedRegex
---
...dateSubnetCalculatorSubnettingConverter.cs | 3 +-
.../Network/HostRangeHelper.cs | 6 +-
.../NETworkManager.Utilities/RegexHelper.cs | 87 +++++++++++++------
.../IPv4IPv6SubnetValidator.cs | 4 +-
.../IPv4IPv6SubnetmaskOrCIDRValidator.cs | 3 +-
.../IPv4SubnetValidator.cs | 5 +-
.../IPv4SubnetmaskOrCIDRValidator.cs | 3 +-
.../MultipleHostsRangeValidator.cs | 6 +-
.../SubnetmaskValidator.cs | 3 +-
.../SubnetCalculatorSubnettingViewModel.cs | 3 +-
Website/docs/changelog/next-release.md | 1 +
11 files changed, 76 insertions(+), 48 deletions(-)
diff --git a/Source/NETworkManager.Converters/ValidateSubnetCalculatorSubnettingConverter.cs b/Source/NETworkManager.Converters/ValidateSubnetCalculatorSubnettingConverter.cs
index 3e97213917..3b5eccf929 100644
--- a/Source/NETworkManager.Converters/ValidateSubnetCalculatorSubnettingConverter.cs
+++ b/Source/NETworkManager.Converters/ValidateSubnetCalculatorSubnettingConverter.cs
@@ -2,7 +2,6 @@
using System.Globalization;
using System.Net;
using System.Net.Sockets;
-using System.Text.RegularExpressions;
using System.Windows.Data;
using NETworkManager.Models.Network;
using NETworkManager.Utilities;
@@ -40,7 +39,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
};
// Support subnetmask like 255.255.255.0
- int newCidr = Regex.IsMatch(newSubnetmaskOrCidr, RegexHelper.SubnetmaskRegex)
+ int newCidr = RegexHelper.SubnetmaskRegex().IsMatch(newSubnetmaskOrCidr)
? System.Convert.ToByte(Subnetmask.ConvertSubnetmaskToCidr(IPAddress.Parse(newSubnetmaskOrCidr)))
: System.Convert.ToByte(newSubnetmaskOrCidr.TrimStart('/'));
diff --git a/Source/NETworkManager.Models/Network/HostRangeHelper.cs b/Source/NETworkManager.Models/Network/HostRangeHelper.cs
index 37928f8368..e7e05922c6 100644
--- a/Source/NETworkManager.Models/Network/HostRangeHelper.cs
+++ b/Source/NETworkManager.Models/Network/HostRangeHelper.cs
@@ -55,9 +55,9 @@ private static (List<(IPAddress ipAddress, string hostname)> hosts, List
break;
// 192.168.0.0/24
- case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressCidrRegex):
+ case var _ when RegexHelper.IPv4AddressCidrRegex().IsMatch(host):
// 192.168.0.0/255.255.255.0
- case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressSubnetmaskRegex):
+ case var _ when RegexHelper.IPv4AddressSubnetmaskRegex().IsMatch(host):
var network = IPNetwork2.Parse(host);
Parallel.For(IPv4Address.ToInt32(network.Network), IPv4Address.ToInt32(network.Broadcast) + 1,
@@ -87,7 +87,7 @@ private static (List<(IPAddress ipAddress, string hostname)> hosts, List
break;
// 192.168.[50-100].1
- case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressSpecialRangeRegex):
+ case var _ when RegexHelper.IPv4AddressSpecialRangeRegex().IsMatch(host):
var octets = host.Split('.');
var list = new List>();
diff --git a/Source/NETworkManager.Utilities/RegexHelper.cs b/Source/NETworkManager.Utilities/RegexHelper.cs
index ba09fd7ce2..d9d48ee014 100644
--- a/Source/NETworkManager.Utilities/RegexHelper.cs
+++ b/Source/NETworkManager.Utilities/RegexHelper.cs
@@ -5,12 +5,22 @@ namespace NETworkManager.Utilities;
public static partial class RegexHelper
{
///
- /// Match an IPv4-Address like 192.168.178.1
- ///
- // ReSharper disable once InconsistentNaming
+ /// Represents a regular expression pattern that matches valid IPv4 address values.
+ ///
private const string IPv4AddressValues =
@"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
+ ///
+ /// Represents a regular expression pattern that matches valid IPv4 subnet mask values.
+ ///
+ private const string SubnetmaskValues =
+ @"(((255\.){3}(255|254|252|248|240|224|192|128|0+))|((255\.){2}(255|254|252|248|240|224|192|128|0+)\.0)|((255\.)(255|254|252|248|240|224|192|128|0+)(\.0+){2})|((255|254|252|248|240|224|192|128|0+)(\.0+){3}))";
+
+ ///
+ /// Represents the regular expression pattern used to validate CIDR notation values for IPv4 subnet masks.
+ ///
+ private const string CidrRegexValues = @"([1-9]|[1-2][0-9]|3[0-2])";
+
///
/// Provides a compiled regular expression that matches valid IPv4 addresses in dot-decimal notation.
///
@@ -43,6 +53,51 @@ public static partial class RegexHelper
[GeneratedRegex($"^{IPv4AddressValues}-{IPv4AddressValues}$")]
public static partial Regex IPv4AddressRangeRegex();
+ ///
+ /// Provides a compiled regular expression that matches valid IPv4 subnet mask values.
+ ///
+ /// The returned regular expression is generated at compile time and is optimized for
+ /// performance. Use this regex to validate or parse subnet mask strings in IPv4 networking scenarios.
+ /// A instance that matches strings representing valid IPv4 subnet masks.
+ [GeneratedRegex($"^{SubnetmaskValues}$")]
+ public static partial Regex SubnetmaskRegex();
+
+ ///
+ /// Provides a compiled regular expression that matches IPv4 addresses with subnet masks in CIDR notation, such as
+ /// "192.168.178.0/255.255.255.0".
+ ///
+ /// The returned regular expression validates both the IPv4 address and the subnet mask
+ /// components. Use this regex to verify input strings representing IPv4 subnets in formats like
+ /// "address/mask".
+ /// A instance that matches strings containing an IPv4 address followed by a subnet mask,
+ /// separated by a forward slash.
+ [GeneratedRegex($@"^{IPv4AddressValues}\/{SubnetmaskValues}$")]
+ public static partial Regex IPv4AddressSubnetmaskRegex();
+
+ ///
+ /// Provides a compiled regular expression that matches an IPv4 address in CIDR notation, such as
+ /// "192.168.178.0/24".
+ ///
+ /// The returned regular expression can be used to validate or extract IPv4 addresses with CIDR
+ /// notation, such as "192.168.1.0/24". The pattern enforces correct formatting for both the address and the prefix
+ /// length.
+ /// A instance that matches strings containing an IPv4 address followed by a slash and a valid
+ /// CIDR prefix length.
+ [GeneratedRegex($@"^{IPv4AddressValues}\/{CidrRegexValues}$")]
+ public static partial Regex IPv4AddressCidrRegex();
+
+ ///
+ /// Creates a regular expression that matches IPv4 addresses, allowing for a special range in one or more octets.
+ ///
+ /// The returned regular expression matches standard IPv4 addresses and addresses where one or
+ /// more octets are defined by a custom range pattern. This is useful for validating or parsing addresses such as
+ /// "192.168.[50-100].1" where a range is specified in place of an octet. The format and behavior of the special
+ /// range are determined by the SpecialRangeRegex value.
+ /// A instance that matches IPv4 addresses with support for custom special ranges as defined by
+ /// SpecialRangeRegex.
+ [GeneratedRegex($@"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|{SpecialRangeRegex})\.){{3}}((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|{SpecialRangeRegex})$")]
+ public static partial Regex IPv4AddressSpecialRangeRegex();
+
// Match a MAC-Address 000000000000 00:00:00:00:00:00, 00-00-00-00-00-00-00 or 0000.0000.0000
public const string MACAddressRegex =
@"^^[A-Fa-f0-9]{12}$|^[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}$|^[A-Fa-f0-9]{4}.[A-Fa-f0-9]{4}.[A-Fa-f0-9]{4}$$";
@@ -50,24 +105,7 @@ public static partial class RegexHelper
// Match the first 3 bytes of a MAC-Address 000000, 00:00:00, 00-00-00
public const string MACAddressFirst3BytesRegex =
@"^[A-Fa-f0-9]{6}$|^[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}$|^[A-Fa-f0-9]{4}.[A-Fa-f0-9]{2}$";
-
- // Private subnetmask / cidr values
- private const string SubnetmaskValues =
- @"(((255\.){3}(255|254|252|248|240|224|192|128|0+))|((255\.){2}(255|254|252|248|240|224|192|128|0+)\.0)|((255\.)(255|254|252|248|240|224|192|128|0+)(\.0+){2})|((255|254|252|248|240|224|192|128|0+)(\.0+){3}))";
-
- private const string CidrRegex = @"([1-9]|[1-2][0-9]|3[0-2])";
-
- // Match a Subnetmask like 255.255.255.0
- public const string SubnetmaskRegex = @"^" + SubnetmaskValues + @"$";
-
- // Match a subnet from 192.168.178.0/1 to 192.168.178.0/32
- // ReSharper disable once InconsistentNaming
- public const string IPv4AddressCidrRegex = $@"^{IPv4AddressValues}\/{CidrRegex}$";
-
- // Match a subnet from 192.168.178.0/192.0.0.0 to 192.168.178.0/255.255.255.255
- // ReSharper disable once InconsistentNaming
- public const string IPv4AddressSubnetmaskRegex = $@"^{IPv4AddressValues}\/{SubnetmaskValues}$";
-
+
// Match IPv6 address like ::1
// ReSharper disable once InconsistentNaming
public const string IPv6AddressRegex =
@@ -82,11 +120,6 @@ public static partial class RegexHelper
public const string SpecialRangeRegex =
@"\[((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)-(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))([,]((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)-(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))))*\]";
- // Match a IPv4-Address like 192.168.[50-100].1
- // ReSharper disable once InconsistentNaming
- public const string IPv4AddressSpecialRangeRegex =
- $@"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|{SpecialRangeRegex})\.){{3}}((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|{SpecialRangeRegex})$";
-
// Private hostname values
private const string HostnameOrDomainValues =
@"(?=.{1,255}$)(?!-)[A-Za-z0-9-]{1,63}(?
Date: Thu, 11 Dec 2025 21:49:36 +0100
Subject: [PATCH 06/11] Docs: adjust regex comments
---
.../NETworkManager.Utilities/RegexHelper.cs | 55 ++++++-------------
1 file changed, 18 insertions(+), 37 deletions(-)
diff --git a/Source/NETworkManager.Utilities/RegexHelper.cs b/Source/NETworkManager.Utilities/RegexHelper.cs
index d9d48ee014..9f86895ac1 100644
--- a/Source/NETworkManager.Utilities/RegexHelper.cs
+++ b/Source/NETworkManager.Utilities/RegexHelper.cs
@@ -22,79 +22,60 @@ public static partial class RegexHelper
private const string CidrRegexValues = @"([1-9]|[1-2][0-9]|3[0-2])";
///
- /// Provides a compiled regular expression that matches valid IPv4 addresses in dot-decimal notation.
- ///
- /// The returned regular expression is compiled for performance. Use this regex to validate or
- /// extract IPv4 addresses from text. The pattern enforces correct octet ranges and dot separators.
+ /// Provides a compiled regular expression that matches valid IPv4 addresses in dot-decimal notation like "192.168.178.0".
+ ///
/// A instance that matches IPv4 addresses in the format "x.x.x.x", where each x is a number
/// from 0 to 255.
[GeneratedRegex($"^{IPv4AddressValues}$")]
public static partial Regex IPv4AddressRegex();
///
- /// Provides a compiled regular expression that matches valid IPv4 addresses within input text.
- ///
- /// The returned regular expression matches IPv4 addresses in standard dotted-decimal notation
- /// (e.g., "192.168.1.1"). The regular expression is compiled for improved performance when used
- /// repeatedly.
+ /// Provides a compiled regular expression that matches valid IPv4 addresses within input text like "192.168.178.0".
+ ///
/// A instance that can be used to extract IPv4 addresses from strings.
[GeneratedRegex(IPv4AddressValues)]
public static partial Regex IPv4AddressExtractRegex();
///
- /// Gets a regular expression that matches an IPv4 address range in the format "start-end", where both start and end
- /// are valid IPv4 addresses.
- ///
- /// The regular expression expects the input to consist of two IPv4 addresses separated by a
- /// hyphen, with no additional whitespace or characters. Both addresses must be valid IPv4 addresses. This can be
- /// used to validate or parse address range strings in network configuration scenarios.
+ /// Provides a compiles regular expression that matches IPv4 address ranges in the format "start-end" like
+ /// "192.168.178.0-192.168.178.255".
+ ///
/// A instance that matches strings representing IPv4 address ranges, such as
/// "192.168.1.1-192.168.1.100".
[GeneratedRegex($"^{IPv4AddressValues}-{IPv4AddressValues}$")]
public static partial Regex IPv4AddressRangeRegex();
///
- /// Provides a compiled regular expression that matches valid IPv4 subnet mask values.
- ///
- /// The returned regular expression is generated at compile time and is optimized for
- /// performance. Use this regex to validate or parse subnet mask strings in IPv4 networking scenarios.
+ /// Provides a compiled regular expression that matches valid IPv4 subnet mask like "255.255.0.0".
+ ///
/// A instance that matches strings representing valid IPv4 subnet masks.
[GeneratedRegex($"^{SubnetmaskValues}$")]
public static partial Regex SubnetmaskRegex();
///
- /// Provides a compiled regular expression that matches IPv4 addresses with subnet masks in CIDR notation, such as
+ /// Provides a compiled regular expression that matches IPv4 addresses with subnet masks like
/// "192.168.178.0/255.255.255.0".
- ///
- /// The returned regular expression validates both the IPv4 address and the subnet mask
- /// components. Use this regex to verify input strings representing IPv4 subnets in formats like
- /// "address/mask".
+ ///
/// A instance that matches strings containing an IPv4 address followed by a subnet mask,
/// separated by a forward slash.
[GeneratedRegex($@"^{IPv4AddressValues}\/{SubnetmaskValues}$")]
public static partial Regex IPv4AddressSubnetmaskRegex();
///
- /// Provides a compiled regular expression that matches an IPv4 address in CIDR notation, such as
+ /// Provides a compiled regular expression that matches an IPv4 address with CIDR like
/// "192.168.178.0/24".
///
- /// The returned regular expression can be used to validate or extract IPv4 addresses with CIDR
- /// notation, such as "192.168.1.0/24". The pattern enforces correct formatting for both the address and the prefix
- /// length.
/// A instance that matches strings containing an IPv4 address followed by a slash and a valid
/// CIDR prefix length.
[GeneratedRegex($@"^{IPv4AddressValues}\/{CidrRegexValues}$")]
public static partial Regex IPv4AddressCidrRegex();
-
+
///
- /// Creates a regular expression that matches IPv4 addresses, allowing for a special range in one or more octets.
- ///
- /// The returned regular expression matches standard IPv4 addresses and addresses where one or
- /// more octets are defined by a custom range pattern. This is useful for validating or parsing addresses such as
- /// "192.168.[50-100].1" where a range is specified in place of an octet. The format and behavior of the special
- /// range are determined by the SpecialRangeRegex value.
- /// A instance that matches IPv4 addresses with support for custom special ranges as defined by
- /// SpecialRangeRegex.
+ /// Creates a regular expression that matches IPv4 addresses, allowing for a special range in one or more octets like
+ /// "192.168.[0-50].1".
+ ///
+ /// A instance that matches IPv4 addresses with support for custom special ranges like
+ /// "192.168.[0-50].1".
[GeneratedRegex($@"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|{SpecialRangeRegex})\.){{3}}((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|{SpecialRangeRegex})$")]
public static partial Regex IPv4AddressSpecialRangeRegex();
From c8d1e1640888d4d04efe2f9b0cb1317bb35070b3 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Thu, 11 Dec 2025 21:49:58 +0100
Subject: [PATCH 07/11] Design: Migrate dns server to child window
---
.../ViewModels/DNSLookupSettingsViewModel.cs | 74 ++++---
...erverConnectionInfoProfileChildWindow.xaml | 206 ++++++++++++++++++
...erConnectionInfoProfileChildWindow.xaml.cs | 29 +++
3 files changed, 275 insertions(+), 34 deletions(-)
create mode 100644 Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml
create mode 100644 Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml.cs
diff --git a/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs
index 6cb9a0f035..be62bedbcf 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs
@@ -367,7 +367,7 @@ private void LoadSettings()
}
#endregion
-
+
#region ICommand & Actions
///
@@ -418,27 +418,30 @@ private void DeleteDNSServerAction()
///
private async Task AddDNSServer()
{
- var customDialog = new CustomDialog
- {
- Title = Strings.AddDNSServer
- };
+ var childWindow = new ServerConnectionInfoProfileChildWindow();
- var viewModel = new ServerConnectionInfoProfileViewModel(instance =>
- {
- _dialogCoordinator.HideMetroDialogAsync(this, customDialog);
+ var childWindowViewModel = new ServerConnectionInfoProfileViewModel(instance =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
- SettingsManager.Current.DNSLookup_DNSServers.Add(
- new DNSServerConnectionInfoProfile(instance.Name, instance.Servers.ToList()));
- }, _ => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); },
- (ServerInfoProfileNames, false, true),
+ SettingsManager.Current.DNSLookup_DNSServers.Add(
+ new DNSServerConnectionInfoProfile(instance.Name, [.. instance.Servers]));
+ }, _ =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ },
+ (ServerInfoProfileNames, false, false),
_profileDialogDefaultValues);
- customDialog.Content = new ServerConnectionInfoProfileDialog
- {
- DataContext = viewModel
- };
+ childWindow.Title = Strings.AddDNSServer;
+
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
- await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog);
+ await (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow);
}
///
@@ -446,28 +449,31 @@ private async Task AddDNSServer()
///
public async Task EditDNSServer()
{
- var customDialog = new CustomDialog
- {
- Title = Strings.EditDNSServer
- };
+ var childWindow = new ServerConnectionInfoProfileChildWindow();
- var viewModel = new ServerConnectionInfoProfileViewModel(instance =>
- {
- _dialogCoordinator.HideMetroDialogAsync(this, customDialog);
+ var childWindowViewModel = new ServerConnectionInfoProfileViewModel(instance =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
- SettingsManager.Current.DNSLookup_DNSServers.Remove(SelectedDNSServer);
- SettingsManager.Current.DNSLookup_DNSServers.Add(
- new DNSServerConnectionInfoProfile(instance.Name, instance.Servers.ToList()));
- }, _ => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); },
- (ServerInfoProfileNames, true, true),
+ SettingsManager.Current.DNSLookup_DNSServers.Remove(SelectedDNSServer);
+ SettingsManager.Current.DNSLookup_DNSServers.Add(
+ new DNSServerConnectionInfoProfile(instance.Name, [.. instance.Servers]));
+ }, _ =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ },
+ (ServerInfoProfileNames, true, false),
_profileDialogDefaultValues, SelectedDNSServer);
- customDialog.Content = new ServerConnectionInfoProfileDialog
- {
- DataContext = viewModel
- };
+ childWindow.Title = Strings.EditDNSServer;
+
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
- await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog);
+ await (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow);
}
///
diff --git a/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml b/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml
new file mode 100644
index 0000000000..8e2cadf357
--- /dev/null
+++ b/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml.cs b/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml.cs
new file mode 100644
index 0000000000..cc61fbd0b5
--- /dev/null
+++ b/Source/NETworkManager/Views/ServerConnectionInfoProfileChildWindow.xaml.cs
@@ -0,0 +1,29 @@
+using NETworkManager.ViewModels;
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Threading;
+
+namespace NETworkManager.Views;
+
+public partial class ServerConnectionInfoProfileChildWindow
+{
+ public ServerConnectionInfoProfileChildWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void ChildWindow_OnLoaded(object sender, RoutedEventArgs e)
+ {
+ Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(delegate
+ {
+ TextBoxName.Focus();
+ }));
+ }
+
+ private void ContextMenu_Opened(object sender, RoutedEventArgs e)
+ {
+ if (sender is ContextMenu menu)
+ menu.DataContext = (ServerConnectionInfoProfileViewModel)DataContext;
+ }
+}
\ No newline at end of file
From 86c78776baf6ca34b0b9fdb5da596a3ca7dc2501 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Thu, 11 Dec 2025 22:00:40 +0100
Subject: [PATCH 08/11] Design: Migrate SNTP dialog
---
.../ViewModels/SNTPLookupSettingsViewModel.cs | 73 ++++++++++---------
Website/docs/changelog/next-release.md | 16 +++-
2 files changed, 55 insertions(+), 34 deletions(-)
diff --git a/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs
index 09ac160119..70eae4e110 100644
--- a/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs
@@ -137,52 +137,59 @@ private void DeleteServerAction()
private async Task AddServer()
{
- var customDialog = new CustomDialog
- {
- Title = Strings.AddSNTPServer
- };
+ var childWindow = new ServerConnectionInfoProfileChildWindow();
- var viewModel = new ServerConnectionInfoProfileViewModel(instance =>
- {
- _dialogCoordinator.HideMetroDialogAsync(this, customDialog);
+ var childWindowViewModel = new ServerConnectionInfoProfileViewModel(instance =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
- SettingsManager.Current.SNTPLookup_SNTPServers.Add(
- new ServerConnectionInfoProfile(instance.Name, instance.Servers.ToList()));
- }, _ => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); },
+ SettingsManager.Current.SNTPLookup_SNTPServers.Add(
+ new ServerConnectionInfoProfile(instance.Name, [.. instance.Servers]));
+ }, _ =>
+ {
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ },
(ServerInfoProfileNames, false, false), _profileDialogDefaultValues);
- customDialog.Content = new ServerConnectionInfoProfileDialog
- {
- DataContext = viewModel
- };
- await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog);
+ childWindow.Title = Strings.AddSNTPServer;
+
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
+
+ await (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow);
}
public async Task EditServer()
{
- var customDialog = new CustomDialog
+ var childWindow = new ServerConnectionInfoProfileChildWindow();
+
+ var childWindowViewModel = new ServerConnectionInfoProfileViewModel(instance =>
{
- Title = Strings.EditSNTPServer
- };
-
- var viewModel = new ServerConnectionInfoProfileViewModel(instance =>
- {
- _dialogCoordinator.HideMetroDialogAsync(this, customDialog);
-
- SettingsManager.Current.SNTPLookup_SNTPServers.Remove(SelectedSNTPServer);
- SettingsManager.Current.SNTPLookup_SNTPServers.Add(
- new ServerConnectionInfoProfile(instance.Name, instance.Servers.ToList()));
- }, _ => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); },
- (ServerInfoProfileNames, true, false),
- _profileDialogDefaultValues, SelectedSNTPServer);
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
- customDialog.Content = new ServerConnectionInfoProfileDialog
+ SettingsManager.Current.SNTPLookup_SNTPServers.Remove(SelectedSNTPServer);
+ SettingsManager.Current.SNTPLookup_SNTPServers.Add(
+ new ServerConnectionInfoProfile(instance.Name, [.. instance.Servers]));
+ }, _ =>
{
- DataContext = viewModel
- };
+ childWindow.IsOpen = false;
+ ConfigurationManager.Current.IsChildWindowOpen = false;
+ },
+ (ServerInfoProfileNames, true, false),
+ _profileDialogDefaultValues, SelectedSNTPServer);
+
+ childWindow.Title = Strings.EditSNTPServer;
+
+ childWindow.DataContext = childWindowViewModel;
+
+ ConfigurationManager.Current.IsChildWindowOpen = true;
- await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog);
+ await (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow);
}
private async Task DeleteServer()
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 625eedbf6e..889b092671 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -33,6 +33,11 @@ Release date: **xx.xx.2025**
- New language Ukrainian (`uk-UA`) has been added. Thanks to [@vadickkt](https://github.com/vadickkt) [#3240](https://github.com/BornToBeRoot/NETworkManager/pull/3240)
+**DNS Lookup**
+
+- Allow direct dns server input (`|:` - `` is optional) in the lookup view in addition to select from a list of configured servers. [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)
+ See the [DNS Lookup](../application/dns-lookup.md) documentation for more information.
+
**Remote Desktop**
- Flag to enable `Admin (console) session` added to the RDP connect, profile and group dialogs. This flag allows connecting to the console session of the remote computer. [#3216](https://github.com/BornToBeRoot/NETworkManager/pull/3216)
@@ -45,6 +50,11 @@ Release date: **xx.xx.2025**
- Profile file dialog migrated to a child window to improve usability. [#3227](https://github.com/BornToBeRoot/NETworkManager/pull/3227)
- Credential dialogs migrated to child windows to improve usability. [#3231](https://github.com/BornToBeRoot/NETworkManager/pull/3231)
+**DNS Lookup**
+
+- Allow hostname as server address in addition to IP address in the add/edit server dialog. [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)
+- Redesign add/edit server dialog (migrated from dialog to child window). [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)
+
**Remote Desktop**
- Redesign RDP connect dialog (migrated from dialog to child window). [#3216](https://github.com/BornToBeRoot/NETworkManager/pull/3216)
@@ -61,6 +71,10 @@ Release date: **xx.xx.2025**
- Redesign Web Console connect dialog (migrated from dialog to child window). [#3234](https://github.com/BornToBeRoot/NETworkManager/pull/3234)
+**SNTP Lookup**
+
+- Redesign add/edit server dialog (migrated from dialog to child window). [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)
+
## Bug Fixes
**PowerShell**
@@ -77,7 +91,7 @@ Release date: **xx.xx.2025**
- Documentation updated
- Code cleanup & refactoring
- Introduced a new DialogHelper utility to centralize creation of `OK` and `OK/Cancel` dialogs. This reduces duplication and enforces a consistent layout. [#3231](https://github.com/BornToBeRoot/NETworkManager/pull/3231)
- - Migrate RegexHelper to precompiled Regex patterns for improved performance. [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)
+ - Migrate RegexHelper to precompiled Regex patterns for improved performance. [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)
- Migrated existing credential and profile dialogs to use DialogHelper, improving usability, accessibility and maintainability across the app. [#3231](https://github.com/BornToBeRoot/NETworkManager/pull/3231)
- Language files updated via [#transifex](https://github.com/BornToBeRoot/NETworkManager/pulls?q=author%3Aapp%2Ftransifex-integration)
- Dependencies updated via [#dependabot](https://github.com/BornToBeRoot/NETworkManager/pulls?q=author%3Aapp%2Fdependabot)
From bf1b01cdb8fff572c9f63c84e7a47781fa2747a9 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sat, 13 Dec 2025 19:36:38 +0100
Subject: [PATCH 09/11] Feature: Parse input and profile server field
---
.../Network/HostRangeHelper.cs | 5 +-
.../Network/ServerConnectionInfo.cs | 15 ++
.../Network/ServerConnectionInfoProfile.cs | 1 +
.../NETworkManager.Utilities/RegexHelper.cs | 23 +-
.../EmptyOrWindowsDomainValidator.cs | 3 +-
.../HostsFileEntryHostnameValidator.cs | 3 +-
.../IPAddressOrHostnameAsRangeValidator.cs | 9 +-
.../IPAddressOrHostnameValidator.cs | 9 +-
.../MultipleHostsRangeValidator.cs | 3 +-
.../RemoteDesktopHostnameAndPortValidator.cs | 11 +-
.../ServerValidator.cs | 4 +-
.../ViewModels/DNSLookupViewModel.cs | 209 +++++++++++++++---
.../ServerConnectionInfoProfileDialog.xaml | 201 -----------------
.../ServerConnectionInfoProfileDialog.xaml.cs | 24 --
14 files changed, 233 insertions(+), 287 deletions(-)
delete mode 100644 Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml
delete mode 100644 Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml.cs
diff --git a/Source/NETworkManager.Models/Network/HostRangeHelper.cs b/Source/NETworkManager.Models/Network/HostRangeHelper.cs
index e7e05922c6..43ccdc505b 100644
--- a/Source/NETworkManager.Models/Network/HostRangeHelper.cs
+++ b/Source/NETworkManager.Models/Network/HostRangeHelper.cs
@@ -1,5 +1,4 @@
-using ControlzEx.Standard;
-using NETworkManager.Utilities;
+using NETworkManager.Utilities;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -148,7 +147,7 @@ private static (List<(IPAddress ipAddress, string hostname)> hosts, List
break;
// example.com
- case var _ when Regex.IsMatch(host, RegexHelper.HostnameOrDomainRegex):
+ case var _ when RegexHelper.HostnameOrDomainRegex().IsMatch(host):
using (var dnsResolverTask =
DNSClientHelper.ResolveAorAaaaAsync(host, dnsResolveHostnamePreferIPv4))
{
diff --git a/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs b/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs
index b36491c7b3..3250526227 100644
--- a/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs
+++ b/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs
@@ -10,6 +10,7 @@ public class ServerConnectionInfo
///
public ServerConnectionInfo()
{
+
}
///
@@ -52,6 +53,20 @@ public ServerConnectionInfo(string server, int port, TransportProtocol transport
///
public TransportProtocol TransportProtocol { get; set; }
+ // public static ServerConnectionInfo Parse (string input)
+ // {
+
+
+ // return null;
+ // }
+
+ //public static bool TryParse (string input, out ServerConnectionInfo serverConnectionInfo)
+ // {
+ // serverConnectionInfo = null;
+
+ // return false;
+ // }
+
///
/// Returns a string that represents the current object.
///
diff --git a/Source/NETworkManager.Models/Network/ServerConnectionInfoProfile.cs b/Source/NETworkManager.Models/Network/ServerConnectionInfoProfile.cs
index 3c9afed439..a50f03ddbc 100644
--- a/Source/NETworkManager.Models/Network/ServerConnectionInfoProfile.cs
+++ b/Source/NETworkManager.Models/Network/ServerConnectionInfoProfile.cs
@@ -12,6 +12,7 @@ public class ServerConnectionInfoProfile
///
public ServerConnectionInfoProfile()
{
+
}
///
diff --git a/Source/NETworkManager.Utilities/RegexHelper.cs b/Source/NETworkManager.Utilities/RegexHelper.cs
index 9f86895ac1..72b621228b 100644
--- a/Source/NETworkManager.Utilities/RegexHelper.cs
+++ b/Source/NETworkManager.Utilities/RegexHelper.cs
@@ -20,7 +20,13 @@ public static partial class RegexHelper
/// Represents the regular expression pattern used to validate CIDR notation values for IPv4 subnet masks.
///
private const string CidrRegexValues = @"([1-9]|[1-2][0-9]|3[0-2])";
-
+
+ ///
+ /// Represents a regular expression pattern that matches valid hostnames or fully qualified domain names (FQDNs).
+ ///
+ private const string HostnameOrDomainValues =
+ @"(?=.{1,255}$)(?!-)[A-Za-z0-9-]{1,63}(?
/// Provides a compiled regular expression that matches valid IPv4 addresses in dot-decimal notation like "192.168.178.0".
///
@@ -71,7 +77,7 @@ public static partial class RegexHelper
public static partial Regex IPv4AddressCidrRegex();
///
- /// Creates a regular expression that matches IPv4 addresses, allowing for a special range in one or more octets like
+ /// Provides a compiled regular expression that matches IPv4 addresses, allowing for a special range in one or more octets like
/// "192.168.[0-50].1".
///
/// A instance that matches IPv4 addresses with support for custom special ranges like
@@ -101,12 +107,15 @@ public static partial class RegexHelper
public const string SpecialRangeRegex =
@"\[((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)-(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))([,]((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)-(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))))*\]";
- // Private hostname values
- private const string HostnameOrDomainValues =
- @"(?=.{1,255}$)(?!-)[A-Za-z0-9-]{1,63}(?
+ /// Provides a compiled regular expression that matches valid hostnames or fully qualified domain names (FQDNs) like
+ /// server-01 or server-01.example.com.
+ ///
+ /// A instance that matches valid hostnames or FQDNs.
+ [GeneratedRegex($@"^{HostnameOrDomainValues}$")]
+ public static partial Regex HostnameOrDomainRegex();
// Match a hostname with cidr like server-01.example.com/24
public const string HostnameOrDomainWithCidrRegex = $@"^{HostnameOrDomainValues}\/{CidrRegexValues}$";
diff --git a/Source/NETworkManager.Validators/EmptyOrWindowsDomainValidator.cs b/Source/NETworkManager.Validators/EmptyOrWindowsDomainValidator.cs
index 380736f27d..33217399a2 100644
--- a/Source/NETworkManager.Validators/EmptyOrWindowsDomainValidator.cs
+++ b/Source/NETworkManager.Validators/EmptyOrWindowsDomainValidator.cs
@@ -1,5 +1,4 @@
using System.Globalization;
-using System.Text.RegularExpressions;
using System.Windows.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Utilities;
@@ -19,7 +18,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
if (domain.Equals("."))
return ValidationResult.ValidResult;
- return Regex.IsMatch(domain, RegexHelper.HostnameOrDomainRegex)
+ return RegexHelper.HostnameOrDomainRegex().IsMatch(domain)
? ValidationResult.ValidResult
: new ValidationResult(false, Strings.EnterValidDomain);
}
diff --git a/Source/NETworkManager.Validators/HostsFileEntryHostnameValidator.cs b/Source/NETworkManager.Validators/HostsFileEntryHostnameValidator.cs
index f8e1ab397f..d99c9700bf 100644
--- a/Source/NETworkManager.Validators/HostsFileEntryHostnameValidator.cs
+++ b/Source/NETworkManager.Validators/HostsFileEntryHostnameValidator.cs
@@ -2,7 +2,6 @@
using NETworkManager.Utilities;
using System.Diagnostics;
using System.Globalization;
-using System.Text.RegularExpressions;
using System.Windows.Controls;
namespace NETworkManager.Validators;
@@ -19,7 +18,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
foreach (var hostname in input.Split(' '))
{
- if (Regex.IsMatch(hostname, RegexHelper.HostnameOrDomainRegex) == false)
+ if (RegexHelper.HostnameOrDomainRegex().IsMatch(hostname) == false)
{
isValid = false;
break;
diff --git a/Source/NETworkManager.Validators/IPAddressOrHostnameAsRangeValidator.cs b/Source/NETworkManager.Validators/IPAddressOrHostnameAsRangeValidator.cs
index ffb76cbb75..86b9efd9a6 100644
--- a/Source/NETworkManager.Validators/IPAddressOrHostnameAsRangeValidator.cs
+++ b/Source/NETworkManager.Validators/IPAddressOrHostnameAsRangeValidator.cs
@@ -1,8 +1,9 @@
-using System.Globalization;
+using NETworkManager.Localization.Resources;
+using NETworkManager.Utilities;
+using System.DirectoryServices.ActiveDirectory;
+using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
-using NETworkManager.Localization.Resources;
-using NETworkManager.Utilities;
namespace NETworkManager.Validators;
@@ -22,7 +23,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
// Check if it is a valid IPv4 address like 192.168.0.1, a valid IPv6 address like ::1 or a valid hostname like server-01 or server-01.example.com
var isValid = RegexHelper.IPv4AddressRegex().IsMatch(localItem) ||
Regex.IsMatch(localItem, RegexHelper.IPv6AddressRegex) ||
- Regex.IsMatch(localItem, RegexHelper.HostnameOrDomainRegex);
+ RegexHelper.HostnameOrDomainRegex().IsMatch(localItem);
if (!isValid)
return new ValidationResult(false, Strings.EnterValidHostnameOrIPAddress);
diff --git a/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs b/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs
index 3126b87a16..f2c064101f 100644
--- a/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs
+++ b/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs
@@ -1,8 +1,9 @@
-using System.Globalization;
+using NETworkManager.Localization.Resources;
+using NETworkManager.Utilities;
+using System.DirectoryServices.ActiveDirectory;
+using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
-using NETworkManager.Localization.Resources;
-using NETworkManager.Utilities;
namespace NETworkManager.Validators;
@@ -24,7 +25,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
return ValidationResult.ValidResult;
// Check if it is a valid hostname like server-01 or server-01.example.com
- if (Regex.IsMatch(input, RegexHelper.HostnameOrDomainRegex))
+ if(RegexHelper.HostnameOrDomainRegex().IsMatch(input))
return ValidationResult.ValidResult;
return new ValidationResult(false, Strings.EnterValidHostnameOrIPAddress);
diff --git a/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs b/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs
index 4c4e7dc553..a2674ce8ae 100644
--- a/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs
+++ b/Source/NETworkManager.Validators/MultipleHostsRangeValidator.cs
@@ -1,6 +1,7 @@
using NETworkManager.Localization.Resources;
using NETworkManager.Models.Network;
using NETworkManager.Utilities;
+using System.DirectoryServices.ActiveDirectory;
using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
@@ -74,7 +75,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
continue;
// server-01.example.com
- if (Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameOrDomainRegex))
+ if (RegexHelper.HostnameOrDomainRegex().IsMatch(ipHostOrRange))
continue;
// server-01.example.com/24
diff --git a/Source/NETworkManager.Validators/RemoteDesktopHostnameAndPortValidator.cs b/Source/NETworkManager.Validators/RemoteDesktopHostnameAndPortValidator.cs
index 880de518fd..32b78db059 100644
--- a/Source/NETworkManager.Validators/RemoteDesktopHostnameAndPortValidator.cs
+++ b/Source/NETworkManager.Validators/RemoteDesktopHostnameAndPortValidator.cs
@@ -1,8 +1,9 @@
-using System.Globalization;
+using NETworkManager.Localization.Resources;
+using NETworkManager.Utilities;
+using System.DirectoryServices.ActiveDirectory;
+using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;
-using NETworkManager.Localization.Resources;
-using NETworkManager.Utilities;
namespace NETworkManager.Validators;
@@ -16,7 +17,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var hostnameAndPortValues = hostnameAndPort.Split(':');
- if (Regex.IsMatch(hostnameAndPortValues[0], RegexHelper.HostnameOrDomainRegex) &&
+ if (RegexHelper.HostnameOrDomainRegex().IsMatch(hostnameAndPortValues[0]) &&
!string.IsNullOrEmpty(hostnameAndPortValues[1]) &&
Regex.IsMatch(hostnameAndPortValues[1], RegexHelper.PortRegex))
return ValidationResult.ValidResult;
@@ -24,7 +25,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
return new ValidationResult(false, Strings.EnterValidHostnameAndPort);
}
- return Regex.IsMatch((string)value, RegexHelper.HostnameOrDomainRegex)
+ return RegexHelper.HostnameOrDomainRegex().IsMatch((string)value)
? ValidationResult.ValidResult
: new ValidationResult(false, Strings.EnterValidHostname);
}
diff --git a/Source/NETworkManager.Validators/ServerValidator.cs b/Source/NETworkManager.Validators/ServerValidator.cs
index ca2d381cd3..b029b62227 100644
--- a/Source/NETworkManager.Validators/ServerValidator.cs
+++ b/Source/NETworkManager.Validators/ServerValidator.cs
@@ -13,11 +13,13 @@ public partial class ServerValidator : ValidationRule
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var allowOnlyIPAddress = Wrapper.AllowOnlyIPAddress;
+
var genericErrorResult =
allowOnlyIPAddress ? Strings.EnterValidIPAddress : Strings.EnterValidHostnameOrIPAddress;
var input = (value as string)?.Trim();
+ // Empty input is considered invalid
if (string.IsNullOrEmpty(input))
return new ValidationResult(false, genericErrorResult);
@@ -30,7 +32,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
return ValidationResult.ValidResult;
// Check if it is a valid hostname like server-01 or server-01.example.com
- if (Regex.IsMatch(input, RegexHelper.HostnameOrDomainRegex) && !allowOnlyIPAddress)
+ if(RegexHelper.HostnameOrDomainRegex().IsMatch(input) && !allowOnlyIPAddress)
return ValidationResult.ValidResult;
return new ValidationResult(false, genericErrorResult);
diff --git a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
index ad963abb08..efa1a29923 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
@@ -15,6 +15,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
+using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
@@ -120,7 +121,7 @@ public DNSServerConnectionInfoProfile SelectedDNSServer
OnPropertyChanged();
}
}
-
+
///
/// Backing field for .
///
@@ -324,7 +325,7 @@ public DNSLookupViewModel(IDialogCoordinator instance, Guid tabId, string host)
ListSortDirection.Descending));
DNSServers.SortDescriptions.Add(new SortDescription(nameof(DNSServerConnectionInfoProfile.Name),
ListSortDirection.Ascending));
-
+
DNSServer = string.IsNullOrEmpty(SettingsManager.Current.DNSLookup_SelectedDNSServer_v2)
? SettingsManager.Current.DNSLookup_DNSServers.FirstOrDefault()?.Name
: SettingsManager.Current.DNSLookup_SelectedDNSServer_v2;
@@ -351,7 +352,7 @@ public void OnLoaded()
return;
if (!string.IsNullOrEmpty(Host))
- QueryAsync();
+ QueryAsync().ConfigureAwait(false);
_firstLoad = false;
}
@@ -411,7 +412,7 @@ private bool Query_CanExecute(object parameter)
private void QueryAction()
{
if (!IsRunning)
- QueryAsync();
+ QueryAsync().ConfigureAwait(false);
}
///
@@ -466,43 +467,174 @@ private async Task QueryAsync()
dnsSettings.CustomDNSSuffix = SettingsManager.Current.DNSLookup_CustomDNSSuffix?.TrimStart('.');
}
-
- DNSLookup dnsLookup;
-
// Try find existing dns server profile
var dnsServerProfile = SettingsManager.Current.DNSLookup_DNSServers
.FirstOrDefault(x => x.Name == DNSServer);
+ DNSLookup dnsLookup = null;
+
+ List dnsServersToResolve = [];
+
// Use profile if found
if (dnsServerProfile != null)
{
- dnsLookup = dnsServerProfile.UseWindowsDNSServer
- ? new DNSLookup(dnsSettings)
- : new DNSLookup(dnsSettings, dnsServerProfile.Servers);
+ // Use windows dns server
+ if (dnsServerProfile.UseWindowsDNSServer)
+ dnsLookup = new DNSLookup(dnsSettings);
+ // Use custom dns servers from profile
+ else
+ dnsServersToResolve = dnsServerProfile.Servers;
}
- // Otherwise try to parse custom server string
+ // Parse custom dns server from input
else
- {
- List customDNSServers = [];
+ {
+ foreach (var dnsServer in DNSServer.Split(';').Select(x => x.Trim()))
+ {
+ Debug.WriteLine("PARSE: " + dnsServer);
+
+ // [IPv6]:Port
+ if (dnsServer.StartsWith('[') && dnsServer.Contains("]:"))
+ {
+ var endIndex = dnsServer.IndexOf("]:", StringComparison.Ordinal);
+
+ var ipPart = dnsServer[1..endIndex];
+ var portPart = dnsServer[(endIndex + 2)..];
+
+ Debug.WriteLine($"IPV6 with port detected. IP: {ipPart}, Port: {portPart}");
+
+ // Validate IPv6 and port
+ if (IPAddress.TryParse(ipPart, out IPAddress ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6 &&
+ int.TryParse(portPart, out int port) && port > 0 && port <= 65535)
+ {
+ dnsServersToResolve.Add(new ServerConnectionInfo(ip.ToString(), port));
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(StatusMessage))
+ StatusMessage += Environment.NewLine;
+
+ StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
+ IsStatusMessageDisplayed = true;
+
+ continue;
+ }
+ }
+ // [IPv6]
+ else if (dnsServer.StartsWith('[') && dnsServer.EndsWith(']'))
+ {
+ var ipPart = dnsServer[1..^1];
+
+ Debug.WriteLine($"IPV6 without port detected. IP: {ipPart}");
+
+ // Validate IPv6
+ if (IPAddress.TryParse(ipPart, out IPAddress ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
+ {
+ dnsServersToResolve.Add(new ServerConnectionInfo(ip.ToString(), 53));
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(StatusMessage))
+ StatusMessage += Environment.NewLine;
+
+ StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
+ IsStatusMessageDisplayed = true;
+
+ continue;
+ }
+ }
+ // IPv6
+ else if (dnsServer.Count(c => c == ':') > 1)
+ {
+ Debug.WriteLine($"IPv6 without port detected. IP: {dnsServer}");
+
+ // Validate IPv6
+ if (IPAddress.TryParse(dnsServer, out IPAddress ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
+ {
+ dnsServersToResolve.Add(new ServerConnectionInfo(ip.ToString(), 53));
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(StatusMessage))
+ StatusMessage += Environment.NewLine;
+
+ StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
+ IsStatusMessageDisplayed = true;
+
+ continue;
+ }
+ }
+ // IPv4/hostname:port
+ else if (dnsServer.Contains(':'))
+ {
+ var parts = dnsServer.Split([':'], 2);
- foreach (var customDNSServer in DNSServer.Split(';').Select(x => x.Trim()))
- {
- var customDNSServerArgs = customDNSServer.Split(':');
+ Debug.WriteLine($"IPv4 or Hostname with port detected. Host: {parts[0]}, Port: {parts[1]}");
- var server = customDNSServerArgs[0];
- var port = customDNSServerArgs.Length == 2 && int.TryParse(customDNSServerArgs[1], out var p) ? p : 53;
+ // Validate IPv4/Hostname and port
+ if ((RegexHelper.IPv4AddressRegex().IsMatch(parts[0]) || RegexHelper.HostnameOrDomainRegex().IsMatch(parts[0])) && int.TryParse(parts[1], out int port) && port > 0 && port <= 65535)
+ {
+ dnsServersToResolve.Add(new ServerConnectionInfo(parts[0], port));
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(StatusMessage))
+ StatusMessage += Environment.NewLine;
+
+ StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
+ IsStatusMessageDisplayed = true;
- // Resolve hostname to IP address
- if (!IPAddress.TryParse(server, out _))
+ continue;
+ }
+ }
+ // IPv4/hostname
+ else
{
- var dnsResult = await DNSClientHelper.ResolveAorAaaaAsync(server,
- SettingsManager.Current.Network_ResolveHostnamePreferIPv4);
+ Debug.WriteLine($"IPv4 or Hostname without port detected. Host: {dnsServer}");
+ // Validate IPv4/Hostname
+
+ if (RegexHelper.IPv4AddressRegex().IsMatch(dnsServer) || RegexHelper.HostnameOrDomainRegex().IsMatch(dnsServer))
+ {
+ dnsServersToResolve.Add(new ServerConnectionInfo(dnsServer, 53));
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(StatusMessage))
+ StatusMessage += Environment.NewLine;
+
+ StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
+ IsStatusMessageDisplayed = true;
+
+ continue;
+ }
+ }
+ }
+ }
+
+ // DNS Lookup instance not created yet (not using Windows DNS server), need to resolve dns servers first
+ if (dnsLookup == null)
+ {
+ List resolvedDNSServers = [];
+
+ foreach (var dnsServerToResolve in dnsServersToResolve)
+ {
+ Debug.WriteLine("RESOLVE: " + dnsServerToResolve.Server);
+
+ // Check if already an IP address
+ if (IPAddress.TryParse(dnsServerToResolve.Server, out _))
+ {
+ resolvedDNSServers.Add(dnsServerToResolve);
+ }
+ // Need to resolve hostname to IP address
+ else
+ {
+ var dnsResult = await DNSClientHelper.ResolveAorAaaaAsync(dnsServerToResolve.Server,
+ SettingsManager.Current.Network_ResolveHostnamePreferIPv4);
if (dnsResult.HasError)
{
- var dnsErrorMessage = DNSClientHelper.FormatDNSClientResultError(server, dnsResult);
+ var dnsErrorMessage = DNSClientHelper.FormatDNSClientResultError(dnsServerToResolve.Server, dnsResult);
- if(!string.IsNullOrEmpty(StatusMessage))
+ if (!string.IsNullOrEmpty(StatusMessage))
StatusMessage += Environment.NewLine;
StatusMessage += $"{Strings.DNSServer}: {dnsErrorMessage}";
@@ -511,21 +643,32 @@ private async Task QueryAsync()
continue; // Skip this server, try next one
}
- server = dnsResult.Value.ToString();
+ resolvedDNSServers.Add(new ServerConnectionInfo(
+ dnsResult.Value.ToString(),
+ dnsServerToResolve.Port,
+ dnsServerToResolve.TransportProtocol)
+ );
}
-
- customDNSServers.Add(new ServerConnectionInfo(server, port, TransportProtocol.Udp));
}
- // Check if we have any valid custom dns servers
- if (customDNSServers.Count == 0)
+ // Create DNS lookup instance if we have any resolved dns servers
+ if (resolvedDNSServers.Count > 0)
+ {
+ dnsLookup = new DNSLookup(dnsSettings, resolvedDNSServers);
+ }
+ // No valid dns servers, return with error
+ else
{
+ if (!string.IsNullOrEmpty(StatusMessage))
+ StatusMessage += Environment.NewLine;
+
+ StatusMessage += "Could not parse / resolve any of the specified DNS servers.";
+ IsStatusMessageDisplayed = true;
+
IsRunning = false;
-
+
return;
}
-
- dnsLookup = new DNSLookup(dnsSettings, customDNSServers);
}
dnsLookup.RecordReceived += DNSLookup_RecordReceived;
@@ -557,7 +700,7 @@ public void OnClose()
private void AddHostToHistory(string host)
{
// Create the new list
- var list = ListHelper.Modify(SettingsManager.Current.DNSLookup_HostHistory.ToList(), host,
+ var list = ListHelper.Modify([.. SettingsManager.Current.DNSLookup_HostHistory], host,
SettingsManager.Current.General_HistoryListEntries);
// Clear the old items
diff --git a/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml b/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml
deleted file mode 100644
index db26ad245e..0000000000
--- a/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml
+++ /dev/null
@@ -1,201 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml.cs b/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml.cs
deleted file mode 100644
index 6bef55bbfb..0000000000
--- a/Source/NETworkManager/Views/ServerConnectionInfoProfileDialog.xaml.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Windows;
-using System.Windows.Controls;
-using NETworkManager.ViewModels;
-
-namespace NETworkManager.Views;
-
-public partial class ServerConnectionInfoProfileDialog
-{
- public ServerConnectionInfoProfileDialog()
- {
- InitializeComponent();
- }
-
- private void UserControl_Loaded(object sender, RoutedEventArgs e)
- {
- TextBoxName.Focus();
- }
-
- private void ContextMenu_Opened(object sender, RoutedEventArgs e)
- {
- if (sender is ContextMenu menu)
- menu.DataContext = (ServerConnectionInfoProfileViewModel)DataContext;
- }
-}
\ No newline at end of file
From a2088f6707ea0fcd7e585bc5e82849f12aaae09a Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sat, 13 Dec 2025 22:30:31 +0100
Subject: [PATCH 10/11] Docs: #3261 & dotnet format
---
.../Resources/Strings.Designer.cs | 18 ++
.../Resources/Strings.resx | 6 +
.../Network/DNSServer.cs | 5 +
.../Network/DNSServerConnectionInfoProfile.cs | 2 +-
.../Network/ServerConnectionInfo.cs | 122 +++++++++--
.../NETworkManager.Utilities/RegexHelper.cs | 4 +-
.../IPAddressOrHostnameValidator.cs | 2 +-
.../ServerValidator.cs | 2 +-
.../ViewModels/DNSLookupHostViewModel.cs | 4 +-
.../ViewModels/DNSLookupViewModel.cs | 194 ++++--------------
.../ViewModels/HostsFileEditorViewModel.cs | 2 +-
.../ViewModels/IPGeolocationHostViewModel.cs | 4 +-
.../ViewModels/IPScannerHostViewModel.cs | 4 +-
.../ViewModels/LookupOUILookupViewModel.cs | 2 +-
.../ViewModels/LookupPortViewModel.cs | 2 +-
.../NetworkConnectionWidgetViewModel.cs | 10 +-
.../ViewModels/PingMonitorHostViewModel.cs | 2 +-
.../ViewModels/PortScannerHostViewModel.cs | 2 +-
Website/docs/application/dns-lookup.md | 22 +-
Website/docs/changelog/next-release.md | 1 +
20 files changed, 219 insertions(+), 191 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 7e2dea087e..ca27acd2f3 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -2157,6 +2157,15 @@ public static string CouldNotGetPublicIPAddressFromXXXMessage {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die Could not parse or resolve any of the specified DNS servers. ähnelt.
+ ///
+ public static string CouldNotParseOrResolveDNSServers {
+ get {
+ return ResourceManager.GetString("CouldNotParseOrResolveDNSServers", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Could not parse public ip address from "{0}"! Try another service or use the default... ähnelt.
///
@@ -2166,6 +2175,15 @@ public static string CouldNotParsePublicIPAddressFromXXXMessage {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die Could not parse DNS server "{0}". ähnelt.
+ ///
+ public static string CouldNotParseX {
+ get {
+ return ResourceManager.GetString("CouldNotParseX", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Could not resolve hostname for: "{0}" ähnelt.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index cad95c179a..c72d6e06b5 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -3945,4 +3945,10 @@ If you click Cancel, the profile file will remain unencrypted.
A restart is required to apply changes such as language settings.
+
+ Could not parse or resolve any of the specified DNS servers.
+
+
+ Could not parse DNS server "{0}".
+
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Network/DNSServer.cs b/Source/NETworkManager.Models/Network/DNSServer.cs
index 13cb3d7cd4..acbd0270ee 100644
--- a/Source/NETworkManager.Models/Network/DNSServer.cs
+++ b/Source/NETworkManager.Models/Network/DNSServer.cs
@@ -36,6 +36,11 @@ public static List GetDefaultList()
new("209.244.0.3", 53, TransportProtocol.Udp),
new("209.244.0.4", 53, TransportProtocol.Udp)
]),
+ new("Quad9",
+ [
+ new ("9.9.9.9", 53, TransportProtocol.Udp),
+ new ("149.112.112.112", 53 , TransportProtocol.Udp)
+ ]),
new("Verisign",
[
new("64.6.64.6", 53, TransportProtocol.Udp),
diff --git a/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs b/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs
index c1ba8003f2..ccc9209aae 100644
--- a/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs
+++ b/Source/NETworkManager.Models/Network/DNSServerConnectionInfoProfile.cs
@@ -30,5 +30,5 @@ public DNSServerConnectionInfoProfile(string name, List se
///
/// Use the DNS server from Windows.
///
- public bool UseWindowsDNSServer { get; set; }
+ public bool UseWindowsDNSServer { get; set; }
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs b/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs
index 3250526227..0a402771a6 100644
--- a/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs
+++ b/Source/NETworkManager.Models/Network/ServerConnectionInfo.cs
@@ -1,4 +1,11 @@
-namespace NETworkManager.Models.Network;
+using NETworkManager.Utilities;
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace NETworkManager.Models.Network;
///
/// Class contains information about a server.
@@ -53,19 +60,110 @@ public ServerConnectionInfo(string server, int port, TransportProtocol transport
///
public TransportProtocol TransportProtocol { get; set; }
- // public static ServerConnectionInfo Parse (string input)
- // {
-
+ ///
+ /// Tries to parse a server connection string into a object.
+ /// Supports formats: IPv4, IPv4:port, IPv6, [IPv6], [IPv6]:port, hostname, hostname:port
+ ///
+ /// Server connection string to parse.
+ /// Parsed object if successful.
+ /// Default port to use if not specified in input.
+ /// Transport protocol to set in the parsed object.
+ /// True if parsing was successful; otherwise, false.
+ public static bool TryParse(string input, out ServerConnectionInfo serverConnectionInfo, int defaultPort, TransportProtocol transportProtocol)
+ {
+ serverConnectionInfo = null;
+
+ if (string.IsNullOrWhiteSpace(input))
+ return false;
+
+ input = input.Trim();
+
+ Debug.WriteLine("Parse server connection info input: " + input);
+
+ // [IPv6]:Port
+ if (input.StartsWith('[') && input.Contains("]:"))
+ {
+ var endIndex = input.IndexOf("]:", StringComparison.Ordinal);
+ var ipPart = input[1..endIndex];
+ var portPart = input[(endIndex + 2)..];
+
+ Debug.WriteLine($"IPv6 with port detected. IP: {ipPart}, Port: {portPart}");
+
+ if (IPAddress.TryParse(ipPart, out IPAddress ip) && ip.AddressFamily == AddressFamily.InterNetworkV6 &&
+ int.TryParse(portPart, out int port) && port > 0 && port <= 65535)
+ {
+ serverConnectionInfo = new ServerConnectionInfo(ip.ToString(), port, transportProtocol);
+ return true;
+ }
+ }
+ // [IPv6]
+ else if (input.StartsWith('[') && input.EndsWith(']'))
+ {
+ var ipPart = input[1..^1];
+
+ Debug.WriteLine($"IPv6 without port detected. IP: {ipPart}");
- // return null;
- // }
+ if (IPAddress.TryParse(ipPart, out IPAddress ip) && ip.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ serverConnectionInfo = new ServerConnectionInfo(ip.ToString(), defaultPort, transportProtocol);
+ return true;
+ }
+ }
+ // IPv6 without brackets (contains multiple colons)
+ else if (input.Count(c => c == ':') > 1)
+ {
+ Debug.WriteLine($"IPv6 without port detected. IP: {input}");
- //public static bool TryParse (string input, out ServerConnectionInfo serverConnectionInfo)
- // {
- // serverConnectionInfo = null;
-
- // return false;
- // }
+ if (IPAddress.TryParse(input, out IPAddress ip) && ip.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ serverConnectionInfo = new ServerConnectionInfo(ip.ToString(), defaultPort, transportProtocol);
+ return true;
+ }
+ }
+ // IPv4/hostname:port (single colon)
+ else if (input.Contains(':'))
+ {
+ var parts = input.Split([':'], 2);
+
+ Debug.WriteLine($"IPv4 or Hostname with port detected. Host: {parts[0]}, Port: {parts[1]}");
+
+ if ((RegexHelper.IPv4AddressRegex().IsMatch(parts[0]) || RegexHelper.HostnameOrDomainRegex().IsMatch(parts[0])) &&
+ int.TryParse(parts[1], out int port) && port > 0 && port <= 65535)
+ {
+ serverConnectionInfo = new ServerConnectionInfo(parts[0], port, transportProtocol);
+ return true;
+ }
+ }
+ // IPv4/hostname
+ else
+ {
+ if (RegexHelper.IPv4AddressRegex().IsMatch(input) || RegexHelper.HostnameOrDomainRegex().IsMatch(input))
+ {
+ Debug.WriteLine($"IPv4 or Hostname without port detected. Host: {input}");
+
+ serverConnectionInfo = new ServerConnectionInfo(input, defaultPort, transportProtocol);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Parses a server connection string into a object.
+ /// Supports formats: IPv4, IPv4:port, IPv6, [IPv6], [IPv6]:port, hostname, hostname:port
+ ///
+ /// Server connection string to parse.
+ /// Default port to use if not specified in input.
+ /// Parsed object.
+ /// Thrown when the input string is not in a valid format.
+ public static ServerConnectionInfo Parse(string input, int defaultPort, TransportProtocol transportProtocol)
+ {
+ if (TryParse(input, out var serverConnectionInfo, defaultPort, transportProtocol))
+ return serverConnectionInfo;
+
+ throw new FormatException($"Could not parse server connection info from input: {input}");
+ }
///
/// Returns a string that represents the current object.
diff --git a/Source/NETworkManager.Utilities/RegexHelper.cs b/Source/NETworkManager.Utilities/RegexHelper.cs
index 72b621228b..ec5d2385a1 100644
--- a/Source/NETworkManager.Utilities/RegexHelper.cs
+++ b/Source/NETworkManager.Utilities/RegexHelper.cs
@@ -92,7 +92,7 @@ public static partial class RegexHelper
// Match the first 3 bytes of a MAC-Address 000000, 00:00:00, 00-00-00
public const string MACAddressFirst3BytesRegex =
@"^[A-Fa-f0-9]{6}$|^[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}$|^[A-Fa-f0-9]{4}.[A-Fa-f0-9]{2}$";
-
+
// Match IPv6 address like ::1
// ReSharper disable once InconsistentNaming
public const string IPv6AddressRegex =
@@ -107,7 +107,7 @@ public static partial class RegexHelper
public const string SpecialRangeRegex =
@"\[((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)-(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))([,]((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)-(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))))*\]";
-
+
///
/// Provides a compiled regular expression that matches valid hostnames or fully qualified domain names (FQDNs) like
diff --git a/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs b/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs
index f2c064101f..40ad7d72c8 100644
--- a/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs
+++ b/Source/NETworkManager.Validators/IPAddressOrHostnameValidator.cs
@@ -25,7 +25,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
return ValidationResult.ValidResult;
// Check if it is a valid hostname like server-01 or server-01.example.com
- if(RegexHelper.HostnameOrDomainRegex().IsMatch(input))
+ if (RegexHelper.HostnameOrDomainRegex().IsMatch(input))
return ValidationResult.ValidResult;
return new ValidationResult(false, Strings.EnterValidHostnameOrIPAddress);
diff --git a/Source/NETworkManager.Validators/ServerValidator.cs b/Source/NETworkManager.Validators/ServerValidator.cs
index b029b62227..bdd0416db3 100644
--- a/Source/NETworkManager.Validators/ServerValidator.cs
+++ b/Source/NETworkManager.Validators/ServerValidator.cs
@@ -32,7 +32,7 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
return ValidationResult.ValidResult;
// Check if it is a valid hostname like server-01 or server-01.example.com
- if(RegexHelper.HostnameOrDomainRegex().IsMatch(input) && !allowOnlyIPAddress)
+ if (RegexHelper.HostnameOrDomainRegex().IsMatch(input) && !allowOnlyIPAddress)
return ValidationResult.ValidResult;
return new ValidationResult(false, genericErrorResult);
diff --git a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs
index da95fab145..7db27bdec4 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs
@@ -586,7 +586,7 @@ private void CollapseAllProfileGroupsAction()
{
SetIsExpandedForAllProfileGroups(false);
}
-
+
///
/// Gets the callback for closing a tab item.
///
@@ -613,7 +613,7 @@ private void SetIsExpandedForAllProfileGroups(bool isExpanded)
foreach (var group in Profiles.Groups.Cast())
GroupExpanderStateStore[group.Name.ToString()] = isExpanded;
}
-
+
///
/// Resizes the profile view.
///
diff --git a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
index efa1a29923..0924da7dd2 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
@@ -431,7 +431,6 @@ private void ExportAction()
#endregion
#region Methods
-
///
/// Performs the DNS query.
///
@@ -442,7 +441,6 @@ private async Task QueryAsync()
IsRunning = true;
- // Reset the latest results
Results.Clear();
DragablzTabItem.SetTabHeader(_tabId, Host);
@@ -467,203 +465,76 @@ private async Task QueryAsync()
dnsSettings.CustomDNSSuffix = SettingsManager.Current.DNSLookup_CustomDNSSuffix?.TrimStart('.');
}
- // Try find existing dns server profile
+ // Try to find DNS server profile
var dnsServerProfile = SettingsManager.Current.DNSLookup_DNSServers
.FirstOrDefault(x => x.Name == DNSServer);
DNSLookup dnsLookup = null;
-
List dnsServersToResolve = [];
- // Use profile if found
+ // Use DNS server profile if found
if (dnsServerProfile != null)
{
- // Use windows dns server
if (dnsServerProfile.UseWindowsDNSServer)
dnsLookup = new DNSLookup(dnsSettings);
- // Use custom dns servers from profile
else
dnsServersToResolve = dnsServerProfile.Servers;
}
- // Parse custom dns server from input
+ // Parse DNS server input
else
{
- foreach (var dnsServer in DNSServer.Split(';').Select(x => x.Trim()))
+ foreach (var dnsServerInput in DNSServer.Split(';'))
{
- Debug.WriteLine("PARSE: " + dnsServer);
-
- // [IPv6]:Port
- if (dnsServer.StartsWith('[') && dnsServer.Contains("]:"))
- {
- var endIndex = dnsServer.IndexOf("]:", StringComparison.Ordinal);
-
- var ipPart = dnsServer[1..endIndex];
- var portPart = dnsServer[(endIndex + 2)..];
-
- Debug.WriteLine($"IPV6 with port detected. IP: {ipPart}, Port: {portPart}");
-
- // Validate IPv6 and port
- if (IPAddress.TryParse(ipPart, out IPAddress ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6 &&
- int.TryParse(portPart, out int port) && port > 0 && port <= 65535)
- {
- dnsServersToResolve.Add(new ServerConnectionInfo(ip.ToString(), port));
- }
- else
- {
- if (!string.IsNullOrEmpty(StatusMessage))
- StatusMessage += Environment.NewLine;
-
- StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
- IsStatusMessageDisplayed = true;
-
- continue;
- }
- }
- // [IPv6]
- else if (dnsServer.StartsWith('[') && dnsServer.EndsWith(']'))
- {
- var ipPart = dnsServer[1..^1];
-
- Debug.WriteLine($"IPV6 without port detected. IP: {ipPart}");
-
- // Validate IPv6
- if (IPAddress.TryParse(ipPart, out IPAddress ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
- {
- dnsServersToResolve.Add(new ServerConnectionInfo(ip.ToString(), 53));
- }
- else
- {
- if (!string.IsNullOrEmpty(StatusMessage))
- StatusMessage += Environment.NewLine;
-
- StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
- IsStatusMessageDisplayed = true;
-
- continue;
- }
- }
- // IPv6
- else if (dnsServer.Count(c => c == ':') > 1)
- {
- Debug.WriteLine($"IPv6 without port detected. IP: {dnsServer}");
-
- // Validate IPv6
- if (IPAddress.TryParse(dnsServer, out IPAddress ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
- {
- dnsServersToResolve.Add(new ServerConnectionInfo(ip.ToString(), 53));
- }
- else
- {
- if (!string.IsNullOrEmpty(StatusMessage))
- StatusMessage += Environment.NewLine;
-
- StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
- IsStatusMessageDisplayed = true;
-
- continue;
- }
- }
- // IPv4/hostname:port
- else if (dnsServer.Contains(':'))
- {
- var parts = dnsServer.Split([':'], 2);
-
- Debug.WriteLine($"IPv4 or Hostname with port detected. Host: {parts[0]}, Port: {parts[1]}");
-
- // Validate IPv4/Hostname and port
- if ((RegexHelper.IPv4AddressRegex().IsMatch(parts[0]) || RegexHelper.HostnameOrDomainRegex().IsMatch(parts[0])) && int.TryParse(parts[1], out int port) && port > 0 && port <= 65535)
- {
- dnsServersToResolve.Add(new ServerConnectionInfo(parts[0], port));
- }
- else
- {
- if (!string.IsNullOrEmpty(StatusMessage))
- StatusMessage += Environment.NewLine;
-
- StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
- IsStatusMessageDisplayed = true;
-
- continue;
- }
- }
- // IPv4/hostname
+ if (ServerConnectionInfo.TryParse(dnsServerInput, out var serverInfo, 53, TransportProtocol.Udp))
+ dnsServersToResolve.Add(serverInfo);
else
- {
- Debug.WriteLine($"IPv4 or Hostname without port detected. Host: {dnsServer}");
- // Validate IPv4/Hostname
-
- if (RegexHelper.IPv4AddressRegex().IsMatch(dnsServer) || RegexHelper.HostnameOrDomainRegex().IsMatch(dnsServer))
- {
- dnsServersToResolve.Add(new ServerConnectionInfo(dnsServer, 53));
- }
- else
- {
- if (!string.IsNullOrEmpty(StatusMessage))
- StatusMessage += Environment.NewLine;
-
- StatusMessage += $"{Strings.DNSServer}: Could not parse DNS server '{dnsServer}'";
- IsStatusMessageDisplayed = true;
-
- continue;
- }
- }
+ AppendStatusMessage(Strings.DNSServer + ": " + string.Format(Strings.CouldNotParseX, dnsServerInput));
}
}
- // DNS Lookup instance not created yet (not using Windows DNS server), need to resolve dns servers first
+ // Resolve DNS server hostnames (if any) - not required when using Windows DNS servers
if (dnsLookup == null)
{
List resolvedDNSServers = [];
foreach (var dnsServerToResolve in dnsServersToResolve)
{
- Debug.WriteLine("RESOLVE: " + dnsServerToResolve.Server);
-
// Check if already an IP address
if (IPAddress.TryParse(dnsServerToResolve.Server, out _))
{
resolvedDNSServers.Add(dnsServerToResolve);
- }
- // Need to resolve hostname to IP address
- else
- {
- var dnsResult = await DNSClientHelper.ResolveAorAaaaAsync(dnsServerToResolve.Server,
- SettingsManager.Current.Network_ResolveHostnamePreferIPv4);
- if (dnsResult.HasError)
- {
- var dnsErrorMessage = DNSClientHelper.FormatDNSClientResultError(dnsServerToResolve.Server, dnsResult);
+ continue;
+ }
- if (!string.IsNullOrEmpty(StatusMessage))
- StatusMessage += Environment.NewLine;
+ // Resolve hostname to IP address
+ var dnsResult = await DNSClientHelper.ResolveAorAaaaAsync(dnsServerToResolve.Server,
+ SettingsManager.Current.Network_ResolveHostnamePreferIPv4);
- StatusMessage += $"{Strings.DNSServer}: {dnsErrorMessage}";
- IsStatusMessageDisplayed = true;
+ if (dnsResult.HasError)
+ {
+ var dnsErrorMessage = DNSClientHelper.FormatDNSClientResultError(dnsServerToResolve.Server, dnsResult);
- continue; // Skip this server, try next one
- }
+ AppendStatusMessage($"{Strings.DNSServer}: {dnsErrorMessage}");
- resolvedDNSServers.Add(new ServerConnectionInfo(
- dnsResult.Value.ToString(),
- dnsServerToResolve.Port,
- dnsServerToResolve.TransportProtocol)
- );
+ continue;
}
+
+ resolvedDNSServers.Add(new ServerConnectionInfo(
+ dnsResult.Value.ToString(),
+ dnsServerToResolve.Port,
+ dnsServerToResolve.TransportProtocol)
+ );
}
- // Create DNS lookup instance if we have any resolved dns servers
+ // Create DNS lookup instance
if (resolvedDNSServers.Count > 0)
{
dnsLookup = new DNSLookup(dnsSettings, resolvedDNSServers);
}
- // No valid dns servers, return with error
else
{
- if (!string.IsNullOrEmpty(StatusMessage))
- StatusMessage += Environment.NewLine;
-
- StatusMessage += "Could not parse / resolve any of the specified DNS servers.";
- IsStatusMessageDisplayed = true;
+ AppendStatusMessage(Strings.CouldNotParseOrResolveDNSServers);
IsRunning = false;
@@ -692,6 +563,19 @@ public void OnClose()
ConfigurationManager.Current.DNSLookupTabCount--;
}
+ ///
+ /// Appends the specified message to the current status message, adding a new line if a message already exists.
+ ///
+ /// The status message text to append. Cannot be null.
+ private void AppendStatusMessage(string message)
+ {
+ if (!string.IsNullOrEmpty(StatusMessage))
+ StatusMessage += Environment.NewLine;
+
+ StatusMessage += message;
+ IsStatusMessageDisplayed = true;
+ }
+
// Modify history list
///
/// Adds the host to the history.
diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
index cb4937d19a..08f77b591b 100644
--- a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
+++ b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
@@ -429,7 +429,7 @@ private async Task AddEntryAction()
{
childWindow.IsOpen = false;
ConfigurationManager.Current.IsChildWindowOpen = false;
-
+
IsModifying = false;
});
diff --git a/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs b/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs
index 7f4fdc19f0..bdea0a12fb 100644
--- a/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs
@@ -586,7 +586,7 @@ private void CollapseAllProfileGroupsAction()
{
SetIsExpandedForAllProfileGroups(false);
}
-
+
///
/// Gets the callback for closing a tab item.
///
@@ -613,7 +613,7 @@ private void SetIsExpandedForAllProfileGroups(bool isExpanded)
foreach (var group in Profiles.Groups.Cast())
GroupExpanderStateStore[group.Name.ToString()] = isExpanded;
}
-
+
///
/// Resizes the profile view.
///
diff --git a/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs
index 6952823984..995d91872c 100644
--- a/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs
@@ -560,7 +560,7 @@ private void ClearProfileFilterAction()
IsProfileFilterSet = false;
ProfileFilterIsOpen = false;
}
-
+
///
/// Gets the command to expand all profile groups.
///
@@ -613,7 +613,7 @@ private void SetIsExpandedForAllProfileGroups(bool isExpanded)
foreach (var group in Profiles.Groups.Cast())
GroupExpanderStateStore[group.Name.ToString()] = isExpanded;
}
-
+
///
/// Resizes the profile view.
///
diff --git a/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs b/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs
index 1b3c31c54e..76daee1589 100644
--- a/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs
+++ b/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs
@@ -71,7 +71,7 @@ private void AddSearchToHistory(string macAddressOrVendor)
#endregion
#region Variables
-
+
///
/// The logger.
///
diff --git a/Source/NETworkManager/ViewModels/LookupPortViewModel.cs b/Source/NETworkManager/ViewModels/LookupPortViewModel.cs
index e321e447b0..7051fa87f0 100644
--- a/Source/NETworkManager/ViewModels/LookupPortViewModel.cs
+++ b/Source/NETworkManager/ViewModels/LookupPortViewModel.cs
@@ -67,7 +67,7 @@ private void AddSearchToHistory(string portOrService)
#endregion
#region Variables
-
+
///
/// The logger.
///
diff --git a/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs b/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs
index 5a0dc1e3bc..db3617750a 100644
--- a/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs
+++ b/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs
@@ -19,7 +19,7 @@ namespace NETworkManager.ViewModels;
public class NetworkConnectionWidgetViewModel : ViewModelBase
{
#region Variables
-
+
///
/// The logger.
///
@@ -657,7 +657,7 @@ public void Check()
{
CheckAsync().ConfigureAwait(false);
}
-
+
///
/// The cancellation token source.
///
@@ -667,7 +667,7 @@ public void Check()
/// The check task.
///
private Task _checkTask = Task.CompletedTask;
-
+
///
/// Checks the network connections asynchronously.
///
@@ -714,7 +714,7 @@ private async Task CheckAsync()
Log.Info("Network connection check completed.");
}
}
-
+
///
/// Runs the check tasks.
///
@@ -728,7 +728,7 @@ await Task.WhenAll(
CheckConnectionInternetAsync(ct)
);
}
-
+
///
/// Checks the computer connection.
///
diff --git a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs
index cd04f600ca..c2366c25dc 100644
--- a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs
@@ -338,7 +338,7 @@ public bool IsProfileFilterSet
}
private readonly GroupExpanderStateStore _groupExpanderStateStore = new();
-
+
///
/// Gets the group expander state store.
///
diff --git a/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs
index 07dc2fafdf..3b0e188816 100644
--- a/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs
@@ -505,7 +505,7 @@ private void CollapseAllProfileGroupsAction()
{
SetIsExpandedForAllProfileGroups(false);
}
-
+
///
/// Gets the callback for closing a tab item.
///
diff --git a/Website/docs/application/dns-lookup.md b/Website/docs/application/dns-lookup.md
index e85250758b..bc89614735 100644
--- a/Website/docs/application/dns-lookup.md
+++ b/Website/docs/application/dns-lookup.md
@@ -8,17 +8,32 @@ With the **DNS Lookup** you can query DNS servers for various resource records.
Example inputs:
-- `server-01.borntoberoot.net` (`A` record)
-- `10.0.0.1` (`PTR` record)
+| Host | Type | Description |
+| ---------------------------- | ------- | -------------------------------------------------------------------- |
+| `server-01.borntoberoot.net` | `A` | To get the IPv4 address of the hostname. |
+| `server-01.borntoberoot.net` | `AAAA` | To get the IPv6 address of the hostname. |
+| `server.borntoberoot.net` | `CNAME` | To get the canonical name of the hostname. |
+| `borntoberoot.net` | `MX` | To get the mail exchange servers for the domain. |
+| `borntoberoot.net` | `NS` | To get the name servers for the domain. |
+| `10.0.0.1` | `PTR` | To get the hostname associated with the IP address (reverse lookup). |
+
+| DNS server | Description |
+| ---------------------- | -------------------------------------------------------------------------- |
+| `[Windows DNS]` | Uses the DNS server configured in Windows. |
+| `Cloudflare` (profile) | Uses Cloudflare's public DNS servers from a predefined profile. |
+| `1.1.1.1:53` (input) | Uses the custom DNS server `1.1.1.1` on port `53` from the input directly. |
+| `ns3.inwx.eu` (input) | Uses the custom DNS server `ns3.inwx.eu` from the input directly. |
:::note
-Multiple inputs can be combined with a semicolon (`;`).
+Multiple inputs (host, DNS server) can be combined with a semicolon (`;`).
Example: `server-01.borntoberoot.net; 10.0.0.1`
:::
+The dns server can be selected from a list of configured servers or you can enter a custom dns server in the format `|:` (`` is optional, to use a custom port with IPv6 enclose the address in square brackets: `[]:53`).
+

:::note
@@ -72,6 +87,7 @@ List of DNS server profiles. A profile can contain one or more DNS servers with
| DNS.Watch | `84.200.69.80:53` | `84.200.70.40:53` |
| Google Public DNS | `8.8.8.8:53` | `8.8.4.4:53` |
| Level3 | `209.244.0.3:53` | `209.244.0.4:53` |
+| Quad9 | `9.9.9.9` | `149.112.112.112` |
| Verisign | `64.6.64.6:53` | `64.6.65.6:53` |
:::note
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 889b092671..6ff95ac989 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -53,6 +53,7 @@ Release date: **xx.xx.2025**
**DNS Lookup**
- Allow hostname as server address in addition to IP address in the add/edit server dialog. [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)
+- Add `quad9` to the predefined DNS server list. [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)
- Redesign add/edit server dialog (migrated from dialog to child window). [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)
**Remote Desktop**
From a00df72b6f435aa9ef87201a84a9cc3cbe34834b Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sat, 13 Dec 2025 22:31:19 +0100
Subject: [PATCH 11/11] Update NETworkManager.Documentation.csproj
---
.../NETworkManager.Documentation.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/NETworkManager.Documentation/NETworkManager.Documentation.csproj b/Source/NETworkManager.Documentation/NETworkManager.Documentation.csproj
index f344bd7af9..996f839fac 100644
--- a/Source/NETworkManager.Documentation/NETworkManager.Documentation.csproj
+++ b/Source/NETworkManager.Documentation/NETworkManager.Documentation.csproj
@@ -1,4 +1,4 @@
-
+
{95CE4AD5-14C0-4486-9C11-5D6A5EC48176}
Library