获取局域网所有IP是很多内网工具开发的基础需求,结合ARP广播和Ping并发的方式可以在保证扫描速度的同时提高结果准确性。ARP广播可以直接从系统ARP缓存中获取已通信设备的IP信息,Ping并发则可以批量检测未出现在ARP缓存中的存活IP,两者互补能覆盖更多场景。
一、ARP广播获取局域网IP
ARP协议的作用是将IP地址解析为对应的MAC地址,系统会维护一个ARP缓存表,记录近期通信过的设备的IP和MAC对应关系。我们可以通过读取系统的ARP缓存表,快速获取同网段内已经有过通信的设备的IP信息,这种方式不需要发送探测包,速度非常快。
1. ARP缓存读取实现
在C#中可以通过调用系统命令arp -a获取ARP缓存内容,然后解析输出结果提取IP地址。以下是具体实现代码:
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections.Generic;
public class ArpScanner
{
/// <summary>
/// 获取ARP缓存中的所有IP地址
/// </summary>
/// <returns>IP地址列表</returns>
public static List<string> GetArpIps()
{
List<string> ipList = new List<string>();
try
{
Process process = new Process();
process.StartInfo.FileName = "arp";
process.StartInfo.Arguments = "-a";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
// 匹配IP地址的正则表达式
Regex ipRegex = new Regex(@"(d{1,3}.){3}d{1,3}");
MatchCollection matches = ipRegex.Matches(output);
foreach (Match match in matches)
{
string ip = match.Value;
// 过滤无效IP和回环地址
if (IsValidIp(ip) && !ip.StartsWith("127.") && !ipList.Contains(ip))
{
ipList.Add(ip);
}
}
}
catch (Exception ex)
{
Console.WriteLine("读取ARP缓存失败:" + ex.Message);
}
return ipList;
}
/// <summary>
/// 验证IP地址是否合法
/// </summary>
private static bool IsValidIp(string ip)
{
string[] parts = ip.Split('.');
if (parts.Length != 4) return false;
foreach (string part in parts)
{
if (!int.TryParse(part, out int num) || num < 0 || num > 255)
{
return false;
}
}
return true;
}
}
2. 使用说明
调用GetArpIps方法即可直接获取当前系统ARP缓存中的所有合法IP地址,但是这种方式只能获取已经和本机有过通信的设备IP,新接入局域网且未通信的设备无法被检测到。
二、Ping并发扫描局域网IP
为了获取更多未出现在ARP缓存中的存活IP,我们可以使用Ping的方式批量检测同网段的所有IP地址。使用并发Ping可以大幅提升扫描速度,避免逐个Ping等待超时导致的耗时过长问题。
1. 并发Ping实现
以下是使用Task实现并发Ping扫描的代码,支持自定义扫描的IP段和并发数量:
using System;
using System.Net.NetworkInformation;
using System.Collections.Generic;
using System.Threading.Tasks;
public class PingScanner
{
/// <summary>
/// 并发Ping扫描指定网段的所有IP
/// </summary>
/// <param name="baseIp">网段基础IP,例如192.168.1</param>
/// <param name="start">起始主机位</param>
/// <param name="end">结束主机位</param>
/// <param name="timeout">Ping超时时间(毫秒)</param>
/// <returns>存活的IP地址列表</returns>
public static async Task<List<string>> ScanIpsAsync(string baseIp, int start, int end, int timeout = 1000)
{
List<string> aliveIps = new List<string>();
List<Task> tasks = new List<Task>();
for (int i = start; i <= end; i++)
{
string ip = $"{baseIp}.{i}";
tasks.Add(Task.Run(async () =>
{
try
{
Ping ping = new Ping();
PingReply reply = await ping.SendPingAsync(ip, timeout);
if (reply.Status == IPStatus.Success)
{
lock (aliveIps)
{
aliveIps.Add(ip);
}
}
}
catch
{
// 忽略Ping失败的异常
}
}));
}
await Task.WhenAll(tasks);
return aliveIps;
}
}
2. 使用说明
调用ScanIpsAsync方法时,传入网段基础IP、起始主机位、结束主机位和超时时间即可,方法会返回所有Ping通的IP地址。例如扫描192.168.1.1到192.168.1.254的网段,可以调用await PingScanner.ScanIpsAsync("192.168.1", 1, 254);。
三、两种方式结合实现完整扫描
将ARP缓存获取和Ping并发扫描结合,既可以利用ARP的快速特性,又能覆盖未通信的存活设备,实现更完整的局域网IP扫描。以下是组合使用的示例代码:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("开始扫描局域网IP...");
// 第一步:获取ARP缓存中的IP
List<string> arpIps = ArpScanner.GetArpIps();
Console.WriteLine($"ARP缓存中获取到{arpIps.Count}个IP:");
foreach (string ip in arpIps)
{
Console.WriteLine(ip);
}
// 第二步:并发Ping扫描整个C段,这里假设本机网段是192.168.1
Console.WriteLine("开始Ping扫描整个网段...");
List<string> pingIps = await PingScanner.ScanIpsAsync("192.168.1", 1, 254);
Console.WriteLine($"Ping扫描获取到{pingIps.Count}个存活IP:");
foreach (string ip in pingIps)
{
Console.WriteLine(ip);
}
// 合并去重结果
HashSet<string> allIps = new HashSet<string>(arpIps);
foreach (string ip in pingIps)
{
allIps.Add(ip);
}
Console.WriteLine($"最终扫描到{allIps.Count}个局域网IP:");
foreach (string ip in allIps)
{
Console.WriteLine(ip);
}
}
}
四、注意事项
- ARP缓存获取方式依赖系统ARP表,不同系统的ARP缓存过期时间不同,可能无法获取所有设备
- 并发Ping扫描不要设置过高的并发数,避免被系统判定为异常流量,建议并发数控制在50以内
- 扫描前需要确认本机所在的网段,避免扫描非本网的IP段导致无效请求
- 部分设备可能禁用了ICMP响应,会导致Ping扫描无法检测到这些设备