From 69c7cbf4d804093c89dc6305ea7fec65ab2e9cac Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Thu, 17 Jul 2025 21:56:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=80=E5=8D=95=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E9=9D=99=E6=80=81arp=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/ArpClient.cs | 421 ++++++++++++++++++++++++++++++++++++++++ server/src/UdpServer.cs | 236 ---------------------- 2 files changed, 421 insertions(+), 236 deletions(-) create mode 100644 server/src/ArpClient.cs diff --git a/server/src/ArpClient.cs b/server/src/ArpClient.cs new file mode 100644 index 0000000..6a806df --- /dev/null +++ b/server/src/ArpClient.cs @@ -0,0 +1,421 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +/// +/// ARP 记录管理静态类(跨平台支持) +/// +public static class Arp +{ + /// + /// 读取所有 ARP 记录 + /// + /// ARP 记录列表 + public static async Task> GetArpTableAsync() + { + var entries = new List(); + + 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; + } + + /// + /// 添加 ARP 记录 + /// + /// IP 地址 + /// MAC 地址 + /// 网络接口名称(可选) + /// 是否成功 + public static async Task 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 + { + string command = GetArpAddCommand(ipAddress, macAddress, interfaceName); + var result = await ExecuteCommandAsync(command); + return result.IsSuccess; + } + catch (Exception ex) + { + throw new Exception($"添加 ARP 记录失败: {ex.Message}"); + } + } + + /// + /// 删除 ARP 记录 + /// + /// 要删除的 IP 地址 + /// 网络接口名称(可选) + /// 是否成功 + public static async Task 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}"); + } + } + + /// + /// 清空所有 ARP 记录 + /// + /// 是否成功 + public static async Task ClearArpTableAsync() + { + try + { + string command = GetArpClearCommand(); + var result = await ExecuteCommandAsync(command); + return result.IsSuccess; + } + catch (Exception ex) + { + throw new Exception($"清空 ARP 表失败: {ex.Message}"); + } + } + + /// + /// 查询特定 IP 的 ARP 记录 + /// + /// IP 地址 + /// ARP 记录,如果不存在则返回 null + public static async Task 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; + } + + /// + /// 获取 ARP 列表命令 + /// + private static string GetArpListCommand() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? "arp -a" + : "arp -a"; + } + + /// + /// 获取 ARP 添加命令 + /// + private static string GetArpAddCommand(string ipAddress, string macAddress, string interfaceName) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return string.IsNullOrWhiteSpace(interfaceName) + ? $"arp -s {ipAddress} {macAddress}" + : $"arp -s {ipAddress} {macAddress} {interfaceName}"; + } + 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("不支持的操作系统平台"); + } + } + + /// + /// 获取 ARP 删除命令 + /// + 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("不支持的操作系统平台"); + } + } + + /// + /// 获取 ARP 清空命令 + /// + 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("不支持的操作系统平台"); + } + } + + /// + /// 获取 ARP 查询命令 + /// + private static string GetArpQueryCommand(string ipAddress) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return $"arp -a {ipAddress}"; + } + else + { + return $"arp -n {ipAddress}"; + } + } + + /// + /// 执行系统命令 + /// + /// 命令 + /// 命令执行结果 + private static async Task 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 + }; + } + + using var process = new Process { StartInfo = processInfo }; + process.Start(); + + var output = await process.StandardOutput.ReadToEndAsync(); + var error = await process.StandardError.ReadToEndAsync(); + + await process.WaitForExitAsync(); + + return new CommandResult + { + IsSuccess = process.ExitCode == 0, + Output = output, + Error = error, + ExitCode = process.ExitCode + }; + } + catch (Exception ex) + { + return new CommandResult + { + IsSuccess = false, + Error = ex.Message, + ExitCode = -1 + }; + } + } + + /// + /// 解析 ARP 记录行 + /// + /// ARP 记录行 + /// 解析后的 ARP 记录 + private static ArpEntry? ParseArpEntry(string line) + { + if (string.IsNullOrWhiteSpace(line)) + return null; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return ParseWindowsArpEntry(line); + } + else + { + return ParseUnixArpEntry(line); + } + } + + /// + /// 解析 Windows ARP 记录 + /// + private static ArpEntry? ParseWindowsArpEntry(string line) + { + // Windows arp -a 输出格式: IP地址 物理地址 类型 + var pattern = @"(\d+\.\d+\.\d+\.\d+)\s+([a-fA-F0-9-]{17})\s+(\w+)"; + var match = Regex.Match(line, pattern); + + if (match.Success) + { + return new ArpEntry + { + IpAddress = match.Groups[1].Value, + MacAddress = match.Groups[2].Value.Replace('-', ':'), + Type = match.Groups[3].Value + }; + } + + return null; + } + + /// + /// 解析 Unix/Linux ARP 记录 + /// + private static ArpEntry? ParseUnixArpEntry(string line) + { + // Unix/Linux arp -a 输出格式: hostname (ip) at mac [ether] on interface + var pattern = @"(\S+)\s+\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([a-fA-F0-9:]{17})\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 = match.Groups[3].Value, + Type = match.Groups[4].Value, + Interface = match.Groups[5].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 = simpleMatch.Groups[2].Value, + Interface = simpleMatch.Groups[3].Value + }; + } + + return null; + } +} + +/// +/// ARP 记录条目 +/// +public class ArpEntry +{ + public string Hostname { get; set; } = string.Empty; + public string IpAddress { get; set; } = string.Empty; + public string MacAddress { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public string Interface { get; set; } = string.Empty; + + public override string ToString() + { + return $"{IpAddress} -> {MacAddress} ({Interface})"; + } +} + +/// +/// 命令执行结果 +/// +public class CommandResult +{ + public bool IsSuccess { get; set; } + public string Output { get; set; } = string.Empty; + public string Error { get; set; } = string.Empty; + public int ExitCode { get; set; } +} diff --git a/server/src/UdpServer.cs b/server/src/UdpServer.cs index f5d9323..70a9cc6 100644 --- a/server/src/UdpServer.cs +++ b/server/src/UdpServer.cs @@ -585,242 +585,6 @@ public class UDPServer } } - // 强制进行ARP刷新,防止后续传输时造成影响 - // FlushArpEntry(ipAddr); - } - - /// - /// 跨平台ARP缓存刷新 - /// - /// 目标IP地址 - private void FlushArpEntry(string ipAddr) - { - try - { - // 验证IP地址格式 - if (!IPAddress.TryParse(ipAddr, out var _)) - { - logger.Warn($"Invalid IP address format: {ipAddr}"); - return; - } - - ExecuteArpFlush(ipAddr); - } - catch (Exception ex) - { - logger.Error($"Error during ARP cache flush for {ipAddr}: {ex.Message}"); - } - } - - /// - /// 使用Ping类重新刷新ARP缓存 - /// - /// 目标IP地址 - private async void RefreshArpWithPing(string ipAddr) - { - try - { - using var ping = new Ping(); - var options = new PingOptions - { - DontFragment = true, - Ttl = 32, - }; - - // 创建32字节的数据包 - byte[] buffer = new byte[32]; - for (int i = 0; i < buffer.Length; i++) - { - buffer[i] = (byte)(i % 256); - } - - // 异步发送ping,100ms超时 - var reply = await ping.SendPingAsync(ipAddr, 100, buffer, options); - - if (reply.Status == IPStatus.Success) - { - logger.Debug($"ARP cache refreshed successfully for {ipAddr} (RTT: {reply.RoundtripTime}ms)"); - } - else - { - logger.Warn($"Ping to {ipAddr} failed with status: {reply.Status}, but ARP entry should still be updated"); - } - } - catch (Exception ex) - { - logger.Error($"Error refreshing ARP with ping for {ipAddr}: {ex.Message}"); - } - } - - /// - /// 执行ARP刷新流程:先删除ARP条目,再用ping重新刷新 - /// - /// 目标IP地址 - private void ExecuteArpFlush(string ipAddr) - { - try - { - // 第一步:删除ARP条目 - bool deleteSuccess = DeleteArpEntry(ipAddr); - - if (deleteSuccess) - { - logger.Debug($"ARP entry deleted successfully for {ipAddr}"); - - // 第二步:使用Ping类重新刷新ARP缓存 - RefreshArpWithPing(ipAddr); - } - else - { - logger.Warn($"Failed to delete ARP entry for {ipAddr}, but continuing with ping refresh"); - // 即使删除失败,也尝试ping刷新 - RefreshArpWithPing(ipAddr); - } - } - catch (Exception ex) - { - logger.Error($"Failed to execute ARP flush for {ipAddr}: {ex.Message}"); - } - } - - /// - /// 删除ARP条目 - /// - /// 目标IP地址 - /// 是否成功删除 - private bool DeleteArpEntry(string ipAddr) - { - try - { - string command; - string arguments; - - if (OperatingSystem.IsWindows()) - { - // Windows: arp -d - command = "arp"; - arguments = $"-d {ipAddr}"; - } - else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) - { - // Linux/macOS: 优先使用 ip 命令删除 - if (IsCommandAvailable("ip")) - { - command = "ip"; - arguments = $"neigh del {ipAddr}"; - } - else if (IsCommandAvailable("arp")) - { - command = "arp"; - arguments = $"-d {ipAddr}"; - } - else - { - logger.Warn("Neither 'ip' nor 'arp' command is available for ARP entry deletion"); - return false; - } - } - else - { - logger.Warn($"Unsupported operating system for ARP entry deletion"); - return false; - } - - return ExecuteCommand(command, arguments, $"delete ARP entry for {ipAddr}"); - } - catch (Exception ex) - { - logger.Error($"Error deleting ARP entry for {ipAddr}: {ex.Message}"); - return false; - } - } - - /// - /// 检查系统命令是否可用 - /// - /// 命令名称 - /// 命令是否可用 - private bool IsCommandAvailable(string command) - { - try - { - var process = new System.Diagnostics.Process - { - StartInfo = new System.Diagnostics.ProcessStartInfo - { - FileName = OperatingSystem.IsWindows() ? "where" : "which", - Arguments = command, - UseShellExecute = false, - RedirectStandardOutput = true, - CreateNoWindow = true - } - }; - - process.Start(); - process.WaitForExit(1000); // 1秒超时 - return process.ExitCode == 0; - } - catch - { - return false; - } - } - - /// - /// 执行系统命令 - /// - /// 命令 - /// 参数 - /// 操作描述 - /// 是否成功执行 - private bool ExecuteCommand(string command, string arguments, string operation) - { - try - { - var process = new System.Diagnostics.Process - { - StartInfo = new System.Diagnostics.ProcessStartInfo - { - FileName = command, - Arguments = arguments, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - } - }; - - process.Start(); - - // 设置超时时间,避免进程挂起 - if (process.WaitForExit(5000)) // 5秒超时 - { - var output = process.StandardOutput.ReadToEnd(); - var error = process.StandardError.ReadToEnd(); - - if (process.ExitCode == 0) - { - logger.Debug($"Command executed successfully: {operation}"); - return true; - } - else - { - logger.Warn($"Command failed: {operation}. Exit code: {process.ExitCode}, Error: {error}"); - return false; - } - } - else - { - process.Kill(); - logger.Warn($"Command timed out: {operation}"); - return false; - } - } - catch (Exception ex) - { - logger.Error($"Failed to execute command for {operation}: {ex.Message}"); - return false; - } } ///