From 01ae7e68aaddf01c964f0580bfeefcfcdceaff8e Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sat, 9 Dec 2023 01:33:44 +0100 Subject: [PATCH] Feature: Ping Monitor improvements (#2573) * Feature: Ping Monitor improvements * Feature: DNS Lookup / Port Scanner improve group string * Feature: Host range aded to Ping Monitor & new design * Feature: Allow range in profile * Feature: Ping Monitor * Feature: Setting to expand host view * Feature: Expand ping monitor host * Docs: Ping Monitor * Chore: Refactoring * Feature: Ping Monitor * Docs: Update img --- InnoSetup.iss | 2 +- Source/GlobalAssemblyInfo.cs | 4 +- .../PercentConverter.cs | 21 - .../Resources/StaticStrings.Designer.cs | 2 +- .../Resources/StaticStrings.resx | 2 +- .../Resources/Strings.Designer.cs | 29 +- .../Resources/Strings.resx | 11 +- .../ExportManager.DNSLookupRecordInfo.cs | 14 +- .../Network/DNSLookup.cs | 33 +- .../Network/DNSLookupRecordInfo.cs | 39 +- .../Network/HostNotFoundException.cs | 21 - .../Network/HostRangeHelper.cs | 195 +++++----- .../Network/HostnameArgs.cs | 23 ++ .../Network/IPScanner.cs | 24 +- Source/NETworkManager.Models/Network/Ping.cs | 25 +- .../Network/PingMonitorOptions.cs | 15 - .../Network/PortScanner.cs | 15 +- .../Network/PortScannerPortInfo.cs | 5 + .../Network/Traceroute.cs | 12 +- .../GlobalStaticConfiguration.cs | 1 + .../NETworkManager.Settings/SettingsInfo.cs | 17 +- .../SettingsManager.cs | 20 +- Source/NETworkManager.Utilities/DNSClient.cs | 47 +-- .../IPAddressComparer.cs | 13 + .../IPAddressHelper.cs | 24 ++ Source/NETworkManager/ProfileDialogManager.cs | 34 +- .../Resources/Styles/ListBoxStyle.xaml | 8 - .../ViewModels/DNSLookupHostViewModel.cs | 6 +- .../ViewModels/DNSLookupViewModel.cs | 6 +- .../ViewModels/IPScannerViewModel.cs | 126 +++--- .../ViewModels/PingMonitorHostViewModel.cs | 250 +++++++----- .../PingMonitorSettingsViewModel.cs | 18 + .../ViewModels/PingMonitorViewModel.cs | 155 +++++--- .../ViewModels/PortScannerViewModel.cs | 119 +++--- .../Views/DiscoveryProtocolView.xaml | 31 +- .../NETworkManager/Views/IPScannerView.xaml | 30 +- .../Views/IPScannerView.xaml.cs | 2 +- .../Views/PingMonitorHostView.xaml | 107 +++++- .../Views/PingMonitorHostView.xaml.cs | 5 +- .../Views/PingMonitorSettingsView.xaml | 4 +- .../NETworkManager/Views/PingMonitorView.xaml | 359 ++++++++++-------- .../Views/PingMonitorView.xaml.cs | 21 +- .../NETworkManager/Views/PortScannerView.xaml | 16 +- .../Views/PortScannerView.xaml.cs | 2 +- .../NETworkManager/Views/ProfileDialog.xaml | 4 +- Source/NETworkManager/Views/WhoisView.xaml | 9 +- docs/Changelog/next-release.md | 14 + .../01_Application/06_PingMonitor.md | 22 +- .../01_Application/06_PingMonitor.png | Bin 52304 -> 93045 bytes 49 files changed, 1161 insertions(+), 801 deletions(-) delete mode 100644 Source/NETworkManager.Converters/PercentConverter.cs delete mode 100644 Source/NETworkManager.Models/Network/HostNotFoundException.cs create mode 100644 Source/NETworkManager.Models/Network/HostnameArgs.cs delete mode 100644 Source/NETworkManager.Models/Network/PingMonitorOptions.cs create mode 100644 Source/NETworkManager.Utilities/IPAddressComparer.cs diff --git a/InnoSetup.iss b/InnoSetup.iss index 9b8146e3f6..9b37a6a418 100644 --- a/InnoSetup.iss +++ b/InnoSetup.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "NETworkManager" -#define MyAppVersion "2023.11.28.0" +#define MyAppVersion "2023.12.9.0" #define MyAppPublisher "BornToBeRoot" #define MyAppURL "https://github.com/BornToBeRoot/NETworkManager/" #define MyAppExeName "NETworkManager.exe" diff --git a/Source/GlobalAssemblyInfo.cs b/Source/GlobalAssemblyInfo.cs index 9f8aa2cbe3..42739fc2cb 100644 --- a/Source/GlobalAssemblyInfo.cs +++ b/Source/GlobalAssemblyInfo.cs @@ -6,5 +6,5 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2023.11.28.0")] -[assembly: AssemblyFileVersion("2023.11.28.0")] +[assembly: AssemblyVersion("2023.12.9.0")] +[assembly: AssemblyFileVersion("2023.12.9.0")] diff --git a/Source/NETworkManager.Converters/PercentConverter.cs b/Source/NETworkManager.Converters/PercentConverter.cs deleted file mode 100644 index 3dada7e353..0000000000 --- a/Source/NETworkManager.Converters/PercentConverter.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace NETworkManager.Converters; - -public sealed class PercentConverter : IMultiValueConverter -{ - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) - { - if ((int)values[0] == 0) - return 0; - - return Math.Round(((float)((int)values[1]) / (int)values[0]) * 100, 2); - } - - public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } -} diff --git a/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs b/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs index 86ac9ca1b1..d324a9d6a5 100644 --- a/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs +++ b/Source/NETworkManager.Localization/Resources/StaticStrings.Designer.cs @@ -222,7 +222,7 @@ public static string ExampleHostnameOrIPAddress { } /// - /// Looks up a localized string similar to 192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; borntoberoot.net/24. + /// Looks up a localized string similar to 192.168.178.0/24; 10.0.0.0 - 10.0.0.9; 10.0.[0-9,20].[1-2]; server-01.borntoberoot.net/24. /// public static string ExampleHostRange { get { diff --git a/Source/NETworkManager.Localization/Resources/StaticStrings.resx b/Source/NETworkManager.Localization/Resources/StaticStrings.resx index f3f3400d9e..c8dc4731f0 100644 --- a/Source/NETworkManager.Localization/Resources/StaticStrings.resx +++ b/Source/NETworkManager.Localization/Resources/StaticStrings.resx @@ -145,7 +145,7 @@ SERVER-01 or 10.0.0.10 - 192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; borntoberoot.net/24 + 192.168.178.0/24; 10.0.0.0 - 10.0.0.9; 10.0.[0-9,20].[1-2]; server-01.borntoberoot.net/24 10.0.0.10 diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs index 79e13af035..527a01772c 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs +++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs @@ -1391,6 +1391,15 @@ public static string CanceledByUserMessage { } } + /// + /// Looks up a localized string similar to Host cannot be set while other hosts are being added. Please wait until the process is complete and try again.. + /// + public static string CannotSetHostWhileRunningMessage { + get { + return ResourceManager.GetString("CannotSetHostWhileRunningMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Caps lock is enabled!. /// @@ -1661,6 +1670,15 @@ public static string Close { } } + /// + /// Looks up a localized string similar to Close all. + /// + public static string CloseAll { + get { + return ResourceManager.GetString("CloseAll", resourceCulture); + } + } + /// /// Looks up a localized string similar to Closed. /// @@ -3607,6 +3625,15 @@ public static string Expand { } } + /// + /// Looks up a localized string similar to Expand host view. + /// + public static string ExpandHostView { + get { + return ResourceManager.GetString("ExpandHostView", resourceCulture); + } + } + /// /// Looks up a localized string similar to Experience. /// @@ -4387,7 +4414,7 @@ public static string HostnameOrIPAddress { } /// - /// Looks up a localized string similar to Host(s). + /// Looks up a localized string similar to Hosts. /// public static string Hosts { get { diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx index 9a3907e955..71d346647f 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.resx +++ b/Source/NETworkManager.Localization/Resources/Strings.resx @@ -873,7 +873,7 @@ First make a backup copy of your profile files before enabling encryption!Hostname or IP address - Host(s) + Hosts ID @@ -3748,4 +3748,13 @@ Try again in a few seconds. The object's state is inconsistent, preventing the set operation! + + Close all + + + Expand host view + + + Host cannot be set while other hosts are being added. Please wait until the process is complete and try again. + \ No newline at end of file diff --git a/Source/NETworkManager.Models/Export/ExportManager.DNSLookupRecordInfo.cs b/Source/NETworkManager.Models/Export/ExportManager.DNSLookupRecordInfo.cs index f60bab636d..60db155e11 100644 --- a/Source/NETworkManager.Models/Export/ExportManager.DNSLookupRecordInfo.cs +++ b/Source/NETworkManager.Models/Export/ExportManager.DNSLookupRecordInfo.cs @@ -46,11 +46,11 @@ private static void CreateCsv(IEnumerable collection, strin var stringBuilder = new StringBuilder(); stringBuilder.AppendLine( - $"{nameof(DNSLookupRecordInfo.DomainName)},{nameof(DNSLookupRecordInfo.TTL)},{nameof(DNSLookupRecordInfo.RecordClass)},{nameof(DNSLookupRecordInfo.RecordType)},{nameof(DNSLookupRecordInfo.Result)},{nameof(DNSLookupRecordInfo.Server)},{nameof(DNSLookupRecordInfo.IPEndPoint)}"); + $"{nameof(DNSLookupRecordInfo.DomainName)},{nameof(DNSLookupRecordInfo.TTL)},{nameof(DNSLookupRecordInfo.RecordClass)},{nameof(DNSLookupRecordInfo.RecordType)},{nameof(DNSLookupRecordInfo.Result)},{nameof(DNSLookupRecordInfo.NameServerIPAddress)},{nameof(DNSLookupRecordInfo.NameServerHostName)},{nameof(DNSLookupRecordInfo.NameServerPort)}"); foreach (var info in collection) stringBuilder.AppendLine( - $"{info.DomainName},{info.TTL},{info.RecordClass},{info.RecordType},{info.Result},{info.Server},{info.IPEndPoint}"); + $"{info.DomainName},{info.TTL},{info.RecordClass},{info.RecordType},{info.Result},{info.NameServerIPAddress},{info.NameServerHostName},{info.NameServerPort}"); System.IO.File.WriteAllText(filePath, stringBuilder.ToString()); } @@ -73,8 +73,9 @@ from info in collection new XElement(nameof(DNSLookupRecordInfo.RecordClass), info.RecordClass), new XElement(nameof(DNSLookupRecordInfo.RecordType), info.RecordType), new XElement(nameof(DNSLookupRecordInfo.Result), info.Result), - new XElement(nameof(DNSLookupRecordInfo.Server), info.Server), - new XElement(nameof(DNSLookupRecordInfo.IPEndPoint), info.IPEndPoint))))); + new XElement(nameof(DNSLookupRecordInfo.NameServerIPAddress), info.NameServerIPAddress), + new XElement(nameof(DNSLookupRecordInfo.NameServerHostName), info.NameServerHostName), + new XElement(nameof(DNSLookupRecordInfo.NameServerPort), info.NameServerPort))))); document.Save(filePath); } @@ -97,8 +98,9 @@ private static void CreateJson(IReadOnlyList collection, st collection[i].RecordClass, collection[i].RecordType, collection[i].Result, - collection[i].Server, - collection[i].IPEndPoint + collection[i].NameServerIPAddress, + collection[i].NameServerHostName, + collection[i].NameServerPort }; } diff --git a/Source/NETworkManager.Models/Network/DNSLookup.cs b/Source/NETworkManager.Models/Network/DNSLookup.cs index 9cf248a9ae..7115c8b680 100644 --- a/Source/NETworkManager.Models/Network/DNSLookup.cs +++ b/Source/NETworkManager.Models/Network/DNSLookup.cs @@ -2,11 +2,11 @@ using DnsClient.Protocol; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Threading.Tasks; +using NETworkManager.Utilities; namespace NETworkManager.Models.Network; @@ -116,8 +116,15 @@ public void ResolveAsync(IEnumerable hosts) var queries = _addSuffix && _settings.QueryType != QueryType.PTR ? GetHostWithSuffix(hosts) : hosts; // Foreach dns server - Parallel.ForEach(_servers, dnsServer => + Parallel.ForEach(_servers, async dnsServer => { + // Get the dns server hostname for some additional information + var dnsServerHostName = string.Empty; + + var dnsResult = await DNSClient.GetInstance().ResolvePtrAsync(dnsServer.Address); + if (!dnsResult.HasError) + dnsServerHostName = dnsResult.Value; + // Init each dns server once LookupClientOptions lookupClientOptions = new(dnsServer) { @@ -129,7 +136,7 @@ public void ResolveAsync(IEnumerable hosts) }; LookupClient lookupClient = new(lookupClientOptions); - + // Foreach host Parallel.ForEach(queries, query => { @@ -153,7 +160,7 @@ public void ResolveAsync(IEnumerable hosts) } // Process the results... - ProcessDnsAnswers(dnsResponse.Answers, dnsResponse.NameServer); + ProcessDnsAnswers(dnsResponse.Answers, dnsResponse.NameServer, dnsServerHostName); } catch (Exception ex) { @@ -171,7 +178,7 @@ public void ResolveAsync(IEnumerable hosts) /// /// List of DNS resource records. /// DNS name server that answered the query. - private void ProcessDnsAnswers(IEnumerable answers, NameServer nameServer) + private void ProcessDnsAnswers(IEnumerable answers, NameServer nameServer, string nameServerHostname = null) { if(answers is not DnsResourceRecord[] dnsResourceRecords) return; @@ -180,49 +187,49 @@ private void ProcessDnsAnswers(IEnumerable answers, NameServe foreach (var record in dnsResourceRecords.ARecords()) OnRecordReceived(new DNSLookupRecordReceivedArgs( new DNSLookupRecordInfo( - record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}" , $"{record.Address}", $"{nameServer.Address}", $"{nameServer.Address}:{nameServer.Port}"))); + record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}" , $"{record.Address}", $"{nameServer.Address}", nameServerHostname, nameServer.Port))); // AAAA foreach (var record in dnsResourceRecords.AaaaRecords()) OnRecordReceived(new DNSLookupRecordReceivedArgs( new DNSLookupRecordInfo( - record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", $"{record.Address}", $"{nameServer.Address}", $"{nameServer.Address}:{nameServer.Port}"))); + record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", $"{record.Address}", $"{nameServer.Address}", nameServerHostname, nameServer.Port))); // CNAME foreach (var record in dnsResourceRecords.CnameRecords()) OnRecordReceived(new DNSLookupRecordReceivedArgs( new DNSLookupRecordInfo( - record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.CanonicalName, $"{nameServer.Address}", $"{nameServer.Address}:{nameServer.Port}"))); + record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.CanonicalName, $"{nameServer.Address}", nameServerHostname, nameServer.Port))); // MX foreach (var record in dnsResourceRecords.MxRecords()) OnRecordReceived(new DNSLookupRecordReceivedArgs( new DNSLookupRecordInfo( - record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.Exchange, $"{nameServer.Address}", $"{nameServer.Address}:{nameServer.Port}"))); + record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.Exchange, $"{nameServer.Address}", nameServerHostname, nameServer.Port))); // NS foreach (var record in dnsResourceRecords.NsRecords()) OnRecordReceived(new DNSLookupRecordReceivedArgs( new DNSLookupRecordInfo( - record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.NSDName, $"{nameServer.Address}", $"{nameServer.Address}:{nameServer.Port}"))); + record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.NSDName, $"{nameServer.Address}", nameServerHostname, nameServer.Port))); // PTR foreach (var record in dnsResourceRecords.PtrRecords()) OnRecordReceived(new DNSLookupRecordReceivedArgs( new DNSLookupRecordInfo( - record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.PtrDomainName, $"{nameServer.Address}", $"{nameServer.Address}:{nameServer.Port}"))); + record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.PtrDomainName, $"{nameServer.Address}", nameServerHostname, nameServer.Port))); // SOA foreach (var record in dnsResourceRecords.SoaRecords()) OnRecordReceived(new DNSLookupRecordReceivedArgs( new DNSLookupRecordInfo( - record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.MName + ", " + record.RName, $"{nameServer.Address}", $"{nameServer.Address}:{nameServer.Port}"))); + record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", record.MName + ", " + record.RName, $"{nameServer.Address}", nameServerHostname, nameServer.Port))); // TXT foreach (var record in dnsResourceRecords.TxtRecords()) OnRecordReceived(new DNSLookupRecordReceivedArgs( new DNSLookupRecordInfo( - record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", string.Join(", ", record.Text), $"{nameServer.Address}", $"{nameServer.Address}:{nameServer.Port}"))); + record.DomainName, record.TimeToLive, $"{record.RecordClass}",$"{record.RecordType}", string.Join(", ", record.Text), $"{nameServer.Address}", nameServerHostname, nameServer.Port))); // ToDo: implement more } diff --git a/Source/NETworkManager.Models/Network/DNSLookupRecordInfo.cs b/Source/NETworkManager.Models/Network/DNSLookupRecordInfo.cs index dfe9521f0a..3b354129cc 100644 --- a/Source/NETworkManager.Models/Network/DNSLookupRecordInfo.cs +++ b/Source/NETworkManager.Models/Network/DNSLookupRecordInfo.cs @@ -9,12 +9,12 @@ public class DNSLookupRecordInfo /// Domain name of the record. /// public string DomainName { get; set; } - + /// /// Time to live (TTL) of the record. /// public int TTL { get; set; } - + /// /// Class of the record. /// @@ -31,14 +31,26 @@ public class DNSLookupRecordInfo public string Result { get; set; } /// - /// Name server that provided the result. + /// IP address of the name server that provided the result. + /// + public string NameServerIPAddress { get; set; } + + /// + /// Port of the name server that provided the result. + /// + public int NameServerPort { get; set; } + + /// + /// Hostname of the name server that provided the result. /// - public string Server { get; set; } + public string NameServerHostName { get; set; } /// - /// IP endpoint (IP address:port) of the name server that provided the result. + /// Hostname (if available) or/and IP address with port of the name server that provided the result. /// - public string IPEndPoint { get; set; } + public string NameServerAsString => string.IsNullOrEmpty(NameServerHostName) + ? $"{NameServerIPAddress}:{NameServerPort}" + : $"{NameServerHostName.TrimEnd('.')} # {NameServerIPAddress}:{NameServerPort}"; /// /// Creates a new instance of with the specified parameters. @@ -48,16 +60,19 @@ public class DNSLookupRecordInfo /// Class of the record. /// Type of the record. /// Result of the record. (IP address, hostname, text, etc.) - /// Name server that provided the result. - /// IP endpoint (IP address:port) of the name server that provided the result. - public DNSLookupRecordInfo(string domainName, int ttl, string recordClass, string recordType, string result, string server, string ipEndPoint) + /// IP address of the name server that provided the result. + /// Hostname of the name server that provided the result. + /// Port of the name server that provided the result. + public DNSLookupRecordInfo(string domainName, int ttl, string recordClass, string recordType, string result, + string nameServerIPAddress, string nameServerHostName, int nameServerPort) { DomainName = domainName; TTL = ttl; RecordClass = recordClass; RecordType = recordType; Result = result; - Server = server; - IPEndPoint = ipEndPoint; + NameServerIPAddress = nameServerIPAddress; + NameServerPort = nameServerPort; + NameServerHostName = nameServerHostName; } -} +} \ No newline at end of file diff --git a/Source/NETworkManager.Models/Network/HostNotFoundException.cs b/Source/NETworkManager.Models/Network/HostNotFoundException.cs deleted file mode 100644 index ad3f8999a5..0000000000 --- a/Source/NETworkManager.Models/Network/HostNotFoundException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace NETworkManager.Models.Network; - -public class HostNotFoundException : Exception -{ - public HostNotFoundException() - { - - } - - public HostNotFoundException(string message) : base(message) - { - - } - - public HostNotFoundException(string message, Exception innerException) : base(message, innerException) - { - - } -} diff --git a/Source/NETworkManager.Models/Network/HostRangeHelper.cs b/Source/NETworkManager.Models/Network/HostRangeHelper.cs index 8dad0d15aa..91c971fff4 100644 --- a/Source/NETworkManager.Models/Network/HostRangeHelper.cs +++ b/Source/NETworkManager.Models/Network/HostRangeHelper.cs @@ -13,67 +13,87 @@ namespace NETworkManager.Models.Network; +/// +/// Helper class to interact with host ranges. +/// E.g. Parse inputs, resolve hostnames and ip ranges. +/// public static class HostRangeHelper { - public static Task CreateIPAddressesFromIPRangesAsync(string[] ipRanges, CancellationToken cancellationToken) + /// + /// Create a list of hosts from a string input like "10.0.0.1; example.com; 10.0.0.0/24" + /// + /// Hosts like "10.0.0.1; example.com; 10.0.0.0/24" + /// List of hosts. + public static IEnumerable CreateListFromInput(string hosts) { - return Task.Run(() => CreateIPAddressesFromIPRanges(ipRanges, cancellationToken), cancellationToken); + return hosts.Replace(" ", "").Split(';') + .Where(x => !string.IsNullOrEmpty(x)) + .Select(x => x.Trim()) + .ToArray(); } - public static IPAddress[] CreateIPAddressesFromIPRanges(string[] ipRanges, CancellationToken cancellationToken) + public static Task<(List<(IPAddress ipAddress, string hostname)> hosts, List hostnamesNotResolved)> ResolveAsync(IEnumerable hosts, bool dnsResolveHostnamePreferIPv4, CancellationToken cancellationToken) { - var bag = new ConcurrentBag(); + return Task.Run(() => Resolve(hosts, dnsResolveHostnamePreferIPv4, cancellationToken), cancellationToken); + } - var parallelOptions = new ParallelOptions - { - CancellationToken = cancellationToken - }; + private static (List<(IPAddress ipAddress, string hostname)> hosts, List hostnamesNotResolved) Resolve(IEnumerable hosts, bool dnsResolveHostnamePreferIPv4, CancellationToken cancellationToken) + { + var hostsBag = new ConcurrentBag<(IPAddress ipAddress, string hostname)>(); + var hostnamesNotResovledBag = new ConcurrentBag(); - foreach (var ipOrRange in ipRanges) + Parallel.ForEach(hosts, new ParallelOptions { CancellationToken = cancellationToken }, host => { - switch (ipOrRange) + switch (host) { - // 192.168.0.1 or 2001:db8:85a3::8a2e:370:7334 - case var _ when Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressRegex) || Regex.IsMatch(ipOrRange, RegexHelper.IPv6AddressRegex): - bag.Add(IPAddress.Parse(ipOrRange)); + // 192.168.0.1 + case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressRegex): + // 2001:db8:85a3::8a2e:370:7334 + case var _ when Regex.IsMatch(host, RegexHelper.IPv6AddressRegex): + hostsBag.Add((IPAddress.Parse(host), string.Empty)); + break; + + // 192.168.0.0/24 + case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressCidrRegex): + // 192.168.0.0/255.255.255.0 + case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressSubnetmaskRegex): + var network = IPNetwork2.System.Net.IPNetwork.Parse(host); - // 192.168.0.0/24 or 192.168.0.0/255.255.255.0 - case var _ when Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressCidrRegex) || Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressSubnetmaskRegex): - var network = IPNetwork2.System.Net.IPNetwork.Parse(ipOrRange); - - Parallel.For(IPv4Address.ToInt32(network.Network), IPv4Address.ToInt32(network.Broadcast) + 1, parallelOptions, i => + Parallel.For(IPv4Address.ToInt32(network.Network), IPv4Address.ToInt32(network.Broadcast) + 1, (i, state) => { - bag.Add(IPv4Address.FromInt32(i)); + if (cancellationToken.IsCancellationRequested) + state.Break(); - parallelOptions.CancellationToken.ThrowIfCancellationRequested(); + hostsBag.Add((IPv4Address.FromInt32(i), string.Empty)); }); - + break; + + // 192.168.0.0 - 192.168.0.100 + case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressRangeRegex): + var range = host.Split('-'); - // 192.168.0.0 - 192.168.0.100 - case var _ when Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressRangeRegex): - var range = ipOrRange.Split('-'); - - Parallel.For(IPv4Address.ToInt32(IPAddress.Parse(range[0])), IPv4Address.ToInt32(IPAddress.Parse(range[1])) + 1, parallelOptions, i => + Parallel.For(IPv4Address.ToInt32(IPAddress.Parse(range[0])), IPv4Address.ToInt32(IPAddress.Parse(range[1])) + 1, (i, state) => { - bag.Add(IPv4Address.FromInt32(i)); + if (cancellationToken.IsCancellationRequested) + state.Break(); - parallelOptions.CancellationToken.ThrowIfCancellationRequested(); + hostsBag.Add((IPv4Address.FromInt32(i), string.Empty)); }); break; + + // 192.168.[50-100].1 + case var _ when Regex.IsMatch(host, RegexHelper.IPv4AddressSpecialRangeRegex): + var octets = host.Split('.'); - // 192.168.[50-100,200].1 --> 192.168.50.1, 192.168.51.1, 192.168.52.1, {..}, 192.168.200.1 - case var _ when Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressSpecialRangeRegex): - var octets = ipOrRange.Split('.'); - - var list = new List>(); + var list = new List>(); // Go through each octet... foreach (var octet in octets) { - var innerList = new List(); + var innerList = new ConcurrentBag(); // Create a range for each octet if (Regex.IsMatch(octet, RegexHelper.SpecialRangeRegex)) @@ -85,10 +105,13 @@ public static IPAddress[] CreateIPAddressesFromIPRanges(string[] ipRanges, Cance { var rangeNumbers = numberOrRange.Split('-'); - for (var i = int.Parse(rangeNumbers[0]); i < (int.Parse(rangeNumbers[1]) + 1); i++) + Parallel.For(int.Parse(rangeNumbers[0]), int.Parse(rangeNumbers[1]) + 1, (i, state) => { + if (cancellationToken.IsCancellationRequested) + state.Break(); + innerList.Add(i); - } + }); } // 200 else { @@ -105,101 +128,81 @@ public static IPAddress[] CreateIPAddressesFromIPRanges(string[] ipRanges, Cance } // Build the new ipv4 - foreach (var i in list[0]) + Parallel.ForEach(list[0], new ParallelOptions { CancellationToken = cancellationToken }, i => { - foreach (var j in list[1]) + Parallel.ForEach(list[1], new ParallelOptions { CancellationToken = cancellationToken }, j => { - foreach (var k in list[2]) + Parallel.ForEach(list[2], new ParallelOptions { CancellationToken = cancellationToken }, k => { - foreach (var h in list[3]) + Parallel.ForEach(list[3], new ParallelOptions { CancellationToken = cancellationToken }, h => { - bag.Add(IPAddress.Parse($"{i}.{j}.{k}.{h}")); - } - } - } - } + hostsBag.Add((IPAddress.Parse($"{i}.{j}.{k}.{h}"), string.Empty)); + }); + }); + }); + }); break; - } - } - - return bag.ToArray(); - } - public static Task> ResolveHostnamesInIPRangesAsync(string[] ipRanges, bool dnsResolveHostnamePreferIPv4, CancellationToken cancellationToken) - { - return Task.Run(() => ResolveHostnamesInIPRanges(ipRanges, dnsResolveHostnamePreferIPv4, cancellationToken), cancellationToken); - } - - public static List ResolveHostnamesInIPRanges(string[] ipRanges, bool dnsResolveHostnamePreferIPv4, CancellationToken cancellationToken) - { - var bag = new ConcurrentBag(); - - var exceptions = new ConcurrentQueue(); - - Parallel.ForEach(ipRanges, new ParallelOptions { CancellationToken = cancellationToken }, ipHostOrRange => - { - switch (ipHostOrRange) - { - // 192.168.0.1 - case var _ when Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressRegex): - // 192.168.0.0/24 - case var _ when Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressCidrRegex): - // 192.168.0.0/255.255.255.0 - case var _ when Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressSubnetmaskRegex): - // 192.168.0.0 - 192.168.0.100 - case var _ when Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressRangeRegex): - // 192.168.[50-100].1 - case var _ when Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressSpecialRangeRegex): - // 2001:db8:85a3::8a2e:370:7334 - case var _ when Regex.IsMatch(ipHostOrRange, RegexHelper.IPv6AddressRegex): - bag.Add(ipHostOrRange); - break; - // example.com - case var _ when Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameOrDomainRegex): - using (var dnsResolverTask = DNSClientHelper.ResolveAorAaaaAsync(ipHostOrRange, dnsResolveHostnamePreferIPv4)) + case var _ when Regex.IsMatch(host, RegexHelper.HostnameOrDomainRegex): + using (var dnsResolverTask = DNSClientHelper.ResolveAorAaaaAsync(host, dnsResolveHostnamePreferIPv4)) { // Wait for task inside a Parallel.Foreach - dnsResolverTask.Wait(); + dnsResolverTask.Wait(cancellationToken); if (!dnsResolverTask.Result.HasError) - bag.Add($"{dnsResolverTask.Result.Value}"); + hostsBag.Add((IPAddress.Parse($"{dnsResolverTask.Result.Value}"), host)); else - exceptions.Enqueue(new HostNotFoundException(ipHostOrRange)); + hostnamesNotResovledBag.Add(host); } break; - + // example.com/24 or example.com/255.255.255.128 - case var _ when Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameOrDomainWithCidrRegex) || Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameOrDomainWithSubnetmaskRegex): - var hostAndSubnet = ipHostOrRange.Split('/'); + case var _ when Regex.IsMatch(host, RegexHelper.HostnameOrDomainWithCidrRegex): + case var _ when Regex.IsMatch(host, RegexHelper.HostnameOrDomainWithSubnetmaskRegex): + var hostAndSubnet = host.Split('/'); // Only support IPv4 using (var dnsResolverTask = DNSClientHelper.ResolveAorAaaaAsync(hostAndSubnet[0], true)) { // Wait for task inside a Parallel.Foreach - dnsResolverTask.Wait(); + dnsResolverTask.Wait(cancellationToken); if (!dnsResolverTask.Result.HasError) { // Only support IPv4 for ranges for now if (dnsResolverTask.Result.Value.AddressFamily == AddressFamily.InterNetwork) - bag.Add($"{dnsResolverTask.Result.Value}/{hostAndSubnet[1]}"); + { + network = IPNetwork2.System.Net.IPNetwork.Parse($"{dnsResolverTask.Result.Value}/{hostAndSubnet[1]}"); + + Parallel.For(IPv4Address.ToInt32(network.Network), IPv4Address.ToInt32(network.Broadcast) + 1, (i, state) => + { + if (cancellationToken.IsCancellationRequested) + state.Break(); + + hostsBag.Add((IPv4Address.FromInt32(i), string.Empty)); + }); + } else - exceptions.Enqueue(new HostNotFoundException(hostAndSubnet[0])); + { + hostnamesNotResovledBag.Add(hostAndSubnet[0]); + } } else - exceptions.Enqueue(new HostNotFoundException(hostAndSubnet[0])); + { + hostnamesNotResovledBag.Add(hostAndSubnet[0]); + } } break; } }); - if (!exceptions.IsEmpty) - throw new AggregateException(exceptions); + // Sort list and return + IPAddressComparer comparer = new(); - return bag.ToList(); + return ([.. hostsBag.OrderBy(x => x.ipAddress, comparer)], [.. hostnamesNotResovledBag]); } -} \ No newline at end of file +} diff --git a/Source/NETworkManager.Models/Network/HostnameArgs.cs b/Source/NETworkManager.Models/Network/HostnameArgs.cs new file mode 100644 index 0000000000..5da655b21a --- /dev/null +++ b/Source/NETworkManager.Models/Network/HostnameArgs.cs @@ -0,0 +1,23 @@ +using System; + +namespace NETworkManager.Models.Network; + +/// +/// Contains the information of a resolved hostname in a . +/// +public class HostnameArgs : EventArgs +{ + /// + /// Hostname. + /// + public string Hostname { get; } + + /// + /// Creates a new instance of with the given hostname. + /// + /// + public HostnameArgs(string hostname) + { + Hostname = hostname; + } +} diff --git a/Source/NETworkManager.Models/Network/IPScanner.cs b/Source/NETworkManager.Models/Network/IPScanner.cs index 31128163b2..c41d41d860 100644 --- a/Source/NETworkManager.Models/Network/IPScanner.cs +++ b/Source/NETworkManager.Models/Network/IPScanner.cs @@ -24,11 +24,11 @@ public sealed class IPScanner #region Events - public event EventHandler HostFound; + public event EventHandler HostScanned; - private void OnHostFound(IPScannerHostScannedArgs e) + private void OnHostScanned(IPScannerHostScannedArgs e) { - HostFound?.Invoke(this, e); + HostScanned?.Invoke(this, e); } public event EventHandler ScanComplete; @@ -65,7 +65,7 @@ public IPScanner(IPScannerOptions options) #region Methods - public void ScanAsync(IPAddress[] ipAddresses, CancellationToken cancellationToken) + public void ScanAsync(IEnumerable<(IPAddress ipAddress, string hostname)> hosts, CancellationToken cancellationToken) { // Start the scan in a separate task Task.Run(() => @@ -92,10 +92,10 @@ public void ScanAsync(IPAddress[] ipAddresses, CancellationToken cancellationTok }; // Start scan - Parallel.ForEach(ipAddresses, hostParallelOptions, ipAddress => + Parallel.ForEach(hosts, hostParallelOptions, host => { // Start ping async - var pingTask = PingAsync(ipAddress, cancellationToken); + var pingTask = PingAsync(host.ipAddress, cancellationToken); // Start port scan async ConcurrentBag portResults = new(); @@ -105,13 +105,13 @@ public void ScanAsync(IPAddress[] ipAddresses, CancellationToken cancellationTok Parallel.ForEach(_options.PortScanPorts, portParallelOptions, port => { // Test if port is open - using var tcpClient = new TcpClient(ipAddress.AddressFamily); + using var tcpClient = new TcpClient(host.ipAddress.AddressFamily); var portState = PortState.None; try { - var task = tcpClient.ConnectAsync(ipAddress, port); + var task = tcpClient.ConnectAsync(host.ipAddress, port); if (task.Wait(_options.PortScanTimeout)) portState = tcpClient.Connected ? PortState.Open : PortState.Closed; @@ -153,7 +153,7 @@ public void ScanAsync(IPAddress[] ipAddresses, CancellationToken cancellationTok if (_options.ResolveHostname) { // Don't use await in Parallel.ForEach, this will break - var dnsResolverTask = DNSClient.GetInstance().ResolvePtrAsync(ipAddress); + var dnsResolverTask = DNSClient.GetInstance().ResolvePtrAsync(host.ipAddress); // Wait for task inside a Parallel.Foreach dnsResolverTask.Wait(); @@ -174,7 +174,7 @@ public void ScanAsync(IPAddress[] ipAddresses, CancellationToken cancellationTok { // Get info from arp table var arpTableInfo = ARP.GetTable() - .FirstOrDefault(p => p.IPAddress.ToString() == ipAddress.ToString()); + .FirstOrDefault(p => p.IPAddress.ToString() == host.ipAddress.ToString()); if (arpTableInfo != null) macAddress = arpTableInfo.MACAddress; @@ -183,7 +183,7 @@ public void ScanAsync(IPAddress[] ipAddresses, CancellationToken cancellationTok if (macAddress == null) { var networkInterfaceInfo = networkInterfaces.FirstOrDefault(p => - p.IPv4Address.Any(x => x.Item1.Equals(ipAddress))); + p.IPv4Address.Any(x => x.Item1.Equals(host.ipAddress))); if (networkInterfaceInfo != null) macAddress = networkInterfaceInfo.PhysicalAddress; @@ -199,7 +199,7 @@ public void ScanAsync(IPAddress[] ipAddresses, CancellationToken cancellationTok } } - OnHostFound(new IPScannerHostScannedArgs( + OnHostScanned(new IPScannerHostScannedArgs( new IPScannerHostInfo( isReachable, pingInfo, isAnyPortOpen, portResults.OrderBy(x => x.Port).ToList(), hostname, macAddress, vendor))); diff --git a/Source/NETworkManager.Models/Network/Ping.cs b/Source/NETworkManager.Models/Network/Ping.cs index df7707a451..d66b9f1355 100644 --- a/Source/NETworkManager.Models/Network/Ping.cs +++ b/Source/NETworkManager.Models/Network/Ping.cs @@ -16,8 +16,7 @@ public sealed class Ping public byte[] Buffer = new byte[32]; public int TTL = 64; public bool DontFragment = true; - public string Hostname = string.Empty; - + private const int ExceptionCancelCount = 3; #endregion @@ -43,6 +42,13 @@ private void OnPingException(PingExceptionArgs e) PingException?.Invoke(this, e); } + public event EventHandler HostnameResolved; + + private void OnHostnameResolved(HostnameArgs e) + { + HostnameResolved?.Invoke(this, e); + } + public event EventHandler UserHasCanceled; private void OnUserHasCanceled() @@ -56,17 +62,18 @@ public void SendAsync(IPAddress ipAddress, CancellationToken cancellationToken) { Task.Run(async () => { - var hostname = Hostname; + var hostname = string.Empty; // Try to resolve PTR - if (string.IsNullOrEmpty(hostname)) - { - var dnsResult = await DNSClient.GetInstance().ResolvePtrAsync(ipAddress); + var dnsResult = await DNSClient.GetInstance().ResolvePtrAsync(ipAddress); - if (!dnsResult.HasError) - hostname = dnsResult.Value; + if (!dnsResult.HasError) + { + hostname = dnsResult.Value; + + OnHostnameResolved(new HostnameArgs(hostname)); } - + var errorCount = 0; var options = new PingOptions diff --git a/Source/NETworkManager.Models/Network/PingMonitorOptions.cs b/Source/NETworkManager.Models/Network/PingMonitorOptions.cs deleted file mode 100644 index 9de14c3fb5..0000000000 --- a/Source/NETworkManager.Models/Network/PingMonitorOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Net; - -namespace NETworkManager.Models.Network; - -public class PingMonitorOptions -{ - public string Host { get; set; } - public IPAddress IPAddress { get; set; } - - public PingMonitorOptions(string host, IPAddress ipAddress) - { - Host = host; - IPAddress = ipAddress; - } -} diff --git a/Source/NETworkManager.Models/Network/PortScanner.cs b/Source/NETworkManager.Models/Network/PortScanner.cs index a8b6329784..6ead46e40a 100644 --- a/Source/NETworkManager.Models/Network/PortScanner.cs +++ b/Source/NETworkManager.Models/Network/PortScanner.cs @@ -1,6 +1,7 @@ using NETworkManager.Models.Lookup; using NETworkManager.Utilities; using System; +using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; @@ -54,7 +55,7 @@ public PortScanner(PortScannerOptions options) #endregion #region Methods - public void ScanAsync(IPAddress[] ipAddresses, int[] ports, CancellationToken cancellationToken) + public void ScanAsync(IEnumerable<(IPAddress ipAddress, string hostname)> hosts, IEnumerable ports, CancellationToken cancellationToken) { _progressValue = 0; @@ -74,15 +75,15 @@ public void ScanAsync(IPAddress[] ipAddresses, int[] ports, CancellationToken ca MaxDegreeOfParallelism = _options.MaxPortThreads }; - Parallel.ForEach(ipAddresses, hostParallelOptions, ipAddress => + Parallel.ForEach(hosts, hostParallelOptions, host => { // Resolve Hostname (PTR) var hostname = string.Empty; if (_options.ResolveHostname) { - // Don't use await in Paralle.ForEach, this will break - var dnsResolverTask = DNSClient.GetInstance().ResolvePtrAsync(ipAddress); + // Don't use await in Parallel.ForEach, this will break + var dnsResolverTask = DNSClient.GetInstance().ResolvePtrAsync(host.ipAddress); // Wait for task inside a Parallel.Foreach dnsResolverTask.Wait(cancellationToken); @@ -95,13 +96,13 @@ public void ScanAsync(IPAddress[] ipAddresses, int[] ports, CancellationToken ca Parallel.ForEach(ports, portParallelOptions, port => { // Test if port is open - using (var tcpClient = new TcpClient(ipAddress.AddressFamily)) + using (var tcpClient = new TcpClient(host.ipAddress.AddressFamily)) { var portState = PortState.None; try { - var task = tcpClient.ConnectAsync(ipAddress, port); + var task = tcpClient.ConnectAsync(host.ipAddress, port); if (task.Wait(_options.Timeout)) portState = tcpClient.Connected ? PortState.Open : PortState.Closed; @@ -118,7 +119,7 @@ public void ScanAsync(IPAddress[] ipAddresses, int[] ports, CancellationToken ca if (_options.ShowAllResults || portState == PortState.Open) OnPortScanned(new PortScannerPortScannedArgs( - new PortScannerPortInfo(ipAddress, hostname, port, PortLookup.LookupByPortAndProtocol(port), portState))); + new PortScannerPortInfo(host.ipAddress, hostname, port, PortLookup.LookupByPortAndProtocol(port), portState))); } } diff --git a/Source/NETworkManager.Models/Network/PortScannerPortInfo.cs b/Source/NETworkManager.Models/Network/PortScannerPortInfo.cs index d8fc4ebc64..c39385b276 100644 --- a/Source/NETworkManager.Models/Network/PortScannerPortInfo.cs +++ b/Source/NETworkManager.Models/Network/PortScannerPortInfo.cs @@ -23,6 +23,11 @@ public class PortScannerPortInfo : PortInfo /// public int IPAddressInt32 => IPAddress is { AddressFamily: System.Net.Sockets.AddressFamily.InterNetwork } ? IPv4Address.ToInt32(IPAddress) : 0; + /// + /// Hostname (if available) or IP address of the host. + /// + public string HostAsString => string.IsNullOrEmpty(Hostname) ? IPAddress.ToString() : Hostname; + /// /// Creates a new instance of with the specified parameters. /// diff --git a/Source/NETworkManager.Models/Network/Traceroute.cs b/Source/NETworkManager.Models/Network/Traceroute.cs index 382bbe60b0..830247b30f 100644 --- a/Source/NETworkManager.Models/Network/Traceroute.cs +++ b/Source/NETworkManager.Models/Network/Traceroute.cs @@ -11,7 +11,7 @@ namespace NETworkManager.Models.Network; -public class Traceroute +public sealed class Traceroute { #region Variables @@ -23,35 +23,35 @@ public class Traceroute public event EventHandler HopReceived; - protected virtual void OnHopReceived(TracerouteHopReceivedArgs e) + private void OnHopReceived(TracerouteHopReceivedArgs e) { HopReceived?.Invoke(this, e); } public event EventHandler TraceComplete; - protected virtual void OnTraceComplete() + private void OnTraceComplete() { TraceComplete?.Invoke(this, EventArgs.Empty); } public event EventHandler MaximumHopsReached; - protected virtual void OnMaximumHopsReached(MaximumHopsReachedArgs e) + private void OnMaximumHopsReached(MaximumHopsReachedArgs e) { MaximumHopsReached?.Invoke(this, e); } public event EventHandler TraceError; - protected virtual void OnTraceError(TracerouteErrorArgs e) + private void OnTraceError(TracerouteErrorArgs e) { TraceError?.Invoke(this, e); } public event EventHandler UserHasCanceled; - protected virtual void OnUserHasCanceled() + private void OnUserHasCanceled() { UserHasCanceled?.Invoke(this, EventArgs.Empty); } diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs index c07aed6135..37129e4094 100644 --- a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs +++ b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs @@ -110,6 +110,7 @@ public static class GlobalStaticConfiguration public static int PingMonitor_Timeout => 4000; public static int PingMonitor_TTL => 64; public static int PingMonitor_WaitTime => 1000; + public static bool PingMonitor_ExpandHostView => false; public static ExportFileType PingMonitor_ExportFileType => ExportFileType.Csv; // Application: Traceroute diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs index 99d7ea77da..690f668d29 100644 --- a/Source/NETworkManager.Settings/SettingsInfo.cs +++ b/Source/NETworkManager.Settings/SettingsInfo.cs @@ -1400,6 +1400,21 @@ public int PingMonitor_WaitTime OnPropertyChanged(); } } + + private bool _pingMonitor_ExpandHostView = GlobalStaticConfiguration.PingMonitor_ExpandHostView; + + public bool PingMonitor_ExpandHostView + { + get => _pingMonitor_ExpandHostView; + set + { + if (value == _pingMonitor_ExpandHostView) + return; + + _pingMonitor_ExpandHostView = value; + OnPropertyChanged(); + } + } private string _pingMonitor_ExportFilePath; @@ -3713,7 +3728,7 @@ public double WakeOnLAN_ProfileWidth #endregion - #region Whois + #region Whois private ObservableCollection _whois_DomainHistory = new(); diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs index 05fc0cc665..1c31fcd1ca 100644 --- a/Source/NETworkManager.Settings/SettingsManager.cs +++ b/Source/NETworkManager.Settings/SettingsManager.cs @@ -180,6 +180,10 @@ public static void Upgrade(Version fromVersion, Version toVersion) if (fromVersion < new Version(2023, 6, 27, 0)) UpgradeTo_2023_6_27_0(); + // 2023.11.28.0 + if (fromVersion < new Version(2023, 11, 28, 0)) + UpgradeTo_2023_11_28_0(); + // Latest if (fromVersion < toVersion) UpgradeToLatest(toVersion); @@ -286,12 +290,11 @@ private static void UpgradeTo_2023_6_27_0() } /// - /// Method to apply changes for the latest version. + /// Method to apply changes for version 2023.11.28.0. /// - /// Latest version. - private static void UpgradeToLatest(Version version) + private static void UpgradeTo_2023_11_28_0() { - Log.Info($"Apply upgrade to {version}..."); + Log.Info($"Apply upgrade to 2023.11.28.0..."); // First run is required due to the new settings Log.Info("Set \"FirstRun\" to true..."); @@ -305,5 +308,14 @@ private static void UpgradeToLatest(Version version) Log.Info("Init \"DNSLookup_DNSServers\" with default DNS servers..."); Current.DNSLookup_DNSServers = new ObservableCollection(DNSServer.GetDefaultList()); } + + /// + /// Method to apply changes for the latest version. + /// + /// Latest version. + private static void UpgradeToLatest(Version version) + { + Log.Info($"Apply upgrade to {version}..."); + } #endregion } diff --git a/Source/NETworkManager.Utilities/DNSClient.cs b/Source/NETworkManager.Utilities/DNSClient.cs index 6cd1c59dbc..66b69cde9d 100644 --- a/Source/NETworkManager.Utilities/DNSClient.cs +++ b/Source/NETworkManager.Utilities/DNSClient.cs @@ -11,7 +11,7 @@ public class DNSClient : SingletonBase /// /// Error message which is returned when the DNS client is not configured. /// - private static readonly string _notConfiguredMessage = "DNS client is not configured. Call Configure() first."; + private const string NotConfiguredMessage = "DNS client is not configured. Call Configure() first."; /// /// Store the current DNS settings. @@ -41,13 +41,10 @@ public void Configure(DNSClientSettings settings) // Setup custom DNS servers List servers = new(); - foreach (var (Server, Port) in _settings.DNSServers) - servers.Add(new IPEndPoint(IPAddress.Parse(Server), Port)); + foreach (var (server, port) in _settings.DNSServers) + servers.Add(new IPEndPoint(IPAddress.Parse(server), port)); - _client = new LookupClient(new LookupClientOptions(servers.ToArray()) - { - - }); + _client = new LookupClient(new LookupClientOptions(servers.ToArray())); } else { @@ -78,7 +75,7 @@ public void UpdateFromWindows() public async Task ResolveAAsync(string query) { if (!_isConfigured) - throw new DNSClientNotConfiguredException(_notConfiguredMessage); + throw new DNSClientNotConfiguredException(NotConfiguredMessage); try { @@ -91,10 +88,9 @@ public async Task ResolveAAsync(string query) // Validate result because of https://github.com/BornToBeRoot/NETworkManager/issues/1934 var record = result.Answers.ARecords().FirstOrDefault(); - if (record != null) - return new DNSClientResultIPAddress(record.Address, $"{result.NameServer}"); - else - return new DNSClientResultIPAddress(true, $"IP address for \"{query}\" could not be resolved and the DNS server did not return an error. Try to check your DNS server with: dig @{result.NameServer.Address} {query}", $"{result.NameServer}"); + return record != null ? + new DNSClientResultIPAddress(record.Address, $"{result.NameServer}") : + new DNSClientResultIPAddress(true, $"IP address for \"{query}\" could not be resolved and the DNS server did not return an error. Try to check your DNS server with: dig @{result.NameServer.Address} {query}", $"{result.NameServer}"); } catch (DnsResponseException ex) { @@ -110,7 +106,7 @@ public async Task ResolveAAsync(string query) public async Task ResolveAaaaAsync(string query) { if (!_isConfigured) - throw new DNSClientNotConfiguredException(_notConfiguredMessage); + throw new DNSClientNotConfiguredException(NotConfiguredMessage); try { @@ -123,10 +119,9 @@ public async Task ResolveAaaaAsync(string query) // Validate result because of https://github.com/BornToBeRoot/NETworkManager/issues/1934 var record = result.Answers.AaaaRecords().FirstOrDefault(); - if (record != null) - return new DNSClientResultIPAddress(record.Address, $"{result.NameServer}"); - else - return new DNSClientResultIPAddress(true, $"IP address for \"{query}\" could not be resolved and the DNS server did not return an error. Try to check your DNS server with: dig @{result.NameServer.Address} {query}", $"{result.NameServer}"); + return record != null ? + new DNSClientResultIPAddress(record.Address, $"{result.NameServer}") : + new DNSClientResultIPAddress(true, $"IP address for \"{query}\" could not be resolved and the DNS server did not return an error. Try to check your DNS server with: dig @{result.NameServer.Address} {query}", $"{result.NameServer}"); } catch (DnsResponseException ex) { @@ -142,7 +137,7 @@ public async Task ResolveAaaaAsync(string query) public async Task ResolveCnameAsync(string query) { if (!_isConfigured) - throw new DNSClientNotConfiguredException(_notConfiguredMessage); + throw new DNSClientNotConfiguredException(NotConfiguredMessage); try { @@ -155,10 +150,9 @@ public async Task ResolveCnameAsync(string query) // Validate result because of https://github.com/BornToBeRoot/NETworkManager/issues/1934 var record = result.Answers.CnameRecords().FirstOrDefault(); - if (record != null) - return new DNSClientResultString(record.CanonicalName, $"{result.NameServer}"); - else - return new DNSClientResultString(true, $"CNAME for \"{query}\" could not be resolved and the DNS server did not return an error. Try to check your DNS server with: dig @{result.NameServer.Address} {query}", $"{result.NameServer}"); + return record != null ? + new DNSClientResultString(record.CanonicalName, $"{result.NameServer}") : + new DNSClientResultString(true, $"CNAME for \"{query}\" could not be resolved and the DNS server did not return an error. Try to check your DNS server with: dig @{result.NameServer.Address} {query}", $"{result.NameServer}"); } catch (DnsResponseException ex) { @@ -174,7 +168,7 @@ public async Task ResolveCnameAsync(string query) public async Task ResolvePtrAsync(IPAddress ipAddress) { if (!_isConfigured) - throw new DNSClientNotConfiguredException(_notConfiguredMessage); + throw new DNSClientNotConfiguredException(NotConfiguredMessage); try { @@ -187,10 +181,9 @@ public async Task ResolvePtrAsync(IPAddress ipAddress) // Validate result because of https://github.com/BornToBeRoot/NETworkManager/issues/1934 var record = result.Answers.PtrRecords().FirstOrDefault(); - if (record != null) - return new DNSClientResultString(record.PtrDomainName, $"{result.NameServer}"); - else - return new DNSClientResultString(true, $"PTR for \"{ipAddress}\" could not be resolved and the DNS server did not return an error. Try to check your DNS server with: dig @{result.NameServer.Address} -x {ipAddress}", $"{result.NameServer}"); + return record != null ? + new DNSClientResultString(record.PtrDomainName, $"{result.NameServer}"): + new DNSClientResultString(true, $"PTR for \"{ipAddress}\" could not be resolved and the DNS server did not return an error. Try to check your DNS server with: dig @{result.NameServer.Address} -x {ipAddress}", $"{result.NameServer}"); } catch (DnsResponseException ex) { diff --git a/Source/NETworkManager.Utilities/IPAddressComparer.cs b/Source/NETworkManager.Utilities/IPAddressComparer.cs new file mode 100644 index 0000000000..410a5f06eb --- /dev/null +++ b/Source/NETworkManager.Utilities/IPAddressComparer.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Net; + +namespace NETworkManager.Utilities +{ + public class IPAddressComparer : IComparer + { + public int Compare(IPAddress first, IPAddress second) + { + return IPAddressHelper.CompareIPAddresses(first, second); + } + } +} diff --git a/Source/NETworkManager.Utilities/IPAddressHelper.cs b/Source/NETworkManager.Utilities/IPAddressHelper.cs index 7b136d9d78..2db98a4a58 100644 --- a/Source/NETworkManager.Utilities/IPAddressHelper.cs +++ b/Source/NETworkManager.Utilities/IPAddressHelper.cs @@ -4,6 +4,12 @@ namespace NETworkManager.Utilities; public static class IPAddressHelper { + + /// + /// Test if an IP address is a private address. + /// + /// IP address. + /// True if the IP address is a private address, otherwise false. public static bool IsPrivateIPAddress(IPAddress ipAddress) { var addressBytes = ipAddress.GetAddressBytes(); @@ -21,4 +27,22 @@ public static bool IsPrivateIPAddress(IPAddress ipAddress) return false; } } + + /// + /// Compare two IP addresses. + /// + /// First IP address. + /// Second IP address. + /// 0 if the IP addresses are equal, otherwise a negative or positive value. + public static int CompareIPAddresses(IPAddress first, IPAddress second) + { + byte[] bytesFirst = first.GetAddressBytes(); + byte[] bytesSecond = second.GetAddressBytes(); + + for (int i = 0; i < bytesFirst.Length; i++) + if (bytesFirst[i] != bytesSecond[i]) + return bytesFirst[i] - bytesSecond[i]; + + return 0; // IP addresses are equal + } } \ No newline at end of file diff --git a/Source/NETworkManager/ProfileDialogManager.cs b/Source/NETworkManager/ProfileDialogManager.cs index 29ea6988d2..3985a34043 100644 --- a/Source/NETworkManager/ProfileDialogManager.cs +++ b/Source/NETworkManager/ProfileDialogManager.cs @@ -21,7 +21,7 @@ public static class ProfileDialogManager #region Dialog to add, edit, copy as and delete profile - public static async Task ShowAddProfileDialog(IProfileManagerMinimal viewModel, + public static Task ShowAddProfileDialog(IProfileManagerMinimal viewModel, IDialogCoordinator dialogCoordinator, ProfileInfo profile = null, string group = null, ApplicationName applicationName = ApplicationName.None) { @@ -50,10 +50,10 @@ public static async Task ShowAddProfileDialog(IProfileManagerMinimal viewModel, viewModel.OnProfileManagerDialogOpen(); - await dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); + return dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); } - public static async Task ShowEditProfileDialog(IProfileManagerMinimal viewModel, + public static Task ShowEditProfileDialog(IProfileManagerMinimal viewModel, IDialogCoordinator dialogCoordinator, ProfileInfo profile) { CustomDialog customDialog = new() @@ -80,10 +80,11 @@ public static async Task ShowEditProfileDialog(IProfileManagerMinimal viewModel, }; viewModel.OnProfileManagerDialogOpen(); - await dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); + + return dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); } - public static async Task ShowCopyAsProfileDialog(IProfileManagerMinimal viewModel, + public static Task ShowCopyAsProfileDialog(IProfileManagerMinimal viewModel, IDialogCoordinator dialogCoordinator, ProfileInfo profile) { CustomDialog customDialog = new() @@ -110,10 +111,11 @@ public static async Task ShowCopyAsProfileDialog(IProfileManagerMinimal viewMode }; viewModel.OnProfileManagerDialogOpen(); - await dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); + + return dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); } - public static async Task ShowDeleteProfileDialog(IProfileManagerMinimal viewModel, + public static Task ShowDeleteProfileDialog(IProfileManagerMinimal viewModel, IDialogCoordinator dialogCoordinator, IList profiles) { CustomDialog customDialog = new() @@ -144,14 +146,15 @@ public static async Task ShowDeleteProfileDialog(IProfileManagerMinimal viewMode }; viewModel.OnProfileManagerDialogOpen(); - await dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); + + return dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); } #endregion #region Dialog to add, edit and delete group - public static async Task ShowAddGroupDialog(IProfileManagerMinimal viewModel, IDialogCoordinator dialogCoordinator) + public static Task ShowAddGroupDialog(IProfileManagerMinimal viewModel, IDialogCoordinator dialogCoordinator) { CustomDialog customDialog = new() { @@ -177,10 +180,11 @@ public static async Task ShowAddGroupDialog(IProfileManagerMinimal viewModel, ID }; viewModel.OnProfileManagerDialogOpen(); - await dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); + + return dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); } - public static async Task ShowEditGroupDialog(IProfileManagerMinimal viewModel, IDialogCoordinator dialogCoordinator, + public static Task ShowEditGroupDialog(IProfileManagerMinimal viewModel, IDialogCoordinator dialogCoordinator, GroupInfo group) { CustomDialog customDialog = new() @@ -207,10 +211,11 @@ public static async Task ShowEditGroupDialog(IProfileManagerMinimal viewModel, I }; viewModel.OnProfileManagerDialogOpen(); - await dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); + + return dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); } - public static async Task ShowDeleteGroupDialog(IProfileManagerMinimal viewModel, + public static Task ShowDeleteGroupDialog(IProfileManagerMinimal viewModel, IDialogCoordinator dialogCoordinator, GroupInfo group) { CustomDialog customDialog = new() @@ -236,7 +241,8 @@ public static async Task ShowDeleteGroupDialog(IProfileManagerMinimal viewModel, }; viewModel.OnProfileManagerDialogOpen(); - await dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); + + return dialogCoordinator.ShowMetroDialogAsync(viewModel, customDialog); } #endregion diff --git a/Source/NETworkManager/Resources/Styles/ListBoxStyle.xaml b/Source/NETworkManager/Resources/Styles/ListBoxStyle.xaml index d80bbe15e8..12fdcb9b33 100644 --- a/Source/NETworkManager/Resources/Styles/ListBoxStyle.xaml +++ b/Source/NETworkManager/Resources/Styles/ListBoxStyle.xaml @@ -12,14 +12,6 @@ \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs index 8d983725a2..80a8728335 100644 --- a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs @@ -165,10 +165,10 @@ public DNSLookupHostViewModel(IDialogCoordinator instance) var tabId = Guid.NewGuid(); - TabItems = new ObservableCollection - { + TabItems = + [ new(Localization.Resources.Strings.NewTab, new DNSLookupView (tabId), tabId) - }; + ]; // Profiles SetProfilesView(); diff --git a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs index 82c70bf336..18a788f51b 100644 --- a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs +++ b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs @@ -221,8 +221,8 @@ public DNSLookupViewModel(IDialogCoordinator instance, Guid tabId, string host) DNSServers.SourceCollection.Cast().First(); ResultsView = CollectionViewSource.GetDefaultView(Results); - ResultsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(DNSLookupRecordInfo.IPEndPoint))); - ResultsView.SortDescriptions.Add(new SortDescription(nameof(DNSLookupRecordInfo.IPEndPoint), + ResultsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(DNSLookupRecordInfo.NameServerAsString))); + ResultsView.SortDescriptions.Add(new SortDescription(nameof(DNSLookupRecordInfo.NameServerIPAddress), ListSortDirection.Descending)); LoadSettings(); @@ -406,7 +406,7 @@ private void AddHostToHistory(string host) public void SortResultByPropertyName(string sortDescription) { ResultsView.SortDescriptions.Clear(); - ResultsView.SortDescriptions.Add(new SortDescription(nameof(DNSLookupRecordInfo.Server), + ResultsView.SortDescriptions.Add(new SortDescription(nameof(DNSLookupRecordInfo.NameServerIPAddress), ListSortDirection.Descending)); if (_lastSortDescriptionAscending.Equals(sortDescription)) diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index 19b4459041..3b455b567a 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -39,21 +39,21 @@ public class IPScannerViewModel : ViewModelBase, IProfileManagerMinimal private readonly Guid _tabId; private bool _firstLoad = true; - private string _hosts; - public string Hosts + private string _host; + public string Host { - get => _hosts; + get => _host; set { - if (value == _hosts) + if (value == _host) return; - _hosts = value; + _host = value; OnPropertyChanged(); } } - public ICollectionView HostsHistoryView { get; } + public ICollectionView HostHistoryView { get; } private bool _isSubnetDetectionRunning; public bool IsSubnetDetectionRunning @@ -84,21 +84,21 @@ public bool IsRunning } } - private bool _cancelScan; - public bool CancelScan + private bool _isCanceling; + public bool IsCanceling { - get => _cancelScan; + get => _isCanceling; set { - if (value == _cancelScan) + if (value == _isCanceling) return; - _cancelScan = value; + _isCanceling = value; OnPropertyChanged(); } } - private ObservableCollection _results = new(); + private ObservableCollection _results = []; public ObservableCollection Results { get => _results; @@ -220,10 +220,10 @@ public IPScannerViewModel(IDialogCoordinator instance, Guid tabId, string hostOr _dialogCoordinator = instance; _tabId = tabId; - Hosts = hostOrIPRange; + Host = hostOrIPRange; // Host history - HostsHistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.IPScanner_HostHistory); + HostHistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.IPScanner_HostHistory); // Result view ResultsView = CollectionViewSource.GetDefaultView(Results); @@ -237,8 +237,8 @@ public void OnLoaded() if (!_firstLoad) return; - if (!string.IsNullOrEmpty(Hosts)) - StartScan().ConfigureAwait(false); + if (!string.IsNullOrEmpty(Host)) + Start().ConfigureAwait(false); _firstLoad = false; } @@ -260,7 +260,10 @@ private bool Scan_CanExecute(object parameter) private void ScanAction() { - Scan(); + if (IsRunning) + Stop(); + else + Start().ConfigureAwait(false); } public ICommand DetectSubnetCommand => new RelayCommand(_ => DetectSubnetAction()); @@ -344,15 +347,7 @@ private void ExportAction() #endregion #region Methods - private void Scan() - { - if (IsRunning) - StopScan(); - else - StartScan().ConfigureAwait(false); - } - - private async Task StartScan() + private async Task Start() { IsStatusMessageDisplayed = false; IsRunning = true; @@ -367,51 +362,40 @@ private async Task StartScan() { foreach (var tabablzControl in VisualTreeHelper.FindVisualChildren(window)) { - tabablzControl.Items.OfType().First(x => x.Id == _tabId).Header = Hosts; + tabablzControl.Items.OfType().First(x => x.Id == _tabId).Header = Host; } } _cancellationTokenSource = new CancellationTokenSource(); // Resolve hostnames - List ipRanges; + (List<(IPAddress ipAddress, string hostname)> hosts, List hostnamesNotResolved) hosts; try { - ipRanges = await HostRangeHelper.ResolveHostnamesInIPRangesAsync(Hosts.Replace(" ", "").Split(';'), SettingsManager.Current.Network_ResolveHostnamePreferIPv4, _cancellationTokenSource.Token); + hosts = await HostRangeHelper.ResolveAsync(HostRangeHelper.CreateListFromInput(Host), SettingsManager.Current.Network_ResolveHostnamePreferIPv4, _cancellationTokenSource.Token); } catch (OperationCanceledException) { UserHasCanceled(this, EventArgs.Empty); return; } - catch (AggregateException exceptions) // DNS error (could not resolve hostname...) - { - DnsResolveFailed(exceptions); - return; - } - - // Create ip addresses - IPAddress[] ipAddresses; - - try + + // Show error message if (some) hostnames could not be resolved + if(hosts.hostnamesNotResolved.Count > 0) { - // Create a list of all ip addresses - ipAddresses = await HostRangeHelper.CreateIPAddressesFromIPRangesAsync(ipRanges.ToArray(), _cancellationTokenSource.Token); - } - catch (OperationCanceledException) - { - UserHasCanceled(this, EventArgs.Empty); - return; + StatusMessage = $"{Localization.Resources.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", hosts.hostnamesNotResolved)}"; + IsStatusMessageDisplayed = true; + } - HostsToScan = ipAddresses.Length; + HostsToScan = hosts.hosts.Count; HostsScanned = 0; PreparingScan = false; // Add host(s) to the history - AddHostToHistory(Hosts); + AddHostToHistory(Host); var ipScanner = new IPScanner(new IPScannerOptions( SettingsManager.Current.IPScanner_MaxHostThreads, @@ -428,17 +412,17 @@ await PortRangeHelper.ConvertPortRangeToIntArrayAsync(SettingsManager.Current.IP SettingsManager.Current.IPScanner_ShowAllResults )); - ipScanner.HostFound += HostFound; + ipScanner.HostScanned += HostScanned; ipScanner.ScanComplete += ScanComplete; ipScanner.ProgressChanged += ProgressChanged; ipScanner.UserHasCanceled += UserHasCanceled; - ipScanner.ScanAsync(ipAddresses, _cancellationTokenSource.Token); + ipScanner.ScanAsync(hosts.hosts, _cancellationTokenSource.Token); } - private void StopScan() + private void Stop() { - CancelScan = true; + IsCanceling = true; _cancellationTokenSource.Cancel(); } @@ -458,10 +442,10 @@ private async Task DetectIPRange() { subnetmaskDetected = true; - Hosts = $"{localIP}/{Subnetmask.ConvertSubnetmaskToCidr(networkInterface.IPv4Address.First().Item2)}"; + Host = $"{localIP}/{Subnetmask.ConvertSubnetmaskToCidr(networkInterface.IPv4Address.First().Item2)}"; // Fix: If the user clears the TextBox and then clicks again on the button, the TextBox remains empty... - OnPropertyChanged(nameof(Hosts)); + OnPropertyChanged(nameof(Host)); break; } @@ -517,13 +501,13 @@ private void AddHostToHistory(string ipRange) // Clear the old items SettingsManager.Current.IPScanner_HostHistory.Clear(); - OnPropertyChanged(nameof(Hosts)); // Raise property changed again, after the collection has been cleared + OnPropertyChanged(nameof(Host)); // Raise property changed again, after the collection has been cleared // Fill with the new items list.ForEach(x => SettingsManager.Current.IPScanner_HostHistory.Add(x)); } - private async Task Export() + private Task Export() { var customDialog = new CustomDialog { @@ -561,20 +545,20 @@ private async Task Export() DataContext = exportViewModel }; - await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); + return _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); } public void OnClose() { // Stop scan if (IsRunning) - StopScan(); + Stop(); } #endregion #region Events - private void HostFound(object sender, IPScannerHostScannedArgs e) + private void HostScanned(object sender, IPScannerHostScannedArgs e) { Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate { @@ -582,6 +566,11 @@ private void HostFound(object sender, IPScannerHostScannedArgs e) })); } + private void ProgressChanged(object sender, ProgressChangedArgs e) + { + HostsScanned = e.Value; + } + private void ScanComplete(object sender, EventArgs e) { if (Results.Count == 0) @@ -590,21 +579,7 @@ private void ScanComplete(object sender, EventArgs e) IsStatusMessageDisplayed = true; } - CancelScan = false; - IsRunning = false; - } - - private void ProgressChanged(object sender, ProgressChangedArgs e) - { - HostsScanned = e.Value; - } - - private void DnsResolveFailed(AggregateException e) - { - StatusMessage = $"{Localization.Resources.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", e.Flatten().InnerExceptions.Select(x => x.Message))}"; - IsStatusMessageDisplayed = true; - - CancelScan = false; + IsCanceling = false; IsRunning = false; } @@ -612,6 +587,9 @@ private void UserHasCanceled(object sender, EventArgs e) { StatusMessage = Localization.Resources.Strings.CanceledByUserMessage; IsStatusMessageDisplayed = true; + + IsCanceling = false; + IsRunning = false; } #endregion } diff --git a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs index 6fae457237..85b9d1d9bb 100644 --- a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs @@ -10,11 +10,14 @@ using System.Linq; using System.Collections.ObjectModel; using NETworkManager.Views; -using System.Net; using NETworkManager.Profiles; using System.Windows.Threading; using System.Threading.Tasks; using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Threading; +using MahApps.Metro.Controls; using NETworkManager.Models; namespace NETworkManager.ViewModels; @@ -23,6 +26,9 @@ public class PingMonitorHostViewModel : ViewModelBase, IProfileManager { #region Variables private readonly IDialogCoordinator _dialogCoordinator; + + private CancellationTokenSource _cancellationTokenSource; + private readonly DispatcherTimer _searchDispatcherTimer = new(); private readonly bool _isLoading; @@ -57,6 +63,20 @@ public bool IsRunning OnPropertyChanged(); } } + + private bool _isCanceling; + public bool IsCanceling + { + get => _isCanceling; + set + { + if (value == _isCanceling) + return; + + _isCanceling = value; + OnPropertyChanged(); + } + } private bool _isStatusMessageDisplayed; public bool IsStatusMessageDisplayed @@ -86,7 +106,7 @@ private set } } - private ObservableCollection _hosts = new(); + private ObservableCollection _hosts = []; public ObservableCollection Hosts { get => _hosts; @@ -229,7 +249,7 @@ public GridLength ProfileWidth public PingMonitorHostViewModel(IDialogCoordinator instance) { _isLoading = true; - + _dialogCoordinator = instance; // Host history @@ -262,35 +282,48 @@ private void LoadSettings() #endregion #region ICommands & Actions - public ICommand AddHostCommand => new RelayCommand(_ => AddHostAction()); + public ICommand PingCommand => new RelayCommand(_ => PingAction(), Ping_CanExecute); - private async void AddHostAction() + private bool Ping_CanExecute(object parameter) { - await AddHost(Host).ConfigureAwait(true); - - AddHostToHistory(Host); - Host = string.Empty; + return Application.Current.MainWindow != null && !((MetroWindow)Application.Current.MainWindow).IsAnyDialogOpen; } - - public ICommand ExportCommand => new RelayCommand(_ => ExportAction()); - - private void ExportAction() + + private void PingAction() { - SelectedHost?.Export(); + if (IsRunning) + Stop(); + else + Start().ConfigureAwait(false); } + + public ICommand PingProfileCommand => new RelayCommand(_ => PingProfileAction(), PingProfile_CanExecute); - public ICommand AddHostProfileCommand => new RelayCommand(_ => AddHostProfileAction(), AddHostProfile_CanExecute); - - private bool AddHostProfile_CanExecute(object obj) + private bool PingProfile_CanExecute(object obj) { return !IsSearching && SelectedProfile != null; } - private void AddHostProfileAction() + private void PingProfileAction() { - AddHost(SelectedProfile.PingMonitor_Host).ConfigureAwait(false); + if(SetHost(SelectedProfile.PingMonitor_Host)) + Start().ConfigureAwait(false); } + + public ICommand CloseAllCommand => new RelayCommand(_ => CloseAllAction()); + + private void CloseAllAction() + { + RemoveAllHosts(); + } + + public ICommand ExportCommand => new RelayCommand(_ => ExportAction()); + private void ExportAction() + { + SelectedHost?.Export(); + } + public ICommand AddProfileCommand => new RelayCommand(_ => AddProfileAction()); private void AddProfileAction() @@ -337,77 +370,114 @@ private void ClearSearchAction() #endregion #region Methods - public async Task AddHost(string hosts) + + /// + /// Set the host to ping. + /// + /// Host to ping + /// True if the host was set successfully, otherwise false + public bool SetHost(string host) { - IsStatusMessageDisplayed = false; - StatusMessage = string.Empty; + // Check if it is already running or canceling + if (IsRunning || IsCanceling) + { + _dialogCoordinator.ShowMessageAsync(this, Localization.Resources.Strings.Error, Localization.Resources.Strings.CannotSetHostWhileRunningMessage); + + return false; + } + + Host = host; + return true; + } + + public async Task Start() + { + IsStatusMessageDisplayed = false; IsRunning = true; + + _cancellationTokenSource = new CancellationTokenSource(); + + // Resolve hostnames + (List<(IPAddress ipAddress, string hostname)> hosts, List hostnamesNotResolved) hosts; - await Task.Run(() => + try { - Parallel.ForEach(hosts.Split(';'), currentHost => + hosts = await HostRangeHelper.ResolveAsync(HostRangeHelper.CreateListFromInput(Host), SettingsManager.Current.Network_ResolveHostnamePreferIPv4, _cancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + UserHasCanceled(); + + return; + } + + // Show error message if (some) hostnames could not be resolved + if(hosts.hostnamesNotResolved.Count > 0) + { + StatusMessage = $"{Localization.Resources.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", hosts.hostnamesNotResolved)}"; + IsStatusMessageDisplayed = true; + } + + // Add host(s) to history + AddHostToHistory(Host); + + // Add host(s) to list and start the ping + foreach (var hostView in hosts.hosts.Select(currentHost => new PingMonitorView(Guid.NewGuid(), RemoveHostByGuid, currentHost))) + { + // Check if the user has canceled the operation + if (_cancellationTokenSource.IsCancellationRequested) { - var host = currentHost.Trim(); - var hostname = string.Empty; - - // Resolve ip address from hostname - if (!IPAddress.TryParse(host, out var ipAddress)) - { - hostname = host; - - using var dnsResolverTask = DNSClientHelper.ResolveAorAaaaAsync(host, SettingsManager.Current.Network_ResolveHostnamePreferIPv4); - - // Wait for task inside a Parallel.Foreach - dnsResolverTask.Wait(); - - if (dnsResolverTask.Result.HasError) - { - StatusMessageShowOrAdd(host, dnsResolverTask.Result); - return; - } - - ipAddress = dnsResolverTask.Result.Value; - } - - // Resolve hostname from ip address - else - { - using var dnsResolverTask = DNSClient.GetInstance().ResolvePtrAsync(ipAddress); - - // Wait for task inside a Parallel.Foreach - dnsResolverTask.Wait(); - - // Hostname is not necessary for ping. Don't show an error message in the UI. - if (!dnsResolverTask.Result.HasError) - hostname = dnsResolverTask.Result.Value; - } + UserHasCanceled(); + + return; + } + + Hosts.Add(hostView); + + // Start the ping + hostView.Start(); + + // Wait a bit to prevent the UI from freezing + await Task.Delay(25); + } + + Host = string.Empty; + + IsCanceling = false; + IsRunning = false; + } + + private void Stop() + { + IsCanceling = true; + _cancellationTokenSource.Cancel(); + } - Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate - { - Hosts.Add(new PingMonitorView(Guid.NewGuid(), RemoveHost, new PingMonitorOptions(hostname, ipAddress))); - })); - }); - - IsRunning = false; - }).ConfigureAwait(true); + private void RemoveAllHosts() + { + for (var i = Hosts.Count - 1; i >= 0; i--) + { + Hosts[i].Stop(); + Hosts.RemoveAt(i); + } } - private void RemoveHost(Guid hostId) + private void RemoveHostByGuid(Guid hostId) { - var index = -1; + var i = -1; foreach (var host in Hosts) { if (host.HostId.Equals(hostId)) - index = Hosts.IndexOf(host); + i = Hosts.IndexOf(host); } - if (index == -1) + if (i == -1) return; - - Hosts[index].CloseView(); - Hosts.RemoveAt(index); + + Hosts[i].Stop(); + Hosts.RemoveAt(i); } private void AddHostToHistory(string host) @@ -422,7 +492,7 @@ private void AddHostToHistory(string host) // Fill with the new items list.ForEach(x => SettingsManager.Current.PingMonitor_HostHistory.Add(x)); } - + private void ResizeProfile(bool dueToChangedSize) { _canProfileWidthChange = false; @@ -502,29 +572,6 @@ private void RefreshProfiles() SetProfilesView(SelectedProfile); } - - /// - /// Method to display the status message and append messages related to . - /// - /// Host which should be resolved. - /// Information about the error that occurred in the query. - private void StatusMessageShowOrAdd(string host, DNSClientResult result) - { - Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate - { - // Show the message - if (!IsStatusMessageDisplayed) - { - StatusMessage = DNSClientHelper.FormatDNSClientResultError(host, result); - IsStatusMessageDisplayed = true; - - return; - } - - // Append the message - StatusMessage += Environment.NewLine + DNSClientHelper.FormatDNSClientResultError(host, result); - })); - } #endregion #region Event @@ -541,5 +588,14 @@ private void SearchDispatcherTimer_Tick(object sender, EventArgs e) IsSearching = false; } + + private void UserHasCanceled() + { + StatusMessage = Localization.Resources.Strings.CanceledByUserMessage; + IsStatusMessageDisplayed = true; + + IsCanceling = false; + IsRunning = false; + } #endregion } diff --git a/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs index 0c8ae45f9e..d0d4551cde 100644 --- a/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs @@ -91,6 +91,23 @@ public int WaitTime OnPropertyChanged(); } } + + private bool _expandHostView; + public bool ExpandHostView + { + get => _expandHostView; + set + { + if (value == _expandHostView) + return; + + if (!_isLoading) + SettingsManager.Current.PingMonitor_ExpandHostView = value; + + _expandHostView = value; + OnPropertyChanged(); + } + } #endregion #region Contructor, load settings @@ -110,6 +127,7 @@ private void LoadSettings() TTL = SettingsManager.Current.PingMonitor_TTL; DontFragment = SettingsManager.Current.PingMonitor_DontFragment; WaitTime = SettingsManager.Current.PingMonitor_WaitTime; + ExpandHostView = SettingsManager.Current.PingMonitor_ExpandHostView; } #endregion } diff --git a/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs index 478a7ee5b7..5fc62ddc6a 100644 --- a/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs @@ -26,21 +26,34 @@ public class PingMonitorViewModel : ViewModelBase private CancellationTokenSource _cancellationTokenSource; public readonly Guid HostId; - private readonly Action _closeCallback; - private bool _firstLoad = true; - + private readonly Action _removeHostByGuid; + private List _pingInfoList; - private readonly string _host; - public string Host + private string _title; + public string Title { - get => _host; - private init + get => _title; + private set { - if (value == _host) + if (value == _title) return; - _host = value; + _title = value; + OnPropertyChanged(); + } + } + + private string _hostname; + public string Hostname + { + get => _hostname; + private set + { + if (value == _hostname) + return; + + _hostname = value; OnPropertyChanged(); } } @@ -143,6 +156,34 @@ public int Lost } } + private double _packetLoss; + public double PacketLoss + { + get => _packetLoss; + set + { + if (Math.Abs(value - _packetLoss) < 0.01) + return; + + _packetLoss = value; + OnPropertyChanged(); + } + } + + private long _timeMs; + public long TimeMs + { + get => _timeMs; + set + { + if (value == _timeMs) + return; + + _timeMs = value; + OnPropertyChanged(); + } + } + private void InitialTimeChart() { var dayConfig = Mappers.Xy() @@ -194,31 +235,39 @@ public bool IsErrorMessageDisplayed OnPropertyChanged(); } } + + private bool _expandHostView; + public bool ExpandHostView + { + get => _expandHostView; + set + { + if (value == _expandHostView) + return; + + _expandHostView = value; + OnPropertyChanged(); + } + } #endregion #region Contructor, load settings - public PingMonitorViewModel(IDialogCoordinator instance, Guid hostId, Action closeCallback, PingMonitorOptions options) + public PingMonitorViewModel(IDialogCoordinator instance, Guid hostId, Action removeHostByGuid, (IPAddress ipAddress, string hostname) host) { _dialogCoordinator = instance; HostId = hostId; - _closeCallback = closeCallback; - - Host = options.Host; - IPAddress = options.IPAddress; - - InitialTimeChart(); - } + _removeHostByGuid = removeHostByGuid; - public void OnLoaded() - { - if (!_firstLoad) - return; + Title = string.IsNullOrEmpty(host.hostname) ? host.ipAddress.ToString() : $"{host.hostname} # {host.ipAddress}"; - StartPing(); + IPAddress = host.ipAddress; + Hostname = host.hostname; - _firstLoad = false; - } + InitialTimeChart(); + + ExpandHostView = SettingsManager.Current.PingMonitor_ExpandHostView; + } #endregion #region ICommands & Actions @@ -226,39 +275,35 @@ public void OnLoaded() private void PingAction() { - Ping(); + if (IsRunning) + Stop(); + else + Start(); } public ICommand CloseCommand => new RelayCommand(_ => CloseAction()); private void CloseAction() { - _closeCallback(HostId); + _removeHostByGuid(HostId); } #endregion #region Methods - private void Ping() - { - if (IsRunning) - StopPing(); - else - StartPing(); - } - - private void StartPing() + public void Start() { IsErrorMessageDisplayed = false; IsRunning = true; // Reset history - _pingInfoList = new List(); + _pingInfoList = []; // Reset the latest results StatusTime = DateTime.Now; Transmitted = 0; Received = 0; Lost = 0; + PacketLoss = 0; // Reset chart ResetTimeChart(); @@ -271,19 +316,22 @@ private void StartPing() Buffer = new byte[SettingsManager.Current.PingMonitor_Buffer], TTL = SettingsManager.Current.PingMonitor_TTL, DontFragment = SettingsManager.Current.PingMonitor_DontFragment, - WaitTime = SettingsManager.Current.PingMonitor_WaitTime, - Hostname = Host + WaitTime = SettingsManager.Current.PingMonitor_WaitTime }; ping.PingReceived += Ping_PingReceived; ping.PingException += Ping_PingException; + ping.HostnameResolved += Ping_HostnameResolved; ping.UserHasCanceled += Ping_UserHasCanceled; ping.SendAsync(IPAddress, _cancellationTokenSource.Token); } - - private void StopPing() + + public void Stop() { + if (!IsRunning) + return; + _cancellationTokenSource?.Cancel(); } @@ -330,10 +378,9 @@ public async Task Export() SettingsManager.Current.PingMonitor_ExportFileType = instance.FileType; SettingsManager.Current.PingMonitor_ExportFilePath = instance.FilePath; }, _ => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, - new[] - { + [ ExportFileType.Csv, ExportFileType.Xml, ExportFileType.Json - }, false, + ], false, SettingsManager.Current.PingMonitor_ExportFileType, SettingsManager.Current.PingMonitor_ExportFilePath); @@ -344,13 +391,6 @@ public async Task Export() await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); } - - public void OnClose() - { - // Stop the ping - if (IsRunning) - StopPing(); - } #endregion #region Events @@ -385,8 +425,12 @@ private void Ping_PingReceived(object sender, PingReceivedArgs e) timeInfo = new LvlChartsDefaultInfo(e.Args.Timestamp, double.NaN); } + + PacketLoss = Math.Round((double)Lost / Transmitted * 100, 2); + TimeMs = e.Args.Time; - Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate + // Null exception may occur when the application is closing + Application.Current?.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate { Series[0].Values.Add(timeInfo); @@ -403,6 +447,15 @@ private void Ping_UserHasCanceled(object sender, EventArgs e) IsRunning = false; } + private void Ping_HostnameResolved(object sender, HostnameArgs e) + { + // Update title if name was not set in the constructor + if (string.IsNullOrEmpty(Hostname)) + Title = $"{e.Hostname.TrimEnd('.')} # {IPAddress}"; + + Hostname = e.Hostname; + } + private void Ping_PingException(object sender, PingExceptionArgs e) { IsRunning = false; diff --git a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs index e0cc570acd..8278749c28 100644 --- a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs @@ -35,21 +35,21 @@ public class PortScannerViewModel : ViewModelBase private string _lastSortDescriptionAscending = string.Empty; - private string _hosts; - public string Hosts + private string _host; + public string Host { - get => _hosts; + get => _host; set { - if (value == _hosts) + if (value == _host) return; - _hosts = value; + _host = value; OnPropertyChanged(); } } - public ICollectionView HostsHistoryView { get; } + public ICollectionView HostHistoryView { get; } private string _ports; public string Ports @@ -82,21 +82,21 @@ public bool IsRunning } } - private bool _cancelScan; - public bool CancelScan + private bool _isCanceling; + public bool IsCanceling { - get => _cancelScan; + get => _isCanceling; set { - if (value == _cancelScan) + if (value == _isCanceling) return; - _cancelScan = value; + _isCanceling = value; OnPropertyChanged(); } } - private ObservableCollection _results = new(); + private ObservableCollection _results = []; public ObservableCollection Results { get => _results; @@ -216,16 +216,16 @@ public PortScannerViewModel(IDialogCoordinator instance, Guid tabId, string host _dialogCoordinator = instance; _tabId = tabId; - Hosts = host; + Host = host; Ports = port; // Set collection view - HostsHistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.PortScanner_HostHistory); + HostHistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.PortScanner_HostHistory); PortsHistoryView = CollectionViewSource.GetDefaultView(SettingsManager.Current.PortScanner_PortHistory); // Result view ResultsView = CollectionViewSource.GetDefaultView(Results); - ResultsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(PortScannerPortInfo.IPAddress))); + ResultsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(PortScannerPortInfo.HostAsString))); ResultsView.SortDescriptions.Add(new SortDescription(nameof(PortScannerPortInfo.IPAddressInt32), ListSortDirection.Descending)); LoadSettings(); @@ -241,8 +241,8 @@ public void OnLoaded() if (!_firstLoad) return; - if (!string.IsNullOrEmpty(Hosts) && !string.IsNullOrEmpty(Ports)) - StartScan().ConfigureAwait(false); + if (!string.IsNullOrEmpty(Host) && !string.IsNullOrEmpty(Ports)) + Start().ConfigureAwait(false); _firstLoad = false; } @@ -251,7 +251,7 @@ public void OnClose() { // Stop scan if (IsRunning) - StopScan(); + Stop(); } #endregion @@ -272,9 +272,9 @@ private void OpenPortProfileSelectionAction() private void ScanAction() { if (IsRunning) - StopScan(); + Stop(); else - StartScan().ConfigureAwait(false); + Start().ConfigureAwait(false); } public ICommand ExportCommand => new RelayCommand(_ => ExportAction()); @@ -311,7 +311,7 @@ private async Task OpenPortProfileSelection() await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog); } - private async Task StartScan() + private async Task Start() { IsStatusMessageDisplayed = false; StatusMessage = string.Empty; @@ -328,53 +328,43 @@ private async Task StartScan() { foreach (var tabablzControl in VisualTreeHelper.FindVisualChildren(window)) { - tabablzControl.Items.OfType().First(x => x.Id == _tabId).Header = Hosts; + tabablzControl.Items.OfType().First(x => x.Id == _tabId).Header = Host; } } _cancellationTokenSource = new CancellationTokenSource(); // Resolve hostnames - List ipRanges; + (List<(IPAddress ipAddress, string hostname)> hosts, List hostnamesNotResolved) hosts; try { - ipRanges = await HostRangeHelper.ResolveHostnamesInIPRangesAsync(Hosts.Replace(" ", "").Split(';'), SettingsManager.Current.Network_ResolveHostnamePreferIPv4, _cancellationTokenSource.Token); + hosts = await HostRangeHelper.ResolveAsync(HostRangeHelper.CreateListFromInput(Host), SettingsManager.Current.Network_ResolveHostnamePreferIPv4, _cancellationTokenSource.Token); } catch (OperationCanceledException) { UserHasCanceled(this, EventArgs.Empty); return; } - catch (AggregateException exceptions) // DNS error (could not resolve hostname...) - { - DnsResolveFailed(exceptions); - return; - } - - // Create ip addresses - IPAddress[] ipAddresses; - - try - { - // Create a list of all ip addresses - ipAddresses = await HostRangeHelper.CreateIPAddressesFromIPRangesAsync(ipRanges.ToArray(), _cancellationTokenSource.Token); - } - catch (OperationCanceledException) + + // Show error message if (some) hostnames could not be resolved + if(hosts.hostnamesNotResolved.Count > 0) { - UserHasCanceled(this, EventArgs.Empty); - return; + StatusMessage = $"{Localization.Resources.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", hosts.hostnamesNotResolved)}"; + IsStatusMessageDisplayed = true; + } + // Convert ports to int array var ports = await PortRangeHelper.ConvertPortRangeToIntArrayAsync(Ports); - PortsToScan = ports.Length * ipAddresses.Length; + PortsToScan = ports.Length * hosts.hosts.Count; PortsScanned = 0; PreparingScan = false; // Add host(s) to the history - AddHostToHistory(Hosts); + AddHostToHistory(Host); AddPortToHistory(Ports); var portScanner = new PortScanner(new PortScannerOptions( @@ -390,12 +380,12 @@ private async Task StartScan() portScanner.ProgressChanged += ProgressChanged; portScanner.UserHasCanceled += UserHasCanceled; - portScanner.ScanAsync(ipAddresses, ports, _cancellationTokenSource.Token); + portScanner.ScanAsync(hosts.hosts, ports, _cancellationTokenSource.Token); } - private void StopScan() + private void Stop() { - CancelScan = true; + IsCanceling = true; _cancellationTokenSource.Cancel(); } @@ -441,7 +431,7 @@ private void AddHostToHistory(string host) // Clear the old items SettingsManager.Current.PortScanner_HostHistory.Clear(); - OnPropertyChanged(nameof(Hosts)); // Raise property changed again, after the collection has been cleared + OnPropertyChanged(nameof(Host)); // Raise property changed again, after the collection has been cleared // Fill with the new items list.ForEach(x => SettingsManager.Current.PortScanner_HostHistory.Add(x)); @@ -479,13 +469,12 @@ public void SortResultByPropertyName(string sortDescription) #endregion #region Events - private void UserHasCanceled(object sender, EventArgs e) + private void PortScanned(object sender, PortScannerPortScannedArgs e) { - StatusMessage = Localization.Resources.Strings.CanceledByUserMessage; - IsStatusMessageDisplayed = true; - - CancelScan = false; - IsRunning = false; + Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate + { + Results.Add(e.Args); + })); } private void ProgressChanged(object sender, ProgressChangedArgs e) @@ -493,15 +482,6 @@ private void ProgressChanged(object sender, ProgressChangedArgs e) PortsScanned = e.Value; } - private void DnsResolveFailed(AggregateException e) - { - StatusMessage = $"{Localization.Resources.Strings.TheFollowingHostnamesCouldNotBeResolved} {string.Join(", ", e.Flatten().InnerExceptions.Select(x => x.Message))}"; - IsStatusMessageDisplayed = true; - - CancelScan = false; - IsRunning = false; - } - private void ScanComplete(object sender, EventArgs e) { if (Results.Count == 0) @@ -510,16 +490,17 @@ private void ScanComplete(object sender, EventArgs e) IsStatusMessageDisplayed = true; } - CancelScan = false; + IsCanceling = false; IsRunning = false; } - - private void PortScanned(object sender, PortScannerPortScannedArgs e) + + private void UserHasCanceled(object sender, EventArgs e) { - Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate - { - Results.Add(e.Args); - })); + StatusMessage = Localization.Resources.Strings.CanceledByUserMessage; + IsStatusMessageDisplayed = true; + + IsCanceling = false; + IsRunning = false; } #endregion diff --git a/Source/NETworkManager/Views/DiscoveryProtocolView.xaml b/Source/NETworkManager/Views/DiscoveryProtocolView.xaml index 035982fc23..03a219e8b5 100644 --- a/Source/NETworkManager/Views/DiscoveryProtocolView.xaml +++ b/Source/NETworkManager/Views/DiscoveryProtocolView.xaml @@ -81,9 +81,18 @@ - - - + + + - - + - + @@ -595,6 +600,9 @@ diff --git a/Source/NETworkManager/Views/IPScannerView.xaml.cs b/Source/NETworkManager/Views/IPScannerView.xaml.cs index af4c347d77..314dc9c5db 100644 --- a/Source/NETworkManager/Views/IPScannerView.xaml.cs +++ b/Source/NETworkManager/Views/IPScannerView.xaml.cs @@ -27,7 +27,7 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e) _viewModel.OnLoaded(); } - private void Dispatcher_ShutdownStarted(object sender, System.EventArgs e) + private void Dispatcher_ShutdownStarted(object sender, EventArgs e) { _viewModel.OnClose(); } diff --git a/Source/NETworkManager/Views/PingMonitorHostView.xaml b/Source/NETworkManager/Views/PingMonitorHostView.xaml index 3ba5ac2621..ab2f82751b 100644 --- a/Source/NETworkManager/Views/PingMonitorHostView.xaml +++ b/Source/NETworkManager/Views/PingMonitorHostView.xaml @@ -15,10 +15,11 @@ dialogs:DialogParticipation.Register="{Binding}" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:PingMonitorHostViewModel}"> - - + + + @@ -53,25 +54,29 @@ - - - + - + + + @@ -141,6 +194,15 @@ + + + @@ -155,7 +217,16 @@ - + @@ -214,7 +285,7 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Source/NETworkManager/Views/PingMonitorView.xaml.cs b/Source/NETworkManager/Views/PingMonitorView.xaml.cs index 77b4dec268..dece6fb114 100644 --- a/Source/NETworkManager/Views/PingMonitorView.xaml.cs +++ b/Source/NETworkManager/Views/PingMonitorView.xaml.cs @@ -1,7 +1,7 @@ using NETworkManager.ViewModels; using System; -using NETworkManager.Models.Network; using MahApps.Metro.Controls.Dialogs; +using System.Net; namespace NETworkManager.Views; @@ -11,20 +11,24 @@ public partial class PingMonitorView public Guid HostId => _viewModel.HostId; - public PingMonitorView(Guid hostId, Action closeCallback, PingMonitorOptions options) + public PingMonitorView(Guid hostId, Action removeHostByGuid, (IPAddress ipAddress, string hostname) host) { InitializeComponent(); - _viewModel = new PingMonitorViewModel(DialogCoordinator.Instance, hostId, closeCallback, options); + _viewModel = new PingMonitorViewModel(DialogCoordinator.Instance, hostId, removeHostByGuid, host); DataContext = _viewModel; Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted; } - private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e) + public void Start() { - _viewModel.OnLoaded(); + _viewModel.Start(); + } + public void Stop() + { + _viewModel.Stop(); } public void Export() @@ -34,11 +38,6 @@ public void Export() private void Dispatcher_ShutdownStarted(object sender, EventArgs e) { - _viewModel.OnClose(); - } - - public void CloseView() - { - _viewModel.OnClose(); + Stop(); } } diff --git a/Source/NETworkManager/Views/PortScannerView.xaml b/Source/NETworkManager/Views/PortScannerView.xaml index 40c7234fce..4a3ab4d055 100644 --- a/Source/NETworkManager/Views/PortScannerView.xaml +++ b/Source/NETworkManager/Views/PortScannerView.xaml @@ -57,13 +57,13 @@ Style="{StaticResource DefaultTextBlock}" VerticalAlignment="Center" /> - + @@ -111,13 +111,13 @@