Add support for aggressiv ip address scanning.

This commit is contained in:
Holger Börchers 2025-04-04 10:26:08 +02:00
parent 56fa20b86f
commit cb5ec30b99
6 changed files with 158 additions and 5 deletions

47
src/CidrHelper.cs Normal file
View File

@ -0,0 +1,47 @@
using System.Net;
namespace SddpViewer;
public class CidrHelper
{
public static (IPAddress Start, IPAddress End) GetIpRangeFromCidr(string cidr)
{
string[] parts = cidr.Split('/');
if (parts.Length != 2)
throw new FormatException("Ungültiges CIDR-Format.");
IPAddress ip = IPAddress.Parse(parts[0]);
int prefixLength = int.Parse(parts[1]);
if (prefixLength < 0 || prefixLength > 32)
throw new ArgumentException("Ungültige Präfixlänge.");
byte[] ipBytes = ip.GetAddressBytes();
byte[] maskBytes = new byte[4];
int remainingBits = prefixLength;
for (int i = 0; i < maskBytes.Length; i++)
{
if (remainingBits >= 8)
{
maskBytes[i] = 0xFF;
remainingBits -= 8;
}
else if (remainingBits > 0)
{
maskBytes[i] = (byte)(0xFF << (8 - remainingBits));
remainingBits = 0;
}
}
byte[] startBytes = new byte[4];
byte[] endBytes = new byte[4];
for (int i = 0; i < 4; i++)
{
startBytes[i] = (byte)(ipBytes[i] & maskBytes[i]);
endBytes[i] = (byte)(ipBytes[i] | ~maskBytes[i]);
}
return (new IPAddress(startBytes), new IPAddress(endBytes));
}
}

View File

@ -118,8 +118,12 @@ public partial class DiscoveredDeviceViewModel : ObservableObject, IDisposable
private string EvaluateHostName() => Dns.GetHostEntry(IpAddress).HostName;
private string GetResponseHeader() =>
string.Join("," + Environment.NewLine, _device.ResponseHeaders.Select(x => $"{{{x.Key} : {string.Join(";", x.Value)}}}"));
private string GetResponseHeader()
{
return _device.ResponseHeaders is null
? ""
: string.Join("," + Environment.NewLine, _device.ResponseHeaders.Select(x => $"{{{x.Key} : {string.Join(";", x.Value)}}}"));
}
private IPAddress EvaluateIpAddress(DiscoveredSsdpDevice device) =>
IPAddress.TryParse(device.DescriptionLocation.Host, out var value) ? value : IPAddress.Any;

40
src/IpRangeHelper.cs Normal file
View File

@ -0,0 +1,40 @@
using System.Net;
namespace SddpViewer;
public class IpRangeHelper
{
public static IEnumerable<IPAddress> GetIPRange(IPAddress startIp, IPAddress endIp)
{
uint start = IpToUint(startIp);
uint end = IpToUint(endIp);
if (start > end)
{
(start, end) = (end, start);
}
for (uint current = start; current <= end; current++)
{
yield return UintToIp(current);
}
}
private static uint IpToUint(IPAddress ip)
{
byte[] bytes = ip.GetAddressBytes();
if (bytes.Length != 4)
throw new ArgumentException("Nur IPv4 wird unterstützt.");
return (uint)((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]);
}
private static IPAddress UintToIp(uint ip)
{
byte[] bytes = new byte[4];
bytes[0] = (byte)((ip >> 24) & 0xFF);
bytes[1] = (byte)((ip >> 16) & 0xFF);
bytes[2] = (byte)((ip >> 8) & 0xFF);
bytes[3] = (byte)(ip & 0xFF);
return new IPAddress(bytes);
}
}

View File

@ -28,6 +28,7 @@
<Button Command="{Binding StartListeningCommand}" Content="Listen for devices" />
<Button Command="{Binding SearchDevicesNowCommand}" Content="Search now" />
<Button Command="{Binding ResearchCommand}" Content="Search more" />
<Button Command="{Binding IpScanCommand}" Content="Scan ip" />
</StackPanel>
</StackPanel>
<Grid Grid.Column="1" Grid.Row="0">

View File

@ -1,4 +1,7 @@
using System.Collections.Specialized;
using System.Collections.Concurrent;
using System.Collections.Specialized;
using System.Net;
using System.Net.Http.Headers;
namespace SddpViewer;
@ -132,6 +135,64 @@ public sealed partial class MainWindowViewModel : ObservableObject, IDisposable
}
}
[RelayCommand(IncludeCancelCommand = true)]
private async Task IpScan(CancellationToken cancellationToken)
{
if (SelectedNetworkAdapter is not null)
{
var ip = SelectedNetworkAdapter.IpAddress.GetAddressBytes();
ip[^1] = 0;
var interNetwork = new IPAddress(ip);
var cidr = $"{interNetwork}/{SelectedNetworkAdapter.PrefixLength}";
var range = CidrHelper.GetIpRangeFromCidr(cidr);
var ipRange = IpRangeHelper.GetIPRange(range.Start, range.End).Where(IpAddressFilter);
var collection = new ConcurrentBag<DiscoveredDeviceViewModel>();
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromSeconds(1);
await Parallel.ForEachAsync(
ipRange,
new ParallelOptions() { MaxDegreeOfParallelism = 16, CancellationToken = cancellationToken },
async (address, token) =>
{
try
{
token.ThrowIfCancellationRequested();
DiscoveredSsdpDevice device = new DiscoveredSsdpDevice()
{
CacheLifetime = TimeSpan.Zero,
DescriptionLocation = new Uri($"http://{address.ToString()}:8080/discovery/ssdp"),
};
var response = await httpClient.GetAsync(device.DescriptionLocation, token);
response.EnsureSuccessStatusCode();
device.ResponseHeaders = response.Headers;
var viewModel = new DiscoveredDeviceViewModel(device);
collection.Add(viewModel);
Console.WriteLine($"{address} ok");
}
catch (Exception)
{
Console.WriteLine($"{address} not ok");
}
}
);
foreach (DiscoveredDeviceViewModel viewModel in collection)
{
Dispatcher.UIThread.Invoke(() => SddpDevices.Add(viewModel));
}
}
}
private bool IpAddressFilter(IPAddress arg)
{
if (this.SddpDevices.Any(x => Equals(x.IpAddress, arg)))
{
return false;
}
return true;
}
[RelayCommand]
private async Task StartListening(Func<SsdpDeviceLocator, Task>? action = null)
{

View File

@ -4,7 +4,7 @@ using System.Net.Sockets;
namespace SddpViewer;
public record NetworkAdapter(string Name, IPAddress IpAddress)
public record NetworkAdapter(string Name, IPAddress IpAddress, int PrefixLength)
{
public string DisplayName => $"{Name} - {IpAddress}";
@ -20,7 +20,7 @@ public record NetworkAdapter(string Name, IPAddress IpAddress)
.UnicastAddresses.FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
if (physicalAddress is not null)
{
yield return new NetworkAdapter(nic.Name, physicalAddress.Address);
yield return new NetworkAdapter(nic.Name, physicalAddress.Address, physicalAddress.PrefixLength);
}
}
}