FPGA_WebLab/server/src/ArpClient.cs

635 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Diagnostics;
using System.Net.NetworkInformation;
using ArpLookup;
using System.Runtime.InteropServices;
using System.Net;
using System.Text.RegularExpressions;
/// <summary>
/// ARP 记录管理静态类(跨平台支持)
/// </summary>
public static class ArpClient
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// 读取所有 ARP 记录
/// </summary>
/// <returns>ARP 记录列表</returns>
public static async Task<List<ArpEntry>> GetArpTableAsync()
{
var entries = new List<ArpEntry>();
try
{
string command = GetArpListCommand();
var result = await ExecuteCommandAsync(command);
if (result.IsSuccess)
{
var lines = result.Output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var entry = ParseArpEntry(line);
if (entry != null)
{
entries.Add(entry);
}
}
}
}
catch (Exception ex)
{
throw new Exception($"读取 ARP 表失败: {ex.Message}");
}
return entries;
}
/// <summary>
/// 动态更新指定 IP 的 ARP 记录
/// </summary>
/// <param name="ipAddress">要更新的 IP 地址</param>
/// <returns>是否成功发送 Ping</returns>
public static async Task<bool> UpdateArpEntryAsync(string ipAddress)
{
if (string.IsNullOrWhiteSpace(ipAddress))
throw new ArgumentException("IP 地址不能为空", nameof(ipAddress));
try
{
var ret = await ArpClient.DeleteArpEntryAsync(ipAddress);
if (!ret)
{
logger.Error($"删除 ARP 记录失败: {ipAddress}");
}
PhysicalAddress? mac = await Arp.LookupAsync(IPAddress.Parse(ipAddress));
if (mac == null)
return false;
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 添加 ARP 记录
/// </summary>
/// <param name="ipAddress">IP 地址</param>
/// <param name="macAddress">MAC 地址</param>
/// <param name="interfaceName">网络接口名称(可选)</param>
/// <returns>是否成功</returns>
public static async Task<bool> AddArpEntryAsync(string ipAddress, string macAddress, string? interfaceName = null)
{
if (string.IsNullOrWhiteSpace(ipAddress))
throw new ArgumentException("IP 地址不能为空", nameof(ipAddress));
if (string.IsNullOrWhiteSpace(macAddress))
throw new ArgumentException("MAC 地址不能为空", nameof(macAddress));
try
{
// 格式化 MAC 地址以适配不同操作系统
string formattedMac = FormatMacAddress(macAddress);
string command = await GetArpAddCommandAsync(ipAddress, formattedMac, interfaceName);
var result = await ExecuteCommandAsync(command);
return result.IsSuccess;
}
catch (Exception ex)
{
throw new Exception($"添加 ARP 记录失败: {ex.Message}");
}
}
/// <summary>
/// 删除 ARP 记录
/// </summary>
/// <param name="ipAddress">要删除的 IP 地址</param>
/// <param name="interfaceName">网络接口名称(可选)</param>
/// <returns>是否成功</returns>
public static async Task<bool> DeleteArpEntryAsync(string ipAddress, string? interfaceName = null)
{
if (string.IsNullOrWhiteSpace(ipAddress))
throw new ArgumentException("IP 地址不能为空", nameof(ipAddress));
try
{
string command = GetArpDeleteCommand(ipAddress, interfaceName);
var result = await ExecuteCommandAsync(command);
return result.IsSuccess;
}
catch (Exception ex)
{
throw new Exception($"删除 ARP 记录失败: {ex.Message}");
}
}
/// <summary>
/// 清空所有 ARP 记录
/// </summary>
/// <returns>是否成功</returns>
public static async Task<bool> ClearArpTableAsync()
{
try
{
string command = GetArpClearCommand();
var result = await ExecuteCommandAsync(command);
return result.IsSuccess;
}
catch (Exception ex)
{
throw new Exception($"清空 ARP 表失败: {ex.Message}");
}
}
/// <summary>
/// 查询特定 IP 的 ARP 记录
/// </summary>
/// <param name="ipAddress">IP 地址</param>
/// <returns>ARP 记录,如果不存在则返回 null</returns>
public static async Task<ArpEntry?> GetArpEntryAsync(string ipAddress)
{
if (string.IsNullOrWhiteSpace(ipAddress))
throw new ArgumentException("IP 地址不能为空", nameof(ipAddress));
try
{
string command = GetArpQueryCommand(ipAddress);
var result = await ExecuteCommandAsync(command);
if (result.IsSuccess)
{
var lines = result.Output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var entry = ParseArpEntry(line);
if (entry != null && entry.IpAddress == ipAddress)
{
return entry;
}
}
}
}
catch (Exception ex)
{
throw new Exception($"查询 ARP 记录失败: {ex.Message}");
}
return null;
}
/// <summary>
/// 获取 ARP 列表命令
/// </summary>
private static string GetArpListCommand()
{
return "arp -a";
}
/// <summary>
/// 获取 ARP 添加命令
/// </summary>
private static async Task<string> GetArpAddCommandAsync(string ipAddress, string macAddress, string? interfaceName)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (!string.IsNullOrWhiteSpace(interfaceName))
{
// 通过 arp -a 获取接口索引
var interfaceIdx = await GetWindowsInterfaceIndexAsync(interfaceName);
if (interfaceIdx.HasValue)
{
return $"netsh -c i i add neighbors {interfaceIdx.Value} {ipAddress} {macAddress}";
}
}
return $"arp -s {ipAddress} {macAddress}";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return string.IsNullOrWhiteSpace(interfaceName)
? $"arp -s {ipAddress} {macAddress}"
: $"arp -s {ipAddress} {macAddress} -i {interfaceName}";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return string.IsNullOrWhiteSpace(interfaceName)
? $"arp -s {ipAddress} {macAddress}"
: $"arp -s {ipAddress} {macAddress} ifscope {interfaceName}";
}
else
{
throw new PlatformNotSupportedException("不支持的操作系统平台");
}
}
/// <summary>
/// 获取 Windows 接口索引
/// </summary>
/// <param name="interfaceIp">接口IP地址</param>
/// <returns>接口索引十进制如果未找到则返回null</returns>
private static async Task<int?> GetWindowsInterfaceIndexAsync(string interfaceIp)
{
try
{
var result = await ExecuteCommandAsync("arp -a");
if (!result.IsSuccess)
return null;
var lines = result.Output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
// 匹配接口行格式: Interface: 172.6.1.5 --- 0xa
var interfacePattern = @"Interface:\s+(\d+\.\d+\.\d+\.\d+)\s+---\s+(0x[a-fA-F0-9]+)";
var match = Regex.Match(line, interfacePattern);
if (match.Success && match.Groups[1].Value == interfaceIp)
{
// 将十六进制索引转换为十进制
var hexIndex = match.Groups[2].Value;
// 去掉 "0x" 前缀
var hexValue = hexIndex.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? hexIndex.Substring(2)
: hexIndex;
if (int.TryParse(hexValue, System.Globalization.NumberStyles.HexNumber, null, out int decimalIndex))
{
logger.Debug($"找到接口 {interfaceIp} 的索引: {hexIndex} -> {decimalIndex}");
return decimalIndex;
}
}
}
logger.Warn($"未找到接口 {interfaceIp} 的索引");
return null;
}
catch (Exception ex)
{
logger.Error(ex, $"获取接口 {interfaceIp} 索引失败");
return null;
}
}
/// <summary>
/// 获取 ARP 删除命令
/// </summary>
private static string GetArpDeleteCommand(string ipAddress, string? interfaceName)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return $"arp -d {ipAddress}";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return string.IsNullOrWhiteSpace(interfaceName)
? $"arp -d {ipAddress}"
: $"arp -d {ipAddress} -i {interfaceName}";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return string.IsNullOrWhiteSpace(interfaceName)
? $"arp -d {ipAddress}"
: $"arp -d {ipAddress} ifscope {interfaceName}";
}
else
{
throw new PlatformNotSupportedException("不支持的操作系统平台");
}
}
/// <summary>
/// 获取 ARP 清空命令
/// </summary>
private static string GetArpClearCommand()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return "arp -d *";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "ip neigh flush all";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return "arp -d -a";
}
else
{
throw new PlatformNotSupportedException("不支持的操作系统平台");
}
}
/// <summary>
/// 获取 ARP 查询命令
/// </summary>
private static string GetArpQueryCommand(string ipAddress)
{
return $"arp -a {ipAddress}";
}
/// <summary>
/// 执行系统命令
/// </summary>
/// <param name="command">命令</param>
/// <returns>命令执行结果</returns>
private static async Task<CommandResult> ExecuteCommandAsync(string command)
{
try
{
ProcessStartInfo processInfo;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
processInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c {command}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
}
else
{
processInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{command}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
}
logger.Debug($"Executing command: {processInfo.FileName} {processInfo.Arguments}");
using var process = new Process { StartInfo = processInfo };
process.Start();
var output = await process.StandardOutput.ReadToEndAsync();
var error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
logger.Debug($"Command output: {output}");
if (!string.IsNullOrWhiteSpace(error))
logger.Debug($"Command error: {error}");
logger.Debug($"Command exit code: {process.ExitCode}");
return new CommandResult
{
IsSuccess = process.ExitCode == 0,
Output = output,
Error = error,
ExitCode = process.ExitCode
};
}
catch (Exception ex)
{
logger.Error(ex, $"Command execution failed: {command}");
return new CommandResult
{
IsSuccess = false,
Error = ex.Message,
ExitCode = -1
};
}
}
/// <summary>
/// 解析 ARP 记录行
/// </summary>
/// <param name="line">ARP 记录行</param>
/// <returns>解析后的 ARP 记录</returns>
private static ArpEntry? ParseArpEntry(string line)
{
if (string.IsNullOrWhiteSpace(line))
return null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return ParseWindowsArpEntry(line);
}
else
{
return ParseUnixArpEntry(line);
}
}
/// <summary>
/// 解析 Windows ARP 记录
/// </summary>
private static ArpEntry? ParseWindowsArpEntry(string line)
{
// 跳过空行和标题行
if (string.IsNullOrWhiteSpace(line) ||
line.Contains("Interface:") ||
line.Contains("Internet Address") ||
line.Contains("Physical Address") ||
line.Contains("Type"))
{
return null;
}
// Windows arp -a 输出格式: IP地址 物理地址 类型
// 示例: 172.6.0.1 e4-3a-6e-29-c3-5b dynamic
var pattern = @"^\s*(\d+\.\d+\.\d+\.\d+)\s+([a-fA-F0-9-]{17})\s+(\w+)\s*$";
var match = Regex.Match(line, pattern);
if (match.Success)
{
return new ArpEntry
{
IpAddress = match.Groups[1].Value,
MacAddress = FormatMacAddress(match.Groups[2].Value), // 格式化 MAC 地址
Type = match.Groups[3].Value
};
}
return null;
}
/// <summary>
/// 解析 Unix/Linux ARP 记录
/// </summary>
private static ArpEntry? ParseUnixArpEntry(string line)
{
// Unix/Linux arp -a 输出格式: hostname (ip) at mac [ether] PERM on interface
var pattern = @"(\S+)\s+\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([a-fA-F0-9:]{17})\s+\[(\w+)\]\s+(\w+)\s+on\s+(\S+)";
var match = Regex.Match(line, pattern);
if (match.Success)
{
return new ArpEntry
{
Hostname = match.Groups[1].Value,
IpAddress = match.Groups[2].Value,
MacAddress = FormatMacAddress(match.Groups[3].Value), // 格式化 MAC 地址
Type = match.Groups[5].Value,
Interface = match.Groups[6].Value
};
}
// 匹配简单格式: ip mac interface
var simplePattern = @"(\d+\.\d+\.\d+\.\d+)\s+([a-fA-F0-9:]{17})\s+(\S+)";
var simpleMatch = Regex.Match(line, simplePattern);
if (simpleMatch.Success)
{
return new ArpEntry
{
IpAddress = simpleMatch.Groups[1].Value,
MacAddress = FormatMacAddress(simpleMatch.Groups[2].Value), // 格式化 MAC 地址
Interface = simpleMatch.Groups[3].Value
};
}
return null;
}
/// <summary>
/// 判断当前进程是否具有管理员(或 root权限
/// </summary>
/// <returns>如果有管理员权限返回 true否则返回 false</returns>
public static bool IsAdministrator()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Windows: 检查当前用户是否为管理员
using var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var principal = new System.Security.Principal.WindowsPrincipal(identity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
else
{
// Unix/Linux/macOS: 检查是否为 root 用户
return Environment.UserName == "root" || (Environment.GetEnvironmentVariable("USER") == "root");
}
}
/// <summary>
/// 检查指定 IP 是否存在对应的 MAC如果不存在则删除原有 ARP 记录并新增
/// </summary>
/// <param name="ipAddress">IP 地址</param>
/// <param name="macAddress">MAC 地址</param>
/// <param name="interfaceName">网络接口名称(可选)</param>
/// <returns>是否成功</returns>
public static async Task<bool> CheckOrAddAsync(string ipAddress, string macAddress, string? interfaceName = null)
{
if (string.IsNullOrWhiteSpace(ipAddress))
throw new ArgumentException("IP 地址不能为空", nameof(ipAddress));
if (string.IsNullOrWhiteSpace(macAddress))
throw new ArgumentException("MAC 地址不能为空", nameof(macAddress));
// 格式化 MAC 地址以适配不同操作系统
string formattedMac = FormatMacAddress(macAddress);
var entry = await GetArpEntryAsync(ipAddress);
if (entry != null && string.Equals(FormatMacAddress(entry.MacAddress), formattedMac, StringComparison.OrdinalIgnoreCase))
{
// 已存在且 MAC 匹配,无需操作
return true;
}
// 若存在但 MAC 不匹配,先删除
if (entry != null)
{
await DeleteArpEntryAsync(ipAddress, interfaceName);
}
// 新增 ARP 记录
var ret = await AddArpEntryAsync(ipAddress, formattedMac, interfaceName);
if (!ret) logger.Error($"添加 ARP 记录失败: {ipAddress} -> {formattedMac} on {interfaceName}");
return true;
}
/// <summary>
/// 格式化 MAC 地址为指定平台格式
/// </summary>
/// <param name="macAddress">原始 MAC 地址</param>
/// <returns>格式化后的 MAC 地址</returns>
public static string FormatMacAddress(string macAddress)
{
if (string.IsNullOrWhiteSpace(macAddress))
return string.Empty;
var cleaned = macAddress.Replace("-", "").Replace(":", "").ToLowerInvariant();
if (cleaned.Length != 12)
return macAddress;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Windows: XX-XX-XX-XX-XX-XX
return string.Join("-", Enumerable.Range(0, 6).Select(i => cleaned.Substring(i * 2, 2)));
}
else
{
// Unix/Linux/macOS: xx:xx:xx:xx:xx:xx
return string.Join(":", Enumerable.Range(0, 6).Select(i => cleaned.Substring(i * 2, 2)));
}
}
}
/// <summary>
/// ARP 记录条目
/// </summary>
public class ArpEntry
{
/// <summary>
/// [TODO:description]
/// </summary>
public string Hostname { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string IpAddress { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string MacAddress { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string Type { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string Interface { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public override string ToString()
{
return $"{IpAddress} -> {MacAddress} ({Interface})";
}
}
/// <summary>
/// 命令执行结果
/// </summary>
public class CommandResult
{
/// <summary>
/// [TODO:description]
/// </summary>
public bool IsSuccess { get; set; }
/// <summary>
/// [TODO:description]
/// </summary>
public string Output { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public string Error { get; set; } = string.Empty;
/// <summary>
/// [TODO:description]
/// </summary>
public int ExitCode { get; set; }
}