FeederIpScanner.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Net;
  5. using System.Net.NetworkInformation;
  6. using System.Net.Sockets;
  7. using System.Runtime.CompilerServices;
  8. using System.Text;
  9. using System.Text.RegularExpressions;
  10. using System.Threading.Tasks;
  11. namespace TeamAAS.Feeder.Network
  12. {
  13. /// <summary>
  14. /// 网卡接口信息(IPv4)。
  15. /// </summary>
  16. public class NetworkInterfaceInfo
  17. {
  18. public string Name { get; set; }
  19. public IPAddress IPv4Address { get; set; }
  20. public IPAddress IPv4Mask { get; set; }
  21. }
  22. /// <summary>
  23. /// UDP 扫描发现的 Feeder 网络信息。
  24. /// </summary>
  25. public class FeederNetworkInfo : INotifyPropertyChanged
  26. {
  27. public string FeederType { get; set; }
  28. public string Version { get; set; }
  29. public string IP { get; set; }
  30. public int Port { get; set; }
  31. public string Subnet { get; set; }
  32. public string Gateway { get; set; }
  33. public string MAC { get; set; }
  34. private bool _isAccessible;
  35. /// <summary>是否与本机某网卡处于同一网段(前三段相同)。</summary>
  36. public bool IsAccessible
  37. {
  38. get { return _isAccessible; }
  39. set { SetProperty(ref _isAccessible, value); }
  40. }
  41. /// <summary>发现该 Feeder 所使用的本机网卡。</summary>
  42. public NetworkInterfaceInfo Network { get; set; }
  43. public override string ToString() => $"{FeederType} {IP}:{Port} ({MAC})";
  44. public event PropertyChangedEventHandler PropertyChanged;
  45. private void SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
  46. {
  47. if (EqualityComparer<T>.Default.Equals(storage, value)) return;
  48. storage = value;
  49. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  50. }
  51. }
  52. /// <summary>
  53. /// Feeder IP 自动搜索/设置(UDP 广播扫描)。
  54. /// 纯 System.Net.Sockets.UdpClient 实现,无第三方依赖。
  55. /// 协议:向 255.255.255.255:1234 广播 {udpscan},Feeder 回应
  56. /// { FF0Ver2.6.5 / ip:192.168.31.200:5000 / sub:255.255.255.0 / gw:... / mac:...}。
  57. /// </summary>
  58. public class FeederIpScanner
  59. {
  60. private const int BroadcastPort = 1234;
  61. private const int LocalBindPort = 8000;
  62. private const string LogSource = "Feeder.Scan";
  63. private UdpClient _udpClient = new UdpClient();
  64. /// <summary>扫描所有网卡,返回发现的 Feeder 列表。</summary>
  65. public async Task<List<FeederNetworkInfo>> ScanAsync()
  66. {
  67. return await Task.Run(() =>
  68. {
  69. var result = new List<FeederNetworkInfo>();
  70. try
  71. {
  72. var netList = GetAllNetworkInterface();
  73. foreach (var nic in netList)
  74. {
  75. Bind(nic.IPv4Address.ToString());
  76. var feeders = GetFeederList(nic);
  77. result.AddRange(feeders);
  78. }
  79. AppLogger.Info($"UDP 扫描完成,发现 {result.Count} 个 Feeder", LogSource);
  80. }
  81. catch (Exception ex)
  82. {
  83. AppLogger.Error("UDP 扫描 Feeder 失败", ex, LogSource);
  84. }
  85. return result;
  86. });
  87. }
  88. /// <summary>绑定到指定本机网卡 IP 与固定本地端口。</summary>
  89. public void Bind(string ip)
  90. {
  91. var localIpAddress = IPAddress.Parse(ip);
  92. try { _udpClient.Client.Close(); } catch { }
  93. try { _udpClient.Client.Dispose(); } catch { }
  94. _udpClient = new UdpClient();
  95. _udpClient.Client.Bind(new IPEndPoint(localIpAddress, LocalBindPort));
  96. }
  97. /// <summary>获取所有已启用、非回环/隧道网口的 IPv4 信息。</summary>
  98. public NetworkInterfaceInfo[] GetAllNetworkInterface()
  99. {
  100. var networks = new List<NetworkInterfaceInfo>();
  101. foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
  102. {
  103. if (ni.OperationalStatus != OperationalStatus.Up ||
  104. ni.NetworkInterfaceType == NetworkInterfaceType.Loopback ||
  105. ni.NetworkInterfaceType == NetworkInterfaceType.Tunnel ||
  106. ni.NetworkInterfaceType == NetworkInterfaceType.Unknown)
  107. continue;
  108. foreach (var ua in ni.GetIPProperties().UnicastAddresses)
  109. {
  110. if (ua.Address.AddressFamily == AddressFamily.InterNetwork)
  111. {
  112. networks.Add(new NetworkInterfaceInfo
  113. {
  114. Name = ni.Name,
  115. IPv4Address = ua.Address,
  116. IPv4Mask = ua.IPv4Mask
  117. });
  118. }
  119. }
  120. }
  121. return networks.ToArray();
  122. }
  123. /// <summary>在指定网卡上广播扫描并收集回应。</summary>
  124. public FeederNetworkInfo[] GetFeederList(NetworkInterfaceInfo network)
  125. {
  126. var feeders = new List<FeederNetworkInfo>();
  127. var ep = new IPEndPoint(IPAddress.Broadcast, BroadcastPort);
  128. var data = Encoding.UTF8.GetBytes("{udpscan}");
  129. try
  130. {
  131. _udpClient.Send(data, data.Length, ep);
  132. _udpClient.Client.ReceiveTimeout = 500;
  133. for (int i = 0; i < 10; i++)
  134. {
  135. try
  136. {
  137. var da = _udpClient.Receive(ref ep);
  138. string config = Encoding.UTF8.GetString(da, 0, da.Length).TrimEnd('\n').TrimEnd('\r');
  139. var info = AnalysisIP(config);
  140. if (info != null)
  141. {
  142. info.IsAccessible = AreFirstThreeOctetsEqual(IPAddress.Parse(info.IP), network.IPv4Address);
  143. info.Network = network;
  144. feeders.Add(info);
  145. }
  146. ep = new IPEndPoint(IPAddress.Broadcast, BroadcastPort);
  147. }
  148. catch (Exception)
  149. {
  150. break;
  151. }
  152. }
  153. }
  154. catch (Exception ex)
  155. {
  156. AppLogger.Warning($"网卡 {network.Name} 扫描异常: {ex.Message}", LogSource);
  157. }
  158. return feeders.ToArray();
  159. }
  160. /// <summary>解析 Feeder 回应串为网络信息;不匹配返回 null。</summary>
  161. public FeederNetworkInfo AnalysisIP(string input)
  162. {
  163. const string pattern = @"{(?<Name>[^V]+)Ver(?<Version>[^\/]+)\/ip:(?<IP>[^:]+):(?<Port>\d+)\/sub:(?<Subnet>[^\/]+)\/gw:(?<Gateway>[^\/]+)\/mac:(?<MAC>[^}]+)}";
  164. var match = Regex.Match(input, pattern);
  165. if (!match.Success) return null;
  166. int.TryParse(match.Groups["Port"].Value.Trim(), out var port);
  167. return new FeederNetworkInfo
  168. {
  169. FeederType = match.Groups["Name"].Value.Trim(),
  170. Version = match.Groups["Version"].Value.Trim(),
  171. IP = match.Groups["IP"].Value.Trim(),
  172. Port = port,
  173. Subnet = match.Groups["Subnet"].Value.Trim(),
  174. Gateway = match.Groups["Gateway"].Value.Trim(),
  175. MAC = match.Groups["MAC"].Value.Trim()
  176. };
  177. }
  178. /// <summary>通过 MAC 重置 Feeder 参数。</summary>
  179. public bool ResetFeeder(string feederMac, NetworkInterfaceInfo network = null)
  180. {
  181. try
  182. {
  183. if (network != null) Bind(network.IPv4Address.ToString());
  184. var ep = new IPEndPoint(IPAddress.Broadcast, BroadcastPort);
  185. string cmd = "{ParReset/mac:" + feederMac + "}";
  186. var data = Encoding.UTF8.GetBytes(cmd);
  187. _udpClient.Send(data, data.Length, ep);
  188. _udpClient.Client.ReceiveTimeout = 5000;
  189. var da = _udpClient.Receive(ref ep);
  190. string rev = Encoding.UTF8.GetString(da, 0, da.Length).TrimEnd('\n').TrimEnd('\r');
  191. bool ok = string.Equals(rev, cmd);
  192. AppLogger.Info($"重置 Feeder {feederMac} {(ok ? "成功" : "失败")}", LogSource);
  193. return ok;
  194. }
  195. catch (Exception ex)
  196. {
  197. AppLogger.Error($"重置 Feeder {feederMac} 异常", ex, LogSource);
  198. return false;
  199. }
  200. }
  201. /// <summary>通过 MAC 设置 Feeder 的 IP/端口/掩码。</summary>
  202. public async Task<bool> SetFeederIpAsync(string feederMac, string targetIp, int targetPort, string targetSub, NetworkInterfaceInfo network = null)
  203. {
  204. try
  205. {
  206. if (network != null) Bind(network.IPv4Address.ToString());
  207. var ep = new IPEndPoint(IPAddress.Broadcast, BroadcastPort);
  208. string cmd = "{ipset/mac:" + $"{feederMac}/ip:{targetIp}:{targetPort}/sub:{targetSub}" + "}";
  209. var data = Encoding.UTF8.GetBytes(cmd);
  210. await _udpClient.SendAsync(data, data.Length, ep);
  211. _udpClient.Client.ReceiveTimeout = 5000;
  212. var received = await _udpClient.ReceiveAsync();
  213. string rev = Encoding.UTF8.GetString(received.Buffer, 0, received.Buffer.Length).TrimEnd('\n').TrimEnd('\r');
  214. bool ok = string.Equals(rev, cmd);
  215. AppLogger.Info($"设置 Feeder {feederMac} -> {targetIp}:{targetPort}/{targetSub} {(ok ? "成功" : "失败")}", LogSource);
  216. return ok;
  217. }
  218. catch (Exception ex)
  219. {
  220. AppLogger.Error($"设置 Feeder {feederMac} IP 异常", ex, LogSource);
  221. return false;
  222. }
  223. }
  224. /// <summary>判断两个 IP 前三段是否相同(是否同网段)。</summary>
  225. public bool AreFirstThreeOctetsEqual(IPAddress ip1, IPAddress ip2)
  226. {
  227. var b1 = ip1.GetAddressBytes();
  228. var b2 = ip2.GetAddressBytes();
  229. for (int i = 0; i < 3; i++)
  230. if (b1[i] != b2[i]) return false;
  231. return true;
  232. }
  233. public void Dispose()
  234. {
  235. try { _udpClient?.Client?.Close(); } catch { }
  236. try { _udpClient?.Client?.Dispose(); } catch { }
  237. try { _udpClient?.Dispose(); } catch { }
  238. }
  239. }
  240. }