Skip to content

Commit

Permalink
#93 IPScanner - Allow server-01.example.com/24 as input
Browse files Browse the repository at this point in the history
  • Loading branch information
BornToBeRoot committed May 18, 2018
1 parent 4d18a7a commit 249ffa4
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 67 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
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
77 changes: 73 additions & 4 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 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
71 changes: 26 additions & 45 deletions Source/NETworkManager/ViewModels/IPScannerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
using NETworkManager.Utilities;
using Dragablz;
using NETworkManager.Controls;
using System.Text.RegularExpressions;
using System.Net.Sockets;

namespace NETworkManager.ViewModels
{
Expand Down Expand Up @@ -510,59 +508,34 @@ private async void StartScan()
}
}


string ipRange = string.Empty;

// Resolve any hostnames
foreach (string ipHostOrRange in IPRange.Replace(" ", "").Split(';'))
{
if (!string.IsNullOrEmpty(ipRange))
ipRange += ";";

if (!Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressCidrRegex) && !Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressSubnetmaskRegex) && !Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressRangeRegex) && !Regex.IsMatch(ipHostOrRange, RegexHelper.IPv4AddressSpecialRangeRegex))
{
string host = string.Empty;

if (ipHostOrRange.Contains('/'))
host = ipHostOrRange.Split('/')[0];
else
host = ipHostOrRange;

IPHostEntry ipHostEntrys = await Dns.GetHostEntryAsync(host);
IPAddress ipAddress = null;

foreach (IPAddress ip in ipHostEntrys.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
ipAddress = ip;
continue;
}
}
cancellationTokenSource = new CancellationTokenSource();

if (ipAddress == null)
throw new Exception();
string[] ipHostOrRanges = IPRange.Replace(" ", "").Split(';');

if (ipHostOrRange.Contains('/'))
ipRange = ipAddress.ToString() + "/" + ipHostOrRange.Split('/')[1];
else
ipRange = ipAddress.ToString();
// Resolve hostnames
List<string> ipRanges = new List<string>();

}
else
{
ipRange += ipHostOrRange;
}
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;
}

cancellationTokenSource = new CancellationTokenSource();

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 @@ -665,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
1 change: 1 addition & 0 deletions Source/NETworkManager/Views/IPScannerView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<Binding Path="IPRange" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<Validator:EmptyValidator ValidatesOnTargetUpdated="True" />
<Validator:IPScanRangeValidator ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</ComboBox.Text>
Expand Down

0 comments on commit 249ffa4

Please sign in to comment.