From 12cd35edff8822bd41fa28e9d6b3d2f632d3f840 Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Fri, 18 Jul 2025 12:28:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0arp=E6=94=AF=E6=8C=81?= =?UTF-8?q?=EF=BC=8C=E4=BB=85=E6=94=AF=E6=8C=81=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .justfile | 10 ++-- components.d.ts | 23 --------- flake.nix | 1 + server/src/ArpClient.cs | 111 +++++++++++++++++++++++++++++++++------- server/src/MsgBus.cs | 9 +++- server/src/UdpServer.cs | 2 +- 6 files changed, 109 insertions(+), 47 deletions(-) diff --git a/.justfile b/.justfile index cda2d8d..55ae22c 100644 --- a/.justfile +++ b/.justfile @@ -15,11 +15,15 @@ clean: rm -rf "dist" rm -rf "wwwroot" -update: - npm install - dotnet restore ./server/server.csproj +update: update-node update-dotnet git submodule update --init --remote --recursive +update-node: + npm install + +update-dotnet: + dotnet restore ./server/server.csproj + # 生成Restful API到网页客户端 gen-api: npm run gen-api diff --git a/components.d.ts b/components.d.ts index dca5943..c80ebed 100644 --- a/components.d.ts +++ b/components.d.ts @@ -9,11 +9,8 @@ export {} declare module 'vue' { export interface GlobalComponents { Alert: typeof import('./src/components/Alert/Alert.vue')['default'] - AlertDemo: typeof import('./src/components/AlertDemo.vue')['default'] BaseBoard: typeof import('./src/components/equipments/BaseBoard.vue')['default'] BaseInputField: typeof import('./src/components/InputField/BaseInputField.vue')['default'] - Canvas: typeof import('./src/components/Canvas.vue')['default'] - ChannelConfig: typeof import('./src/components/LogicAnalyzer/ChannelConfig.vue')['default'] CollapsibleSection: typeof import('./src/components/CollapsibleSection.vue')['default'] ComponentSelector: typeof import('./src/components/LabCanvas/ComponentSelector.vue')['default'] DDR: typeof import('./src/components/equipments/DDR.vue')['default'] @@ -22,16 +19,10 @@ declare module 'vue' { DiagramCanvas: typeof import('./src/components/LabCanvas/DiagramCanvas.vue')['default'] Dialog: typeof import('./src/components/Dialog.vue')['default'] ETH: typeof import('./src/components/equipments/ETH.vue')['default'] - FunctionBar: typeof import('./src/components/FunctionBar.vue')['default'] HDMI: typeof import('./src/components/equipments/HDMI.vue')['default'] IpInputField: typeof import('./src/components/InputField/IpInputField.vue')['default'] ItemList: typeof import('./src/components/LabCanvas/ItemList.vue')['default'] - LabCanvas: typeof import('./src/components/LabCanvasNew/LabCanvas.vue')['default'] - LabCanvasNew: typeof import('./src/components/LabCanvas/LabCanvasNew.vue')['default'] - LabComponentsDrawer: typeof import('./src/components/LabCanvasNew/LabComponentsDrawer.vue')['default'] - LabComponentsDrawerNew: typeof import('./src/components/LabCanvas/LabComponentsDrawerNew.vue')['default'] LogicalWaveFormDisplay: typeof import('./src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue')['default'] - LoginCard: typeof import('./src/components/LoginCard.vue')['default'] MarkdownRenderer: typeof import('./src/components/MarkdownRenderer.vue')['default'] MechanicalButton: typeof import('./src/components/equipments/MechanicalButton.vue')['default'] MotherBoard: typeof import('./src/components/equipments/MotherBoard.vue')['default'] @@ -43,29 +34,15 @@ declare module 'vue' { PortInputField: typeof import('./src/components/InputField/PortInputField.vue')['default'] PropertyEditor: typeof import('./src/components/PropertyEditor.vue')['default'] PropertyPanel: typeof import('./src/components/PropertyPanel.vue')['default'] - RekaSplitterGroup: typeof import('reka-ui')['SplitterGroup'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] - ScrollAreaCorner: typeof import('reka-ui')['ScrollAreaCorner'] - ScrollAreaRoot: typeof import('reka-ui')['ScrollAreaRoot'] - ScrollAreaScrollbar: typeof import('reka-ui')['ScrollAreaScrollbar'] - ScrollAreaThumb: typeof import('reka-ui')['ScrollAreaThumb'] - ScrollAreaViewport: typeof import('reka-ui')['ScrollAreaViewport'] SD: typeof import('./src/components/equipments/SD.vue')['default'] SevenSegmentDisplay: typeof import('./src/components/equipments/SevenSegmentDisplay.vue')['default'] SFP: typeof import('./src/components/equipments/SFP.vue')['default'] Sidebar: typeof import('./src/components/Sidebar.vue')['default'] SMA: typeof import('./src/components/equipments/SMA.vue')['default'] SMT_LED: typeof import('./src/components/equipments/SMT_LED.vue')['default'] - SplitterGroup: typeof import('reka-ui')['SplitterGroup'] - SplitterPanel: typeof import('reka-ui')['SplitterPanel'] - SplitterResizeHandle: typeof import('reka-ui')['SplitterResizeHandle'] Switch: typeof import('./src/components/equipments/Switch.vue')['default'] - TabsContent: typeof import('reka-ui')['TabsContent'] - TabsIndicator: typeof import('reka-ui')['TabsIndicator'] - TabsList: typeof import('reka-ui')['TabsList'] - TabsRoot: typeof import('reka-ui')['TabsRoot'] - TabsTrigger: typeof import('reka-ui')['TabsTrigger'] ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default'] ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default'] TriggerSettings: typeof import('./src/components/LogicAnalyzer/TriggerSettings.vue')['default'] diff --git a/flake.nix b/flake.nix index b099c89..7f9b44b 100644 --- a/flake.nix +++ b/flake.nix @@ -23,6 +23,7 @@ sqls sql-studio zlib + bash # Backend (dotnetCorePackages.combinePackages [ dotnetCorePackages.sdk_9_0 diff --git a/server/src/ArpClient.cs b/server/src/ArpClient.cs index 6a806df..6398670 100644 --- a/server/src/ArpClient.cs +++ b/server/src/ArpClient.cs @@ -7,6 +7,8 @@ using System.Text.RegularExpressions; /// public static class Arp { + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + /// /// 读取所有 ARP 记录 /// @@ -41,6 +43,28 @@ public static class Arp return entries; } + /// + /// 通过 Ping 动态更新指定 IP 的 ARP 记录 + /// + /// 要更新的 IP 地址 + /// 是否成功发送 Ping + public static async Task UpdateArpEntryByPingAsync(string ipAddress) + { + if (string.IsNullOrWhiteSpace(ipAddress)) + throw new ArgumentException("IP 地址不能为空", nameof(ipAddress)); + + try + { + using var ping = new System.Net.NetworkInformation.Ping(); + var reply = await ping.SendPingAsync(ipAddress, 100); + return reply.Status == System.Net.NetworkInformation.IPStatus.Success; + } + catch + { + return false; + } + } + /// /// 添加 ARP 记录 /// @@ -48,7 +72,7 @@ public static class Arp /// MAC 地址 /// 网络接口名称(可选) /// 是否成功 - public static async Task AddArpEntryAsync(string ipAddress, string macAddress, string interfaceName = null) + public static async Task AddArpEntryAsync(string ipAddress, string macAddress, string? interfaceName = null) { if (string.IsNullOrWhiteSpace(ipAddress)) throw new ArgumentException("IP 地址不能为空", nameof(ipAddress)); @@ -74,7 +98,7 @@ public static class Arp /// 要删除的 IP 地址 /// 网络接口名称(可选) /// 是否成功 - public static async Task DeleteArpEntryAsync(string ipAddress, string interfaceName = null) + public static async Task DeleteArpEntryAsync(string ipAddress, string? interfaceName = null) { if (string.IsNullOrWhiteSpace(ipAddress)) throw new ArgumentException("IP 地址不能为空", nameof(ipAddress)); @@ -149,15 +173,13 @@ public static class Arp /// private static string GetArpListCommand() { - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? "arp -a" - : "arp -a"; + return "arp -a"; } /// /// 获取 ARP 添加命令 /// - private static string GetArpAddCommand(string ipAddress, string macAddress, string interfaceName) + private static string GetArpAddCommand(string ipAddress, string macAddress, string? interfaceName) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -186,7 +208,7 @@ public static class Arp /// /// 获取 ARP 删除命令 /// - private static string GetArpDeleteCommand(string ipAddress, string interfaceName) + private static string GetArpDeleteCommand(string ipAddress, string? interfaceName) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -238,14 +260,7 @@ public static class Arp /// private static string GetArpQueryCommand(string ipAddress) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return $"arp -a {ipAddress}"; - } - else - { - return $"arp -n {ipAddress}"; - } + return $"arp -a {ipAddress}"; } /// @@ -284,6 +299,8 @@ public static class Arp }; } + logger.Debug($"Executing command: {processInfo.FileName} {processInfo.Arguments}"); + using var process = new Process { StartInfo = processInfo }; process.Start(); @@ -292,6 +309,11 @@ public static class Arp 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, @@ -302,6 +324,7 @@ public static class Arp } catch (Exception ex) { + logger.Error(ex, $"Command execution failed: {command}"); return new CommandResult { IsSuccess = false, @@ -358,8 +381,8 @@ public static class 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+)"; + // 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) @@ -369,8 +392,8 @@ public static class Arp 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 + Type = match.Groups[5].Value, + Interface = match.Groups[6].Value }; } @@ -390,6 +413,26 @@ public static class Arp return null; } + + /// + /// 判断当前进程是否具有管理员(或 root)权限 + /// + /// 如果有管理员权限返回 true,否则返回 false + 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"); + } + } } /// @@ -397,12 +440,30 @@ public static class Arp /// public class ArpEntry { + /// + /// [TODO:description] + /// public string Hostname { get; set; } = string.Empty; + /// + /// [TODO:description] + /// public string IpAddress { get; set; } = string.Empty; + /// + /// [TODO:description] + /// public string MacAddress { get; set; } = string.Empty; + /// + /// [TODO:description] + /// public string Type { get; set; } = string.Empty; + /// + /// [TODO:description] + /// public string Interface { get; set; } = string.Empty; + /// + /// [TODO:description] + /// public override string ToString() { return $"{IpAddress} -> {MacAddress} ({Interface})"; @@ -414,8 +475,20 @@ public class ArpEntry /// public class CommandResult { + /// + /// [TODO:description] + /// public bool IsSuccess { get; set; } + /// + /// [TODO:description] + /// public string Output { get; set; } = string.Empty; + /// + /// [TODO:description] + /// public string Error { get; set; } = string.Empty; + /// + /// [TODO:description] + /// public int ExitCode { get; set; } } diff --git a/server/src/MsgBus.cs b/server/src/MsgBus.cs index 573b074..c5dff1a 100644 --- a/server/src/MsgBus.cs +++ b/server/src/MsgBus.cs @@ -3,6 +3,8 @@ /// public static class MsgBus { + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + private static readonly UDPServer udpServer = new UDPServer(1234, 12); /// /// 获取UDP服务器 @@ -19,8 +21,13 @@ public static class MsgBus /// 通信总线初始化 /// /// - public static void Init() + public async static void Init() { + if (!Arp.IsAdministrator()) + { + logger.Error($"非管理员运行,ARP无法更新,请用管理员权限运行"); + // throw new Exception($"非管理员运行,ARP无法更新,请用管理员权限运行"); + } udpServer.Start(); isRunning = true; } diff --git a/server/src/UdpServer.cs b/server/src/UdpServer.cs index 70a9cc6..7459404 100644 --- a/server/src/UdpServer.cs +++ b/server/src/UdpServer.cs @@ -128,7 +128,7 @@ public class UDPServer if (IsPortInUse(currentPort)) { throw new ArgumentException( - $"Port {currentPort} is already in use.", + $"端口{currentPort}已被占用,无法启动UDP Server", nameof(port) ); }