using System; using System.Collections.Generic; using System.Net; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace LanDiscovery.Services { public class ArpResult { public required IPAddress Ip { get; set; } public string MacAddress { get; set; } = string.Empty; public bool Found { get; set; } } public class ArpActiveScanner { [DllImport("iphlpapi.dll", ExactSpelling = true)] private static extern int SendARP(uint DestIP, uint SrcIP, byte[] pMacAddr, ref uint PhyAddrLen); private const int MaxConcurrency = 64; // SendARP can be blocking/slow if not careful, but it's per-IP public async Task ScanRangeAsync(IEnumerable ips, IPAddress sourceIp, IProgress<(int scanned, int total)> progress, Func onResult, CancellationToken ct) { var ipList = new List(ips); int total = ipList.Count; int scanned = 0; // Convert source IP to uint once #pragma warning disable CS0618 uint srcIpInt = (uint)BitConverter.ToInt32(sourceIp.GetAddressBytes(), 0); #pragma warning restore CS0618 using var semaphore = new SemaphoreSlim(MaxConcurrency); var tasks = new List(); foreach (var ip in ipList) { if (ct.IsCancellationRequested) break; await semaphore.WaitAsync(ct); tasks.Add(Task.Run(async () => { try { if (ct.IsCancellationRequested) return; var (found, mac) = TrySendArp(ip, srcIpInt); var result = new ArpResult { Ip = ip, Found = found, MacAddress = mac }; await onResult(result); } finally { semaphore.Release(); Interlocked.Increment(ref scanned); progress?.Report((scanned, total)); } }, ct)); } await Task.WhenAll(tasks); } private (bool found, string mac) TrySendArp(IPAddress ip, uint srcIpInt) { byte[] macBytes = new byte[6]; uint macLen = (uint)macBytes.Length; // IP to uint #pragma warning disable CS0618 // Type or member is obsolete uint destIp = (uint)BitConverter.ToInt32(ip.GetAddressBytes(), 0); #pragma warning restore CS0618 int ret = SendARP(destIp, srcIpInt, macBytes, ref macLen); if (ret == 0) { var macStr = BitConverter.ToString(macBytes, 0, (int)macLen); return (true, macStr); } return (false, string.Empty); } } }