Skip to content

Commit

Permalink
IP Scanner - Allow hostname/subnetmask as input (#94)
Browse files Browse the repository at this point in the history
#93  IPScanner - Allow server-01.example.com/24 as input
  • Loading branch information
BornToBeRoot committed May 18, 2018
1 parent be6ff28 commit 6a881b4
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 26 deletions.
22 changes: 22 additions & 0 deletions Source/NETworkManager/Models/Network/HostNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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)
{

}
}
}
1 change: 1 addition & 0 deletions Source/NETworkManager/NETworkManager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@
<Compile Include="Models\Documentation\CommunityManager.cs" />
<Compile Include="Models\Documentation\ResourceInfo.cs" />
<Compile Include="Models\Documentation\ResourceManager.cs" />
<Compile Include="Models\Network\HostNotFoundException.cs" />
<Compile Include="Models\PuTTY\PuTTYSessionInfo.cs" />
<Compile Include="Models\PuTTY\PuTTY.cs" />
<Compile Include="Models\Settings\TracerouteProfileManager.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,8 @@
<system:String x:Key="String_PuTTY">PuTTY</system:String>
<system:String x:Key="String_SNMP">SNMP</system:String>
<system:String x:Key="String_OpenWebsite">Webseite öffnen</system:String>

<system:String x:Key="String_TheFollowingHostnamesCouldNotBeResolved">Die folgenden Hostnamen konnten nicht aufgelöst werden:</system:String>

<!-- Documentation title -->
<system:String x:Key="String_DocumentationTitle_00001">Wie installiere ich RDP 8.1 unter Windows 7 / Server 2008 R2?</system:String>
<system:String x:Key="String_DocumentationTitle_00002">Wie erstelle ich ein benutzerdefiniertes Thema und Akzent?</system:String>
Expand Down Expand Up @@ -512,7 +513,7 @@
<system:String x:Key="String_Watermark_ExampleIPv4Address">192.168.178.55</system:String>
<system:String x:Key="String_Watermark_ExampleSubnetmask">255.255.255.0</system:String>
<system:String x:Key="String_Watermark_ExampleHostnameOrIPAddress">fritz.box or 192.168.178.1</system:String>
<system:String x:Key="String_Watermark_ExampleIPScanRange">192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; 192.168.178.150</system:String>
<system:String x:Key="String_Watermark_ExampleIPScanRange">192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; 192.168.178.150; server-01/24</system:String>
<system:String x:Key="String_Watermark_LocationOfTheImport">Speicherort der Importdatei...</system:String>
<system:String x:Key="String_Watermark_ExampleMACAddressesOrVendor">01:23:45:67:89:AB; 01-23-45; AA11BB; 00F1A2C3D4E5; Intel Corp; Asus</system:String>
<system:String x:Key="String_Watermark_ExamplePortScanRange">22; 80; 443; 500 - 999; 8080</system:String>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@
<system:String x:Key="String_PuTTY">PuTTY</system:String>
<system:String x:Key="String_SNMP">SNMP</system:String>
<system:String x:Key="String_OpenWebsite">Open website</system:String>
<system:String x:Key="String_TheFollowingHostnamesCouldNotBeResolved">The following hostnames could not be resolved:</system:String>

<!-- Documentation title -->
<system:String x:Key="String_DocumentationTitle_00001">How to install RDP 8.1 on Windows 7/Server 2008 R2</system:String>
Expand Down Expand Up @@ -512,7 +513,7 @@
<system:String x:Key="String_Watermark_ExampleIPv4Address">192.168.178.55</system:String>
<system:String x:Key="String_Watermark_ExampleSubnetmask">255.255.255.0</system:String>
<system:String x:Key="String_Watermark_ExampleHostnameOrIPAddress">SERVER-01 or 172.16.0.100</system:String>
<system:String x:Key="String_Watermark_ExampleIPScanRange">192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; 192.168.178.150</system:String>
<system:String x:Key="String_Watermark_ExampleIPScanRange">192.168.1.0/24; 192.168.178.1 - 192.168.178.128; 192.168.[178-179].[1,100,150-200]; 192.168.178.150; server-01/24</system:String>
<system:String x:Key="String_Watermark_LocationOfTheImport">Location of the import file...</system:String>
<system:String x:Key="String_Watermark_ExampleMACAddressesOrVendor">01:23:45:67:89:AB; 01-23-45; AA11BB; 00F1A2C3D4E5; Intel Corp; Asus</system:String>
<system:String x:Key="String_Watermark_ExamplePortScanRange">22; 80; 443; 500 - 999; 8080</system:String>
Expand Down
79 changes: 74 additions & 5 deletions Source/NETworkManager/Utilities/IPScanRangeHelper.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using NETworkManager.Models.Network;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -10,12 +13,12 @@ namespace NETworkManager.Utilities
{
public static class IPScanRangeHelper
{
public static Task<IPAddress[]> ConvertIPRangeToIPAddressesAsync(string ipRange, CancellationToken cancellationToken)
public static Task<IPAddress[]> ConvertIPRangeToIPAddressesAsync(string[] ipRanges, CancellationToken cancellationToken)
{
return Task.Run(() => ConvertIPRangeToIPAddresses(ipRange, cancellationToken), cancellationToken);
return Task.Run(() => ConvertIPRangeToIPAddresses(ipRanges, cancellationToken), cancellationToken);
}

public static IPAddress[] ConvertIPRangeToIPAddresses(string ipRange, CancellationToken cancellationToken)
public static IPAddress[] ConvertIPRangeToIPAddresses(string[] ipRanges, CancellationToken cancellationToken)
{
ConcurrentBag<IPAddress> bag = new ConcurrentBag<IPAddress>();

Expand All @@ -24,7 +27,7 @@ public static IPAddress[] ConvertIPRangeToIPAddresses(string ipRange, Cancellati
CancellationToken = cancellationToken
};

foreach (string ipOrRange in ipRange.Replace(" ", "").Split(';'))
foreach (string ipOrRange in ipRanges)
{
// Match 192.168.0.1
if (Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressRegex))
Expand All @@ -33,7 +36,7 @@ public static IPAddress[] ConvertIPRangeToIPAddresses(string ipRange, Cancellati
continue;
}

// Match 192.168.0.0/24
// Match 192.168.0.0/24 or 192.168.0.0/255.255.255.0
if (Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressCidrRegex) || Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressSubnetmaskRegex))
{
string[] subnet = ipOrRange.Split('/');
Expand Down Expand Up @@ -131,5 +134,71 @@ public static IPAddress[] ConvertIPRangeToIPAddresses(string ipRange, Cancellati

return bag.ToArray();
}

public static Task<List<string>> ResolveHostnamesInIPRangeAsync(string[] ipHostOrRanges, CancellationToken cancellationToken)
{
return Task.Run(() => ResolveHostnamesInIPRange(ipHostOrRanges, cancellationToken), cancellationToken);
}

public static List<string> ResolveHostnamesInIPRange(string[] ipHostOrRanges, CancellationToken cancellationToken)
{
ConcurrentBag<string> bag = new ConcurrentBag<string>();

ParallelOptions parallelOptions = new ParallelOptions()
{
CancellationToken = cancellationToken
};

ConcurrentQueue<HostNotFoundException> exceptions = new ConcurrentQueue<HostNotFoundException>();

Parallel.ForEach(ipHostOrRanges, new ParallelOptions() { CancellationToken = cancellationToken }, ipHostOrRange =>
{
// like 192.168.0.1, 192.168.0.0/24, 192.168.0.0/255.255.255.0, 192.168.0.0 - 192.168.0.100, 192.168.[50-100].1
if (Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressRegex) || Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressCidrRegex) || Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressSubnetmaskRegex) || Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressRangeRegex) || Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressSpecialRangeRegex))
{
bag.Add(ipHostOrRange);
} // like fritz.box, fritz.box/24 or fritz.box/255.255.255.128
else if (Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameRegex) || Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameCidrRegex) || Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameSubnetmaskRegex))
{
IPHostEntry ipHostEntrys = null;
string[] hostAndSubnet = ipHostOrRange.Split('/');
try
{
ipHostEntrys = Dns.GetHostEntry(hostAndSubnet[0]);
}
catch (SocketException)
{
exceptions.Enqueue(new HostNotFoundException(hostAndSubnet[0]));
return;
}
IPAddress ipAddress = null;
foreach (IPAddress ip in ipHostEntrys.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
ipAddress = ip;
break;
}
}
if (ipAddress == null)
exceptions.Enqueue(new HostNotFoundException(hostAndSubnet[0]));
if (ipHostOrRange.Contains('/'))
bag.Add(string.Format("{0}/{1}",ipAddress.ToString(), hostAndSubnet[1]));
else
bag.Add(ipAddress.ToString());
}
});

if (exceptions.Count > 0)
throw new AggregateException(exceptions);

return bag.ToList();
}
}
}
30 changes: 22 additions & 8 deletions Source/NETworkManager/Utilities/RegexHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public static class RegexHelper
{
// Match IPv4-Address like 192.168.178.1
private const string IPv4AddressValues = @"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
private const string IPv4AddressValues = @"(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])";
public const string IPv4AddressRegex = "^" + IPv4AddressValues + "$";

// Match IPv6-Address
Expand All @@ -18,29 +18,43 @@ public static class RegexHelper
// Matche the first 3 bytes of a MAC-Address 000000, 00:00:00, 00-00-00
public const string MACAddressFirst3BytesRegex = @"^[A-Fa-f0-9]{6}$|^[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}$|^[A-Fa-f0-9]{4}.[A-Fa-f0-9]{2}$";

// Private subnetmask / cidr values
private const string SubnetmaskValues = @"(((255\.){3}(255|254|252|248|240|224|192|128|0+))|((255\.){2}(255|254|252|248|240|224|192|128|0+)\.0)|((255\.)(255|254|252|248|240|224|192|128|0+)(\.0+){2})|((255|254|252|248|240|224|192|128|0+)(\.0+){3}))";
private const string CidrRegex = @"([1-9]|[1-2][0-9]|3[0-2])";

// Match a Subnetmask like 255.255.255.0
private const string SubnetmaskValuesFirstOctet = "(255|254|252|248|240|224|192|128)";
private const string SubnetmaskValues = "(255|254|252|248|240|224|192|128|0)";
public const string SubnetmaskRegex = "^(" + SubnetmaskValuesFirstOctet + ".0.0.0)|(255." + SubnetmaskValues + ".0.0)|(255.255." + SubnetmaskValues + ".0)|(255.255.255." + SubnetmaskValues + ")$";
public const string SubnetmaskRegex = @"^" + SubnetmaskValues + @"&";

// Match a subnet from 192.168.178.0/1 to 192.168.178.0/32
public const string IPv4AddressCidrRegex = @"^" + IPv4AddressValues + @"\/([1-9]|[1-2][0-9]|3[0-2])$";
public const string IPv4AddressCidrRegex = @"^" + IPv4AddressValues + @"\/" + CidrRegex + @"$";

// Match a subnet from 192.168.178.0/0 to 192.168.178.0/32
public const string SubnetCalculatorIPv4AddressCidrRegex = @"^" + IPv4AddressValues + @"\/([0-9]|[1-2][0-9]|3[0-2])$";
public const string SubnetCalculatorIPv4AddressCidrRegex = @"^" + IPv4AddressValues + @"\/" + CidrRegex + @"$";

// Match a subnet from 192.168.178.0/192.0.0.0 to 192.168.178.0/255.255.255.255
public const string IPv4AddressSubnetmaskRegex = "^" + IPv4AddressValues + @"\/(" + SubnetmaskValuesFirstOctet + ".0.0.0)|(255." + SubnetmaskValues + ".0.0)|(255.255." + SubnetmaskValues + ".0)|(255.255.255." + SubnetmaskValues + ")$";
public const string IPv4AddressSubnetmaskRegex = @"^" + IPv4AddressValues + @"\/" + SubnetmaskValues + @"$";

// Match a subnet from 192.168.178.0/0.0.0.0 to 192.168.178.0/255.255.255.255
public const string SubnetCalculatorIPv4AddressSubnetmaskRegex = "^" + IPv4AddressValues + @"\/(" + SubnetmaskValues + ".0.0.0)|(255." + SubnetmaskValues + ".0.0)|(255.255." + SubnetmaskValues + ".0)|(255.255.255." + SubnetmaskValues + ")$";
public const string SubnetCalculatorIPv4AddressSubnetmaskRegex = "^" + IPv4AddressValues + @"\/" + SubnetmaskValues + @"$";

// Match a range like [0-255], [0,2,4] and [2,4-6]
public const string SpecialRangeRegex = @"\[((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)-(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))([,]((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)-(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))))*\]";

// Match a IPv4-Address like 192.168.[50-100].1
public const string IPv4AddressSpecialRangeRegex = @"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|" + SpecialRangeRegex + @")\.){3}((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|" + SpecialRangeRegex + @")$";

// Private hostname values
private const string HostnameValues = @"(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])";

// Hostname regex
public const string HostnameRegex = @"^" + HostnameValues + @"$";

// Match a hostname with cidr like server-01.example.com/24
public const string HostnameCidrRegex = @"^" + HostnameValues + @"\/" + CidrRegex + @"$";

// Match a hostname with subnetmask like server-01.example.com/255.255.255.0
public const string HostnameSubnetmaskRegex = @"^" + HostnameValues + @"\/" + SubnetmaskValues + @"$";

// Test for http|https uris
public const string httpAndHttpsUriRegex = @"^http(s)?:\/\/([\w-]+.)+[\w-]+(\/[\w- ./?%&=])?$";

Expand Down
28 changes: 20 additions & 8 deletions Source/NETworkManager/Validators/IPScanRangeValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,24 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
bool isValid = true;

foreach (string ipOrRange in (value as string).Replace(" ", "").Split(';'))
foreach (string ipHostOrRange in (value as string).Replace(" ", "").Split(';'))
{
// like 192.168.0.1
if (Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressRegex))
if (Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressRegex))
continue;

// like 192.168.0.0/24
if (Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressCidrRegex))
if (Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressCidrRegex))
continue;

// like 192.168.0.0/255.255.255.0
if (Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressSubnetmaskRegex))
if (Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressSubnetmaskRegex))
continue;

// like 192.168.0.0 - 192.168.0.100
if (Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressRangeRegex))
if (Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressRangeRegex))
{
string[] range = ipOrRange.Split('-');
string[] range = ipHostOrRange.Split('-');

if (IPv4AddressHelper.ConvertToInt32(IPAddress.Parse(range[0])) >= IPv4AddressHelper.ConvertToInt32(IPAddress.Parse(range[1])))
isValid = false;
Expand All @@ -39,9 +39,9 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
}

// like 192.168.[50-100].1
if (Regex.IsMatch(ipOrRange, RegexHelper.IPv4AddressSpecialRangeRegex))
if (Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressSpecialRangeRegex))
{
string[] octets = ipOrRange.Split('.');
string[] octets = ipHostOrRange.Split('.');

foreach (string octet in octets)
{
Expand All @@ -65,6 +65,18 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo)
continue;
}

// like server-01.example.com
if (Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameRegex))
continue;

// like server-01.example.com/24
if (Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameCidrRegex))
continue;

// like server-01.example.com/255.255.255.0
if (Regex.IsMatch(ipHostOrRange, RegexHelper.HostnameSubnetmaskRegex))
continue;

isValid = false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using NETworkManager.Models.Settings;
using NETworkManager.Utilities;
Expand Down
30 changes: 29 additions & 1 deletion Source/NETworkManager/ViewModels/IPScannerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -510,12 +510,32 @@ private async void StartScan()

cancellationTokenSource = new CancellationTokenSource();

string[] ipHostOrRanges = IPRange.Replace(" ", "").Split(';');

// Resolve hostnames
List<string> ipRanges = new List<string>();

try
{
ipRanges = await IPScanRangeHelper.ResolveHostnamesInIPRangeAsync(ipHostOrRanges, cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
IpScanner_UserHasCanceled(this, EventArgs.Empty);
return;
}
catch (AggregateException exceptions) // DNS error (could not resolve hostname...)
{
IpScanner_DnsResolveFailed(this, exceptions);
return;
}

IPAddress[] ipAddresses;

try
{
// Create a list of all ip addresses
ipAddresses = await IPScanRangeHelper.ConvertIPRangeToIPAddressesAsync(IPRange, cancellationTokenSource.Token);
ipAddresses = await IPScanRangeHelper.ConvertIPRangeToIPAddressesAsync(ipRanges.ToArray(), cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
Expand Down Expand Up @@ -618,6 +638,14 @@ private void IpScanner_ProgressChanged(object sender, ProgressChangedArgs e)
IPAddressesScanned = e.Value;
}

private void IpScanner_DnsResolveFailed(object sender, AggregateException e)
{
StatusMessage = string.Format("{0} {1}", LocalizationManager.GetStringByKey("String_TheFollowingHostnamesCouldNotBeResolved"), string.Join(", ", e.Flatten().InnerExceptions.Select(x => x.Message)));
DisplayStatusMessage = true;

ScanFinished();
}

private void IpScanner_UserHasCanceled(object sender, EventArgs e)
{
StatusMessage = LocalizationManager.GetStringByKey("String_CanceledByUser");
Expand Down

0 comments on commit 6a881b4

Please sign in to comment.