From 4c5c4706b16aa1e8f71f526e0995d4f682606513 Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 3 Dec 2023 19:59:54 +0100 Subject: [PATCH 01/11] Feature: Ping Monitor improvements --- .../NETworkManager.Models/Network/HostRangeHelper.cs | 8 ++++---- .../Network/PingMonitorOptions.cs | 12 +++--------- Source/NETworkManager/Views/IPScannerView.xaml.cs | 2 +- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Source/NETworkManager.Models/Network/HostRangeHelper.cs b/Source/NETworkManager.Models/Network/HostRangeHelper.cs index 8dad0d15aa..ba1dfc9125 100644 --- a/Source/NETworkManager.Models/Network/HostRangeHelper.cs +++ b/Source/NETworkManager.Models/Network/HostRangeHelper.cs @@ -20,7 +20,7 @@ public static Task CreateIPAddressesFromIPRangesAsync(string[] ipRa return Task.Run(() => CreateIPAddressesFromIPRanges(ipRanges, cancellationToken), cancellationToken); } - public static IPAddress[] CreateIPAddressesFromIPRanges(string[] ipRanges, CancellationToken cancellationToken) + private static IPAddress[] CreateIPAddressesFromIPRanges(IEnumerable ipRanges, CancellationToken cancellationToken) { var bag = new ConcurrentBag(); @@ -131,7 +131,7 @@ public static Task> ResolveHostnamesInIPRangesAsync(string[] ipRang return Task.Run(() => ResolveHostnamesInIPRanges(ipRanges, dnsResolveHostnamePreferIPv4, cancellationToken), cancellationToken); } - public static List ResolveHostnamesInIPRanges(string[] ipRanges, bool dnsResolveHostnamePreferIPv4, CancellationToken cancellationToken) + private static List ResolveHostnamesInIPRanges(IEnumerable ipRanges, bool dnsResolveHostnamePreferIPv4, CancellationToken cancellationToken) { var bag = new ConcurrentBag(); @@ -161,7 +161,7 @@ public static List ResolveHostnamesInIPRanges(string[] ipRanges, bool dn using (var dnsResolverTask = DNSClientHelper.ResolveAorAaaaAsync(ipHostOrRange, dnsResolveHostnamePreferIPv4)) { // Wait for task inside a Parallel.Foreach - dnsResolverTask.Wait(); + dnsResolverTask.Wait(cancellationToken); if (!dnsResolverTask.Result.HasError) bag.Add($"{dnsResolverTask.Result.Value}"); @@ -179,7 +179,7 @@ public static List ResolveHostnamesInIPRanges(string[] ipRanges, bool dn using (var dnsResolverTask = DNSClientHelper.ResolveAorAaaaAsync(hostAndSubnet[0], true)) { // Wait for task inside a Parallel.Foreach - dnsResolverTask.Wait(); + dnsResolverTask.Wait(cancellationToken); if (!dnsResolverTask.Result.HasError) { diff --git a/Source/NETworkManager.Models/Network/PingMonitorOptions.cs b/Source/NETworkManager.Models/Network/PingMonitorOptions.cs index 9de14c3fb5..41d1076afa 100644 --- a/Source/NETworkManager.Models/Network/PingMonitorOptions.cs +++ b/Source/NETworkManager.Models/Network/PingMonitorOptions.cs @@ -2,14 +2,8 @@ namespace NETworkManager.Models.Network; -public class PingMonitorOptions +public class PingMonitorOptions(string host, IPAddress ipAddress) { - public string Host { get; set; } - public IPAddress IPAddress { get; set; } - - public PingMonitorOptions(string host, IPAddress ipAddress) - { - Host = host; - IPAddress = ipAddress; - } + public string Host { get; } = host; + public IPAddress IPAddress { get; } = ipAddress; } 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(); } From a60869c80300baf4955041ba6d3ceaa53ca01e0b Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 3 Dec 2023 23:13:09 +0100 Subject: [PATCH 02/11] Feature: DNS Lookup / Port Scanner improve group string --- .../ExportManager.DNSLookupRecordInfo.cs | 14 +++--- .../Network/DNSLookup.cs | 33 ++++++++----- .../Network/DNSLookupRecordInfo.cs | 39 ++++++++++----- .../Network/PortScanner.cs | 5 +- .../Network/PortScannerPortInfo.cs | 5 ++ .../Network/Traceroute.cs | 12 ++--- Source/NETworkManager.Utilities/DNSClient.cs | 47 ++++++++----------- .../ViewModels/DNSLookupViewModel.cs | 6 +-- .../ViewModels/PortScannerViewModel.cs | 2 +- .../NETworkManager/Views/PortScannerView.xaml | 2 +- .../Views/PortScannerView.xaml.cs | 2 +- 11 files changed, 95 insertions(+), 72 deletions(-) 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 @@ private static void CreateXml(IEnumerable collection, strin 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..6a9c55607a 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.IsNullOrWhiteSpace(NameServerHostName) + ? $"{NameServerIPAddress}:{NameServerPort}" + : $"{NameServerHostName} ({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/PortScanner.cs b/Source/NETworkManager.Models/Network/PortScanner.cs index a8b6329784..3864a15b6b 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(IPAddress[] ipAddresses, IEnumerable ports, CancellationToken cancellationToken) { _progressValue = 0; @@ -81,7 +82,7 @@ public void ScanAsync(IPAddress[] ipAddresses, int[] ports, CancellationToken ca if (_options.ResolveHostname) { - // Don't use await in Paralle.ForEach, this will break + // Don't use await in Parallel.ForEach, this will break var dnsResolverTask = DNSClient.GetInstance().ResolvePtrAsync(ipAddress); // Wait for task inside a Parallel.Foreach diff --git a/Source/NETworkManager.Models/Network/PortScannerPortInfo.cs b/Source/NETworkManager.Models/Network/PortScannerPortInfo.cs index d8fc4ebc64..00c57c5080 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.IsNullOrWhiteSpace(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.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/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/PortScannerViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs index e0cc570acd..a72fe66645 100644 --- a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs @@ -225,7 +225,7 @@ public PortScannerViewModel(IDialogCoordinator instance, Guid tabId, string host // 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(); diff --git a/Source/NETworkManager/Views/PortScannerView.xaml b/Source/NETworkManager/Views/PortScannerView.xaml index 40c7234fce..0d6c617599 100644 --- a/Source/NETworkManager/Views/PortScannerView.xaml +++ b/Source/NETworkManager/Views/PortScannerView.xaml @@ -305,7 +305,7 @@ - + diff --git a/Source/NETworkManager/Views/PortScannerView.xaml.cs b/Source/NETworkManager/Views/PortScannerView.xaml.cs index 3a1290d7e8..287e09eb77 100644 --- a/Source/NETworkManager/Views/PortScannerView.xaml.cs +++ b/Source/NETworkManager/Views/PortScannerView.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(); } From cf7ccf7ce6fb746372e02c88ab503e59a32b9850 Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Mon, 4 Dec 2023 02:49:24 +0100 Subject: [PATCH 03/11] Feature: Host range aded to Ping Monitor & new design --- .../Network/DNSLookupRecordInfo.cs | 4 +- .../Network/HostRangeHelper.cs | 175 +++++----- .../Network/HostnameArgs.cs | 23 ++ .../Network/IPScanner.cs | 16 +- Source/NETworkManager.Models/Network/Ping.cs | 25 +- .../Network/PingMonitorOptions.cs | 9 - .../Network/PortScanner.cs | 12 +- .../Network/PortScannerPortInfo.cs | 2 +- .../Resources/Styles/ListBoxStyle.xaml | 8 - .../ViewModels/IPScannerViewModel.cs | 22 +- .../ViewModels/PingMonitorHostViewModel.cs | 87 +++-- .../ViewModels/PingMonitorViewModel.cs | 68 ++-- .../ViewModels/PortScannerViewModel.cs | 22 +- .../Views/PingMonitorHostView.xaml | 20 +- .../NETworkManager/Views/PingMonitorView.xaml | 313 +++++++++--------- .../Views/PingMonitorView.xaml.cs | 10 +- docs/Changelog/next-release.md | 5 + 17 files changed, 421 insertions(+), 400 deletions(-) create mode 100644 Source/NETworkManager.Models/Network/HostnameArgs.cs delete mode 100644 Source/NETworkManager.Models/Network/PingMonitorOptions.cs diff --git a/Source/NETworkManager.Models/Network/DNSLookupRecordInfo.cs b/Source/NETworkManager.Models/Network/DNSLookupRecordInfo.cs index 6a9c55607a..3b354129cc 100644 --- a/Source/NETworkManager.Models/Network/DNSLookupRecordInfo.cs +++ b/Source/NETworkManager.Models/Network/DNSLookupRecordInfo.cs @@ -48,9 +48,9 @@ public class DNSLookupRecordInfo /// /// Hostname (if available) or/and IP address with port of the name server that provided the result. /// - public string NameServerAsString => string.IsNullOrWhiteSpace(NameServerHostName) + public string NameServerAsString => string.IsNullOrEmpty(NameServerHostName) ? $"{NameServerIPAddress}:{NameServerPort}" - : $"{NameServerHostName} ({NameServerIPAddress}:{NameServerPort})"; + : $"{NameServerHostName.TrimEnd('.')} # {NameServerIPAddress}:{NameServerPort}"; /// /// Creates a new instance of with the specified parameters. diff --git a/Source/NETworkManager.Models/Network/HostRangeHelper.cs b/Source/NETworkManager.Models/Network/HostRangeHelper.cs index ba1dfc9125..9f1200fe0d 100644 --- a/Source/NETworkManager.Models/Network/HostRangeHelper.cs +++ b/Source/NETworkManager.Models/Network/HostRangeHelper.cs @@ -15,65 +15,77 @@ namespace NETworkManager.Models.Network; public static class HostRangeHelper { - public static Task CreateIPAddressesFromIPRangesAsync(string[] ipRanges, CancellationToken cancellationToken) + public static string[] CreateListFromInput(string hosts) { - return Task.Run(() => CreateIPAddressesFromIPRanges(ipRanges, cancellationToken), cancellationToken); + return hosts.Replace(" ", "").Split(';') + .Where(x => !string.IsNullOrEmpty(x)) + .Select(x => x.Trim()) + .ToArray(); } - private static IPAddress[] CreateIPAddressesFromIPRanges(IEnumerable ipRanges, CancellationToken cancellationToken) + public static Task> 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)> Resolve(IEnumerable hosts, bool dnsResolveHostnamePreferIPv4, CancellationToken cancellationToken) + { + var result = new ConcurrentBag<(IPAddress ipAddress, string hostname)>(); - foreach (var ipOrRange in ipRanges) + var exceptions = new ConcurrentQueue(); + + 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): + result.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(); + result.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(); + result.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 +97,13 @@ private static IPAddress[] CreateIPAddressesFromIPRanges(IEnumerable ipR { 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,75 +120,41 @@ private static IPAddress[] CreateIPAddressesFromIPRanges(IEnumerable ipR } // 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}")); - } - } - } - } + result.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); - } - - private static List ResolveHostnamesInIPRanges(IEnumerable 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(cancellationToken); if (!dnsResolverTask.Result.HasError) - bag.Add($"{dnsResolverTask.Result.Value}"); + result.Add((IPAddress.Parse($"{dnsResolverTask.Result.Value}"), host)); else - exceptions.Enqueue(new HostNotFoundException(ipHostOrRange)); + exceptions.Enqueue(new HostNotFoundException(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)) @@ -185,12 +166,26 @@ private static List ResolveHostnamesInIPRanges(IEnumerable ipRan { // 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(); + + result.Add((IPv4Address.FromInt32(i), string.Empty)); + }); + } else + { exceptions.Enqueue(new HostNotFoundException(hostAndSubnet[0])); + } } else + { exceptions.Enqueue(new HostNotFoundException(hostAndSubnet[0])); + } } break; @@ -200,6 +195,6 @@ private static List ResolveHostnamesInIPRanges(IEnumerable ipRan if (!exceptions.IsEmpty) throw new AggregateException(exceptions); - return bag.ToList(); + return result.ToList(); } -} \ 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..f305bcf450 100644 --- a/Source/NETworkManager.Models/Network/IPScanner.cs +++ b/Source/NETworkManager.Models/Network/IPScanner.cs @@ -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; 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 41d1076afa..0000000000 --- a/Source/NETworkManager.Models/Network/PingMonitorOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Net; - -namespace NETworkManager.Models.Network; - -public class PingMonitorOptions(string host, IPAddress ipAddress) -{ - public string Host { get; } = host; - public IPAddress IPAddress { get; } = ipAddress; -} diff --git a/Source/NETworkManager.Models/Network/PortScanner.cs b/Source/NETworkManager.Models/Network/PortScanner.cs index 3864a15b6b..6ead46e40a 100644 --- a/Source/NETworkManager.Models/Network/PortScanner.cs +++ b/Source/NETworkManager.Models/Network/PortScanner.cs @@ -55,7 +55,7 @@ public PortScanner(PortScannerOptions options) #endregion #region Methods - public void ScanAsync(IPAddress[] ipAddresses, IEnumerable ports, CancellationToken cancellationToken) + public void ScanAsync(IEnumerable<(IPAddress ipAddress, string hostname)> hosts, IEnumerable ports, CancellationToken cancellationToken) { _progressValue = 0; @@ -75,7 +75,7 @@ public void ScanAsync(IPAddress[] ipAddresses, IEnumerable ports, Cancellat MaxDegreeOfParallelism = _options.MaxPortThreads }; - Parallel.ForEach(ipAddresses, hostParallelOptions, ipAddress => + Parallel.ForEach(hosts, hostParallelOptions, host => { // Resolve Hostname (PTR) var hostname = string.Empty; @@ -83,7 +83,7 @@ public void ScanAsync(IPAddress[] ipAddresses, IEnumerable ports, Cancellat 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(cancellationToken); @@ -96,13 +96,13 @@ public void ScanAsync(IPAddress[] ipAddresses, IEnumerable ports, Cancellat 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; @@ -119,7 +119,7 @@ public void ScanAsync(IPAddress[] ipAddresses, IEnumerable ports, Cancellat 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 00c57c5080..c39385b276 100644 --- a/Source/NETworkManager.Models/Network/PortScannerPortInfo.cs +++ b/Source/NETworkManager.Models/Network/PortScannerPortInfo.cs @@ -26,7 +26,7 @@ public class PortScannerPortInfo : PortInfo /// /// Hostname (if available) or IP address of the host. /// - public string HostAsString => string.IsNullOrWhiteSpace(Hostname) ? IPAddress.ToString() : Hostname; + public string HostAsString => string.IsNullOrEmpty(Hostname) ? IPAddress.ToString() : Hostname; /// /// Creates a new instance of with the specified parameters. 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/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs index 19b4459041..3fc8f46913 100644 --- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs @@ -374,11 +374,11 @@ private async Task StartScan() _cancellationTokenSource = new CancellationTokenSource(); // Resolve hostnames - List ipRanges; + List<(IPAddress ipAddress, string hostname)> hosts; try { - ipRanges = await HostRangeHelper.ResolveHostnamesInIPRangesAsync(Hosts.Replace(" ", "").Split(';'), SettingsManager.Current.Network_ResolveHostnamePreferIPv4, _cancellationTokenSource.Token); + hosts = await HostRangeHelper.ResolveAsync(HostRangeHelper.CreateListFromInput(Hosts), SettingsManager.Current.Network_ResolveHostnamePreferIPv4, _cancellationTokenSource.Token); } catch (OperationCanceledException) { @@ -391,21 +391,7 @@ private async Task StartScan() return; } - // Create ip addresses - IPAddress[] ipAddresses; - - try - { - // Create a list of all ip addresses - ipAddresses = await HostRangeHelper.CreateIPAddressesFromIPRangesAsync(ipRanges.ToArray(), _cancellationTokenSource.Token); - } - catch (OperationCanceledException) - { - UserHasCanceled(this, EventArgs.Empty); - return; - } - - HostsToScan = ipAddresses.Length; + HostsToScan = hosts.Count; HostsScanned = 0; PreparingScan = false; @@ -433,7 +419,7 @@ private async Task StartScan() ipScanner.ProgressChanged += ProgressChanged; ipScanner.UserHasCanceled += UserHasCanceled; - ipScanner.ScanAsync(ipAddresses, _cancellationTokenSource.Token); + ipScanner.ScanAsync(hosts, _cancellationTokenSource.Token); } private void StopScan() diff --git a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs index 6fae457237..c7c9fce75b 100644 --- a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs @@ -10,11 +10,12 @@ 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.Net; +using System.Threading; using NETworkManager.Models; namespace NETworkManager.ViewModels; @@ -23,6 +24,9 @@ public class PingMonitorHostViewModel : ViewModelBase, IProfileManager { #region Variables private readonly IDialogCoordinator _dialogCoordinator; + + private CancellationTokenSource _cancellationTokenSource; + private readonly DispatcherTimer _searchDispatcherTimer = new(); private readonly bool _isLoading; @@ -229,7 +233,7 @@ public GridLength ProfileWidth public PingMonitorHostViewModel(IDialogCoordinator instance) { _isLoading = true; - + _dialogCoordinator = instance; // Host history @@ -337,60 +341,45 @@ private void ClearSearchAction() #endregion #region Methods - public async Task AddHost(string hosts) + public async Task AddHost(string host) { IsStatusMessageDisplayed = false; StatusMessage = string.Empty; IsRunning = true; - await Task.Run(() => - { - Parallel.ForEach(hosts.Split(';'), currentHost => - { - 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); + _cancellationTokenSource = new CancellationTokenSource(); - // Wait for task inside a Parallel.Foreach - dnsResolverTask.Wait(); + // Resolve hostnames + List<(IPAddress ipAddress, string hostname)> hosts; - 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); + try + { + 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; + } - // Wait for task inside a Parallel.Foreach - dnsResolverTask.Wait(); + // Add hosts + foreach (var currentHost in hosts) + { + var hostView = new PingMonitorView(Guid.NewGuid(), RemoveHost, currentHost); - // Hostname is not necessary for ping. Don't show an error message in the UI. - if (!dnsResolverTask.Result.HasError) - hostname = dnsResolverTask.Result.Value; - } + Hosts.Add(hostView); - Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate - { - Hosts.Add(new PingMonitorView(Guid.NewGuid(), RemoveHost, new PingMonitorOptions(hostname, ipAddress))); - })); - }); - - IsRunning = false; - }).ConfigureAwait(true); + // Start the ping + hostView.Start(); + } + + IsRunning = false; } private void RemoveHost(Guid hostId) @@ -403,9 +392,9 @@ private void RemoveHost(Guid hostId) index = Hosts.IndexOf(host); } - if (index == -1) + if (index == -1) return; - + Hosts[index].CloseView(); Hosts.RemoveAt(index); } @@ -422,7 +411,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,7 +491,7 @@ private void RefreshProfiles() SetProfilesView(SelectedProfile); } - + /// /// Method to display the status message and append messages related to . /// diff --git a/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs index 478a7ee5b7..338b644063 100644 --- a/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs +++ b/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs @@ -27,20 +27,33 @@ public class PingMonitorViewModel : ViewModelBase public readonly Guid HostId; private readonly Action _closeCallback; - private bool _firstLoad = true; - + 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 == _title) + return; + + _title = value; + OnPropertyChanged(); + } + } + + private string _hostname; + public string Hostname + { + get => _hostname; + private set { - if (value == _host) + if (value == _hostname) return; - _host = value; + _hostname = value; OnPropertyChanged(); } } @@ -197,28 +210,25 @@ public bool IsErrorMessageDisplayed #endregion #region Contructor, load settings - public PingMonitorViewModel(IDialogCoordinator instance, Guid hostId, Action closeCallback, PingMonitorOptions options) + public PingMonitorViewModel(IDialogCoordinator instance, Guid hostId, Action closeCallback, (IPAddress ipAddress, string hostname) host) { _dialogCoordinator = instance; HostId = hostId; _closeCallback = closeCallback; - Host = options.Host; - IPAddress = options.IPAddress; + Title = string.IsNullOrEmpty(host.hostname) ? host.ipAddress.ToString() : $"{host.hostname} # {host.ipAddress}"; + + IPAddress = host.ipAddress; + Hostname = host.hostname; InitialTimeChart(); } - public void OnLoaded() + public void Start() { - if (!_firstLoad) - return; - StartPing(); - - _firstLoad = false; - } + } #endregion #region ICommands & Actions @@ -252,7 +262,7 @@ private void StartPing() IsRunning = true; // Reset history - _pingInfoList = new List(); + _pingInfoList = []; // Reset the latest results StatusTime = DateTime.Now; @@ -271,17 +281,17 @@ 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() { _cancellationTokenSource?.Cancel(); @@ -330,10 +340,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); @@ -403,6 +412,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 a72fe66645..2d031394bd 100644 --- a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs +++ b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs @@ -335,11 +335,11 @@ private async Task StartScan() _cancellationTokenSource = new CancellationTokenSource(); // Resolve hostnames - List ipRanges; + List<(IPAddress ipAddress, string hostname)> hosts; try { - ipRanges = await HostRangeHelper.ResolveHostnamesInIPRangesAsync(Hosts.Replace(" ", "").Split(';'), SettingsManager.Current.Network_ResolveHostnamePreferIPv4, _cancellationTokenSource.Token); + hosts = await HostRangeHelper.ResolveAsync(HostRangeHelper.CreateListFromInput(Hosts), SettingsManager.Current.Network_ResolveHostnamePreferIPv4, _cancellationTokenSource.Token); } catch (OperationCanceledException) { @@ -352,23 +352,9 @@ private async Task StartScan() return; } - // Create ip addresses - IPAddress[] ipAddresses; - - try - { - // Create a list of all ip addresses - ipAddresses = await HostRangeHelper.CreateIPAddressesFromIPRangesAsync(ipRanges.ToArray(), _cancellationTokenSource.Token); - } - catch (OperationCanceledException) - { - UserHasCanceled(this, EventArgs.Empty); - return; - } - var ports = await PortRangeHelper.ConvertPortRangeToIntArrayAsync(Ports); - PortsToScan = ports.Length * ipAddresses.Length; + PortsToScan = ports.Length * hosts.Count; PortsScanned = 0; PreparingScan = false; @@ -390,7 +376,7 @@ private async Task StartScan() portScanner.ProgressChanged += ProgressChanged; portScanner.UserHasCanceled += UserHasCanceled; - portScanner.ScanAsync(ipAddresses, ports, _cancellationTokenSource.Token); + portScanner.ScanAsync(hosts, ports, _cancellationTokenSource.Token); } private void StopScan() diff --git a/Source/NETworkManager/Views/PingMonitorHostView.xaml b/Source/NETworkManager/Views/PingMonitorHostView.xaml index 3ba5ac2621..02eb83fdbf 100644 --- a/Source/NETworkManager/Views/PingMonitorHostView.xaml +++ b/Source/NETworkManager/Views/PingMonitorHostView.xaml @@ -59,14 +59,14 @@ VerticalAlignment="Center" /> - + @@ -127,7 +127,12 @@ - + @@ -141,6 +146,15 @@ + + + diff --git a/Source/NETworkManager/Views/PingMonitorView.xaml b/Source/NETworkManager/Views/PingMonitorView.xaml index 46a41742f7..b7dcb5f5c2 100644 --- a/Source/NETworkManager/Views/PingMonitorView.xaml +++ b/Source/NETworkManager/Views/PingMonitorView.xaml @@ -10,170 +10,185 @@ xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization" xmlns:liveChart="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf" xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" - dialogs:DialogParticipation.Register="{Binding}" - Loaded="UserControl_Loaded" + dialogs:DialogParticipation.Register="{Binding}" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:PingMonitorViewModel}"> - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Source/NETworkManager/Views/PingMonitorView.xaml.cs b/Source/NETworkManager/Views/PingMonitorView.xaml.cs index 77b4dec268..af3cd0f4cc 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,20 @@ public partial class PingMonitorView public Guid HostId => _viewModel.HostId; - public PingMonitorView(Guid hostId, Action closeCallback, PingMonitorOptions options) + public PingMonitorView(Guid hostId, Action closeCallback, (IPAddress ipAddress, string hostname) host) { InitializeComponent(); - _viewModel = new PingMonitorViewModel(DialogCoordinator.Instance, hostId, closeCallback, options); + _viewModel = new PingMonitorViewModel(DialogCoordinator.Instance, hostId, closeCallback, 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 Export() diff --git a/docs/Changelog/next-release.md b/docs/Changelog/next-release.md index 27a2cda6cb..bd0ac9edf5 100644 --- a/docs/Changelog/next-release.md +++ b/docs/Changelog/next-release.md @@ -28,6 +28,11 @@ Experimental features can be enabled in the settings under [`Settings > Update`] ## Improvements +- Port Scanner + - Hostname added to group +- DNS Lookup + - Hostname of the nameserver added to group + ## Bugfixes ## Other From f949ecf0a4d1caf96709990848cda80d5a40da9b Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Mon, 4 Dec 2023 02:52:14 +0100 Subject: [PATCH 04/11] Feature: Allow range in profile --- Source/NETworkManager/Views/ProfileDialog.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/NETworkManager/Views/ProfileDialog.xaml b/Source/NETworkManager/Views/ProfileDialog.xaml index 2511519499..374c016cae 100644 --- a/Source/NETworkManager/Views/ProfileDialog.xaml +++ b/Source/NETworkManager/Views/ProfileDialog.xaml @@ -1691,7 +1691,7 @@ + mah:TextBoxHelper.Watermark="{x:Static localization:StaticStrings.ExampleHostRange}"> + + + + + - - + @@ -26,13 +25,24 @@ - + + + + + + - - + + @@ -67,13 +77,41 @@ - + + + + + + + + + + + + + + + + + + - - -