mirror of
https://github.com/Stone-Red-Code/ARP-Scanner.git
synced 2026-09-04 08:56:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4447dc0be | ||
|
|
f332631696 | ||
|
|
3528f06ce4 | ||
|
|
e5f69fd256 | ||
|
|
a99903eec6 | ||
|
|
e6943924cc |
@@ -40,7 +40,7 @@ enclosed in quotation marks, you should use an editor that supports UTF-8, not t
|
|||||||
<!-- Note that unstable versions like 0.0.1 can be considered a released version, but it's
|
<!-- Note that unstable versions like 0.0.1 can be considered a released version, but it's
|
||||||
possible that one can release a 0.0.1-beta before you release a 0.0.1 version. If the version
|
possible that one can release a 0.0.1-beta before you release a 0.0.1 version. If the version
|
||||||
number is final, that is considered a released version and not a prerelease. -->
|
number is final, that is considered a released version and not a prerelease. -->
|
||||||
<version>1.1.0.0</version>
|
<version>1.2.0.0</version>
|
||||||
<!-- <packageSourceUrl>Where is this Chocolatey package located (think GitHub)? packageSourceUrl
|
<!-- <packageSourceUrl>Where is this Chocolatey package located (think GitHub)? packageSourceUrl
|
||||||
is highly recommended for the community feed</packageSourceUrl>-->
|
is highly recommended for the community feed</packageSourceUrl>-->
|
||||||
<!-- owners is a poor name for maintainers of the package. It sticks around by this name for
|
<!-- owners is a poor name for maintainers of the package. It sticks around by this name for
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: arp-scanner # you probably want to 'snapcraft register <name>'
|
name: arp-scanner # you probably want to 'snapcraft register <name>'
|
||||||
version: "1.1.0.0" # just for humans, typically '1.2+git' or '1.3.2'
|
version: "1.2.0.0" # just for humans, typically '1.2+git' or '1.3.2'
|
||||||
grade: stable # must be 'stable' to release into candidate/stable channels
|
grade: stable # must be 'stable' to release into candidate/stable channels
|
||||||
summary: A lightweight cross-platform IP scanner # 79 char long summary
|
summary: A lightweight cross-platform IP scanner # 79 char long summary
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -7,14 +7,16 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
|
<Version>1.2.0.0</Version>
|
||||||
|
<AssemblyVersion></AssemblyVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ArpLookup" Version="2.0.3" />
|
<PackageReference Include="ArpLookup" Version="2.0.3" />
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageReference Include="CuteUtils" Version="1.0.0" />
|
<PackageReference Include="CuteUtils" Version="1.1.1" />
|
||||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||||
<PackageReference Include="IPAddressRange" Version="6.0.0" />
|
<PackageReference Include="IPAddressRange" Version="6.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -7,4 +7,16 @@ internal class MonitorOptions : ScanOptions
|
|||||||
{
|
{
|
||||||
[Option('d', "delay", Required = false, Default = 60, HelpText = "The delay between each scan in seconds.")]
|
[Option('d', "delay", Required = false, Default = 60, HelpText = "The delay between each scan in seconds.")]
|
||||||
public int Delay { get; set; }
|
public int Delay { get; set; }
|
||||||
|
|
||||||
|
[Option("changes-only", Required = false, HelpText = "During monitoring, only print when hosts are added or removed.")]
|
||||||
|
public bool ChangesOnly { get; set; }
|
||||||
|
|
||||||
|
[Option("summary", Required = false, HelpText = "Print a compact summary after each scan instead of per-IP progress.")]
|
||||||
|
public bool Summary { get; set; }
|
||||||
|
|
||||||
|
[Option("on-new", Required = false, HelpText = "Command to execute when new hosts appear. Receives JSON on stdin.")]
|
||||||
|
public string? OnNewCommand { get; set; }
|
||||||
|
|
||||||
|
[Option("on-removed", Required = false, HelpText = "Command to execute when hosts disappear. Receives JSON on stdin.")]
|
||||||
|
public string? OnRemovedCommand { get; set; }
|
||||||
}
|
}
|
||||||
+213
-36
@@ -46,7 +46,11 @@ internal static class Program
|
|||||||
|
|
||||||
private static async Task<int> StartMonitor(MonitorOptions options)
|
private static async Task<int> StartMonitor(MonitorOptions options)
|
||||||
{
|
{
|
||||||
ConsoleExt.WriteLine($"Monitoring IP range '{options.IpRange}' every {options.Delay} seconds...", ConsoleColor.DarkYellow);
|
string monitorTarget = string.IsNullOrWhiteSpace(options.IpRange) && options.AutoDiscover
|
||||||
|
? "all local subnets"
|
||||||
|
: options.IpRange ?? "<none>";
|
||||||
|
|
||||||
|
ConsoleExt.WriteLine($"Monitoring IP range '{monitorTarget}' every {options.Delay} seconds...", ConsoleColor.DarkYellow);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -80,15 +84,40 @@ internal static class Program
|
|||||||
|
|
||||||
private static async Task<int> StartScan(ScanOptions options)
|
private static async Task<int> StartScan(ScanOptions options)
|
||||||
{
|
{
|
||||||
|
IEnumerable<IPAddress> ipAddresses;
|
||||||
|
|
||||||
|
if (options.AutoDiscover && string.IsNullOrWhiteSpace(options.IpRange))
|
||||||
|
{
|
||||||
|
ipAddresses = GetLocalSubnets();
|
||||||
|
|
||||||
|
if (!ipAddresses.Any())
|
||||||
|
{
|
||||||
|
ConsoleExt.WriteLine("No local IPv4 subnets found for auto discovery.", ConsoleColor.Red);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(options.IpRange))
|
||||||
|
{
|
||||||
|
ConsoleExt.WriteLine("IP range is required when auto discovery is disabled.", ConsoleColor.Red);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IPAddressRange.TryParse(options.IpRange, out IPAddressRange ipAddressRange))
|
if (!IPAddressRange.TryParse(options.IpRange, out IPAddressRange ipAddressRange))
|
||||||
{
|
{
|
||||||
ConsoleExt.WriteLine("Invalid IP range!", ConsoleColor.Red);
|
ConsoleExt.WriteLine("Invalid IP range!", ConsoleColor.Red);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipAddresses = ipAddressRange;
|
||||||
|
}
|
||||||
|
|
||||||
await macVendorLookup.Initialize(options.Silent);
|
await macVendorLookup.Initialize(options.Silent);
|
||||||
|
|
||||||
long ipAddressesCount = ipAddressRange.Count();
|
long ipAddressesCount = ipAddresses is IPAddressRange range
|
||||||
|
? range.Count()
|
||||||
|
: ipAddresses.LongCount();
|
||||||
long processedIpAddressesCount = 0;
|
long processedIpAddressesCount = 0;
|
||||||
int numberOfDigits = ipAddressesCount.ToString().Length;
|
int numberOfDigits = ipAddressesCount.ToString().Length;
|
||||||
int exitCode = 0;
|
int exitCode = 0;
|
||||||
@@ -114,7 +143,9 @@ internal static class Program
|
|||||||
MaxDegreeOfParallelism = options.Concurrency
|
MaxDegreeOfParallelism = options.Concurrency
|
||||||
};
|
};
|
||||||
|
|
||||||
await Parallel.ForEachAsync(ipAddressRange, parallelOptions, async (IPAddress ipAddress, CancellationToken _) =>
|
bool suppressPerIpOutput = options is MonitorOptions monitorOptions && (monitorOptions.ChangesOnly || monitorOptions.Summary);
|
||||||
|
|
||||||
|
await Parallel.ForEachAsync(ipAddresses, parallelOptions, async (IPAddress ipAddress, CancellationToken _) =>
|
||||||
{
|
{
|
||||||
PhysicalAddress? mac = null;
|
PhysicalAddress? mac = null;
|
||||||
bool fail = false;
|
bool fail = false;
|
||||||
@@ -145,7 +176,7 @@ internal static class Program
|
|||||||
MacInformation macInformation = macVendorLookup.GetInformation(formattedMac);
|
MacInformation macInformation = macVendorLookup.GetInformation(formattedMac);
|
||||||
|
|
||||||
List<string> info = [ipAddress.ToString(), formattedMac, macInformation.VendorName, macInformation.BlockType, macInformation.Private?.ToString() ?? "Unknown", macInformation.LastUpdate];
|
List<string> info = [ipAddress.ToString(), formattedMac, macInformation.VendorName, macInformation.BlockType, macInformation.Private?.ToString() ?? "Unknown", macInformation.LastUpdate];
|
||||||
if (!options.Silent)
|
if (!options.Silent && !suppressPerIpOutput)
|
||||||
{
|
{
|
||||||
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Active: {ipAddress}", ConsoleColor.Green);
|
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Active: {ipAddress}", ConsoleColor.Green);
|
||||||
}
|
}
|
||||||
@@ -153,14 +184,14 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
else if (fail)
|
else if (fail)
|
||||||
{
|
{
|
||||||
if (!options.Silent)
|
if (!options.Silent && !suppressPerIpOutput)
|
||||||
{
|
{
|
||||||
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Failed: {ipAddress}", ConsoleColor.Red);
|
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Failed: {ipAddress}", ConsoleColor.Red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!options.Silent)
|
if (!options.Silent && !suppressPerIpOutput)
|
||||||
{
|
{
|
||||||
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Inactive: {ipAddress}", ConsoleColor.Red);
|
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Inactive: {ipAddress}", ConsoleColor.Red);
|
||||||
}
|
}
|
||||||
@@ -169,11 +200,11 @@ internal static class Program
|
|||||||
|
|
||||||
if (previouslyActiveHosts.Count > 0 && !options.Silent)
|
if (previouslyActiveHosts.Count > 0 && !options.Silent)
|
||||||
{
|
{
|
||||||
PrintDifference(activeHosts, header);
|
PrintDifference(activeHosts, header, options);
|
||||||
}
|
}
|
||||||
else if (!options.Silent)
|
else if (!options.Silent)
|
||||||
{
|
{
|
||||||
PrintActiveHosts(activeHosts, header);
|
PrintActiveHosts(activeHosts, header, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.Silent && (options.JsonPath is not null || options.CsvPath is not null))
|
if (!options.Silent && (options.JsonPath is not null || options.CsvPath is not null))
|
||||||
@@ -187,7 +218,10 @@ internal static class Program
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = Directory.CreateDirectory(Path.GetDirectoryName(jsonPath) ?? string.Empty);
|
if (!string.IsNullOrWhiteSpace(Path.GetDirectoryName(jsonPath)))
|
||||||
|
{
|
||||||
|
_ = Directory.CreateDirectory(Path.GetDirectoryName(jsonPath)!);
|
||||||
|
}
|
||||||
|
|
||||||
string json = JsonSerializer.Serialize(JsonResult.Parse([.. activeHosts], previouslyActiveHosts), jsonSerializerOptions);
|
string json = JsonSerializer.Serialize(JsonResult.Parse([.. activeHosts], previouslyActiveHosts), jsonSerializerOptions);
|
||||||
File.WriteAllText(jsonPath, json);
|
File.WriteAllText(jsonPath, json);
|
||||||
@@ -216,7 +250,11 @@ internal static class Program
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = Directory.CreateDirectory(Path.GetDirectoryName(csvPath) ?? string.Empty);
|
if (!string.IsNullOrWhiteSpace(Path.GetDirectoryName(csvPath)))
|
||||||
|
{
|
||||||
|
_ = Directory.CreateDirectory(Path.GetDirectoryName(csvPath)!);
|
||||||
|
}
|
||||||
|
|
||||||
File.WriteAllLines(csvPath, activeHostsTable.Select(row => string.Join(",", row)));
|
File.WriteAllLines(csvPath, activeHostsTable.Select(row => string.Join(",", row)));
|
||||||
|
|
||||||
if (!options.Silent)
|
if (!options.Silent)
|
||||||
@@ -276,67 +314,206 @@ internal static class Program
|
|||||||
return newPath;
|
return newPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintActiveHosts(ConcurrentBag<string[]> activeHosts, List<string> header)
|
private static void PrintActiveHosts(ConcurrentBag<string[]> activeHosts, List<string> header, ScanOptions options)
|
||||||
{
|
{
|
||||||
List<string[]>? activeHostsTable = [.. activeHosts];
|
IEnumerable<string[]> filtered = FilterByVendorAndMacPrefix(activeHosts, options);
|
||||||
|
|
||||||
|
List<string[]> activeHostsTable = [.. filtered];
|
||||||
activeHostsTable.Insert(0, [.. header]);
|
activeHostsTable.Insert(0, [.. header]);
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
if (!activeHosts.IsEmpty)
|
int filteredCount = GetHostCount(activeHostsTable);
|
||||||
|
|
||||||
|
if (filteredCount > 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Active hosts:");
|
PrintSection("Active hosts", "active host", activeHostsTable, options, ConsoleColor.Green);
|
||||||
|
|
||||||
activeHostsTable.ToArray().To2D().PrintTable(TableStyle.List);
|
|
||||||
|
|
||||||
ConsoleExt.WriteLine($"{Environment.NewLine}Found {"active host".ToQuantity(activeHosts.Count)}", ConsoleColor.Green);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ConsoleExt.WriteLine($"No active hosts found", ConsoleColor.Red);
|
ConsoleExt.WriteLine("No active hosts found", ConsoleColor.Red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintDifference(ConcurrentBag<string[]> activeHosts, List<string> header)
|
private static void PrintDifference(ConcurrentBag<string[]> activeHosts, List<string> header, ScanOptions options)
|
||||||
{
|
{
|
||||||
List<string[]> newHosts = activeHosts.Where(activeHost => !previouslyActiveHosts.Exists(previousHost => previousHost[1] == activeHost[1])).ToList();
|
List<string[]> newHosts = activeHosts.Where(activeHost => !previouslyActiveHosts.Exists(previousHost => previousHost[1] == activeHost[1])).ToList();
|
||||||
List<string[]> removedHosts = previouslyActiveHosts.Where(previousHost => !activeHosts.Any(activeHost => activeHost[1] == previousHost[1])).ToList();
|
List<string[]> removedHosts = previouslyActiveHosts.Where(previousHost => !activeHosts.Any(activeHost => activeHost[1] == previousHost[1])).ToList();
|
||||||
|
|
||||||
List<string[]>? newHostsTable = [.. newHosts];
|
List<string[]> newHostsTable = [.. FilterByVendorAndMacPrefix(newHosts, options)];
|
||||||
newHostsTable.Insert(0, [.. header]);
|
newHostsTable.Insert(0, [.. header]);
|
||||||
|
|
||||||
List<string[]>? removedHostsTable = [.. removedHosts];
|
List<string[]> removedHostsTable = [.. FilterByVendorAndMacPrefix(removedHosts, options)];
|
||||||
removedHostsTable.Insert(0, [.. header]);
|
removedHostsTable.Insert(0, [.. header]);
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
if (newHosts.Count > 0)
|
bool isMonitoring = options is MonitorOptions;
|
||||||
|
|
||||||
|
int newCount = GetHostCount(newHostsTable);
|
||||||
|
|
||||||
|
if (newCount > 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"New hosts:");
|
PrintSection("New hosts", "active host", newHostsTable, options, ConsoleColor.Green, " (summary mode)");
|
||||||
|
|
||||||
newHostsTable.ToArray().To2D().PrintTable(TableStyle.List);
|
|
||||||
|
|
||||||
ConsoleExt.WriteLine($"{Environment.NewLine}Found {"active host".ToQuantity(activeHosts.Count)}", ConsoleColor.Green);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ConsoleExt.WriteLine($"No new hosts found", ConsoleColor.Blue);
|
ConsoleExt.WriteLine("No new hosts found", ConsoleColor.Blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
if (removedHosts.Count > 0)
|
int removedCount = GetHostCount(removedHostsTable);
|
||||||
|
|
||||||
|
if (removedCount > 0)
|
||||||
{
|
{
|
||||||
// Better term for removed host?
|
// Better term for removed host?
|
||||||
Console.WriteLine($"Previously active hosts:");
|
PrintSection("Previously active hosts", "previously active host", removedHostsTable, options, ConsoleColor.Red, " now inactive (summary mode)");
|
||||||
|
|
||||||
removedHostsTable.ToArray().To2D().PrintTable(TableStyle.List);
|
|
||||||
|
|
||||||
ConsoleExt.WriteLine($"{Environment.NewLine}Found {"previously active host".ToQuantity(removedHosts.Count)}", ConsoleColor.Red);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ConsoleExt.WriteLine($"No previously active hosts are currently inactive", ConsoleColor.Blue);
|
ConsoleExt.WriteLine("No previously active hosts are currently inactive", ConsoleColor.Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMonitoring && options is MonitorOptions monitorOptions)
|
||||||
|
{
|
||||||
|
// Execute user-defined commands for new and removed hosts, if configured.
|
||||||
|
JsonResult jsonResult = JsonResult.Parse([.. activeHosts], previouslyActiveHosts);
|
||||||
|
|
||||||
|
if (newHosts.Count > 0 && !string.IsNullOrWhiteSpace(monitorOptions.OnNewCommand))
|
||||||
|
{
|
||||||
|
ExecuteMonitorCommand(monitorOptions.OnNewCommand!, jsonResult.NewHosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedHosts.Count > 0 && !string.IsNullOrWhiteSpace(monitorOptions.OnRemovedCommand))
|
||||||
|
{
|
||||||
|
ExecuteMonitorCommand(monitorOptions.OnRemovedCommand!, jsonResult.RemovedHosts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string[]> FilterByVendorAndMacPrefix(IEnumerable<string[]> hosts, ScanOptions options)
|
||||||
|
{
|
||||||
|
IEnumerable<string[]> filtered = hosts;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(options.VendorFilter))
|
||||||
|
{
|
||||||
|
string vendorFilter = options.VendorFilter.Trim();
|
||||||
|
filtered = filtered.Where(h => h.Length > 2 && h[2].Contains(vendorFilter, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(options.MacPrefixFilter))
|
||||||
|
{
|
||||||
|
string macPrefix = options.MacPrefixFilter.Trim().Replace("-", ":").ToUpperInvariant();
|
||||||
|
filtered = filtered.Where(h => h.Length > 1 && h[1].Replace("-", ":").ToUpperInvariant().StartsWith(macPrefix, StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetHostCount(List<string[]> tableWithHeader)
|
||||||
|
{
|
||||||
|
// subtract one for header row
|
||||||
|
return Math.Max(0, tableWithHeader.Count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintSection(string title, string itemLabel, List<string[]> tableWithHeader, ScanOptions options, ConsoleColor color, string? summarySuffix = null)
|
||||||
|
{
|
||||||
|
int count = GetHostCount(tableWithHeader);
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsSummaryMode(options))
|
||||||
|
{
|
||||||
|
string suffix = summarySuffix ?? " (summary mode)";
|
||||||
|
ConsoleExt.WriteLine($"{title} {itemLabel.ToQuantity(count)}{suffix}", color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine(title + ":");
|
||||||
|
tableWithHeader.ToArray().To2D().PrintTable(TableStyle.List);
|
||||||
|
ConsoleExt.WriteLine($"{Environment.NewLine}Found {itemLabel.ToQuantity(count)}", color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSummaryMode(ScanOptions options)
|
||||||
|
{
|
||||||
|
return options is MonitorOptions monitorOptions && monitorOptions.Summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<IPAddress> GetLocalSubnets()
|
||||||
|
{
|
||||||
|
List<IPAddress> addresses = [];
|
||||||
|
|
||||||
|
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
|
||||||
|
{
|
||||||
|
if (networkInterface.OperationalStatus != OperationalStatus.Up)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPInterfaceProperties ipProperties = networkInterface.GetIPProperties();
|
||||||
|
foreach (UnicastIPAddressInformation unicast in ipProperties.UnicastAddresses)
|
||||||
|
{
|
||||||
|
if (unicast.Address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int prefixLength = unicast.PrefixLength;
|
||||||
|
if (prefixLength is <= 0 or > 32)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint mask = prefixLength == 0 ? 0 : uint.MaxValue << (32 - prefixLength);
|
||||||
|
byte[] ipBytes = unicast.Address.GetAddressBytes();
|
||||||
|
uint ip = (uint)((ipBytes[0] << 24) | (ipBytes[1] << 16) | (ipBytes[2] << 8) | ipBytes[3]);
|
||||||
|
uint network = ip & mask;
|
||||||
|
uint broadcast = network | ~mask;
|
||||||
|
|
||||||
|
for (uint addr = network + 1; addr < broadcast; addr++)
|
||||||
|
{
|
||||||
|
byte[] addrBytes =
|
||||||
|
[
|
||||||
|
(byte)((addr >> 24) & 0xFF),
|
||||||
|
(byte)((addr >> 16) & 0xFF),
|
||||||
|
(byte)((addr >> 8) & 0xFF),
|
||||||
|
(byte)(addr & 0xFF)
|
||||||
|
];
|
||||||
|
addresses.Add(new IPAddress(addrBytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ExecuteMonitorCommand(string command, IEnumerable<JsonResult.HostInformation> hosts)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProcessStartInfo psi = new()
|
||||||
|
{
|
||||||
|
FileName = OperatingSystem.IsWindows() ? "cmd.exe" : "/bin/sh",
|
||||||
|
Arguments = OperatingSystem.IsWindows() ? $"/C {command}" : $"-c \"{command}\"",
|
||||||
|
RedirectStandardInput = true,
|
||||||
|
RedirectStandardOutput = false,
|
||||||
|
RedirectStandardError = false,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
using Process process = Process.Start(psi)!;
|
||||||
|
string json = JsonSerializer.Serialize(hosts, jsonSerializerOptions);
|
||||||
|
process.StandardInput.WriteLine(json);
|
||||||
|
process.StandardInput.Close();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ConsoleExt.WriteLine($"Failed to execute monitor command '{command}': {ex.Message}", ConsoleColor.Red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,8 @@ namespace ARP_Scanner;
|
|||||||
[Verb("scan", true, HelpText = "Scan the specified IP range.")]
|
[Verb("scan", true, HelpText = "Scan the specified IP range.")]
|
||||||
internal class ScanOptions
|
internal class ScanOptions
|
||||||
{
|
{
|
||||||
[Value(0, Required = true, MetaName = "IP range", HelpText = "The IP range to scan.")]
|
[Value(0, Required = false, MetaName = "IP range", HelpText = "The IP range to scan.")]
|
||||||
public required string IpRange { get; set; }
|
public string? IpRange { get; set; }
|
||||||
|
|
||||||
[Option('s', "silent", Required = false, HelpText = "Don't print anything to the console.")]
|
[Option('s', "silent", Required = false, HelpText = "Don't print anything to the console.")]
|
||||||
public bool Silent { get; set; }
|
public bool Silent { get; set; }
|
||||||
@@ -22,4 +22,13 @@ internal class ScanOptions
|
|||||||
|
|
||||||
[Option("csv", Required = false, HelpText = "The path to the CSV file to save the results.")]
|
[Option("csv", Required = false, HelpText = "The path to the CSV file to save the results.")]
|
||||||
public string? CsvPath { get; set; }
|
public string? CsvPath { get; set; }
|
||||||
|
|
||||||
|
[Option("auto", Required = false, HelpText = "Automatically scan all local IPv4 subnets when no IP range is specified.")]
|
||||||
|
public bool AutoDiscover { get; set; }
|
||||||
|
|
||||||
|
[Option("vendor", Required = false, HelpText = "Filter results by vendor name (case-insensitive substring).")]
|
||||||
|
public string? VendorFilter { get; set; }
|
||||||
|
|
||||||
|
[Option("mac-prefix", Required = false, HelpText = "Filter results by MAC prefix (e.g., 00:11:22).")]
|
||||||
|
public string? MacPrefixFilter { get; set; }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user