You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

572 lines
23 KiB

4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. using Apewer.Network;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using System.Net;
  7. using System.Net.NetworkInformation;
  8. using System.Net.Sockets;
  9. using System.Runtime.InteropServices;
  10. using System.Text;
  11. namespace Apewer
  12. {
  13. /// <summary></summary>
  14. public class NetworkUtility
  15. {
  16. #region UDP
  17. /// <summary>唤醒局域网中拥有指定 MAC 地址的设备。</summary>
  18. /// <param name="mac">被唤醒设备的 MAC 地址,必须是长度为 6 的字节数组。</param>
  19. public static void WakeOnLan(byte[] mac)
  20. {
  21. if (mac.Length != 6) return;
  22. var uc = new System.Net.Sockets.UdpClient();
  23. uc.Connect(IPAddress.Broadcast, 65535);
  24. var pack = new List<byte>();
  25. // 前 6 字节为 0xFF。
  26. for (int i = 0; i < 6; i++) pack.Add(255);
  27. // 目标 MAC 地址重复 16 次。
  28. for (int i = 0; i < 16; i++)
  29. {
  30. for (int j = 0; j < 6; j++) pack.Add(mac[j]);
  31. }
  32. // 发送 102 字节数据。
  33. uc.Send(pack.ToArray(), pack.Count);
  34. uc.Close();
  35. }
  36. /// <summary>从 NTP 服务器获取 UTC 时间。</summary>
  37. /// <remarks>
  38. /// 通用 NTP 服务器:<br/>
  39. /// Worldwide: pool.ntp.org<br/>
  40. /// Asia: asia.pool.ntp.org<br/>
  41. /// China: edu.ntp.org.cn<br/>
  42. /// China: us.ntp.org.cn<br/>
  43. /// Europe: europe.pool.ntp.org<br/>
  44. /// North: America north-america.pool.ntp.org<br/>
  45. /// Oceania: oceania.pool.ntp.org<br/>
  46. /// South America: south-america.pool.ntp.org<br/>
  47. /// Windows: time.windows.com<br/>
  48. /// Windows: time.nist.gov<br/>
  49. /// </remarks>
  50. public static Class<DateTime> GetUtcFromNtp(string server = "pool.ntp.org", int port = 123, int timeout = 1000)
  51. {
  52. try
  53. {
  54. var ipa = null as IPAddress;
  55. var parseIP = IPAddress.TryParse(server, out ipa);
  56. if (!parseIP)
  57. {
  58. var addresses = Dns.GetHostEntry(server).AddressList;
  59. if (addresses.Length > 0) ipa = addresses[0];
  60. }
  61. if (ipa == null) return null;
  62. var endpoint = new IPEndPoint(ipa, port);
  63. return GetUtcFromNtp(endpoint, timeout);
  64. }
  65. catch { }
  66. return null;
  67. }
  68. /// <summary>从 NTP 服务器获取 UTC 时间。</summary>
  69. public static Class<DateTime> GetUtcFromNtp(IPEndPoint endpoint, int timeout = 1000)
  70. {
  71. try
  72. {
  73. var request = new byte[48];
  74. request[0] = 0x1B;
  75. var response = new byte[48];
  76. response[0] = 0x1B;
  77. var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  78. socket.Connect(endpoint);
  79. socket.SendTimeout = timeout;
  80. socket.ReceiveTimeout = timeout;
  81. socket.Send(request);
  82. socket.Receive(response);
  83. socket.Close();
  84. const byte replytime = 40;
  85. ulong secondspart = BitConverter.ToUInt32(response, replytime);
  86. ulong secondsfraction = BitConverter.ToUInt32(response, replytime + 4);
  87. secondspart = SwapEndian(secondspart);
  88. secondsfraction = SwapEndian(secondsfraction);
  89. ulong milliseconds = (secondspart * 1000) + ((secondsfraction * 1000) / 0x100000000UL);
  90. var utc = (new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds(milliseconds);
  91. return new Class<DateTime>(utc);
  92. }
  93. catch { }
  94. return null;
  95. }
  96. private static uint SwapEndian(ulong x)
  97. {
  98. var a = ((x & 0x000000ff) << 24);
  99. var b = ((x & 0x0000ff00) << 8);
  100. var c = ((x & 0x00ff0000) >> 8);
  101. var d = ((x & 0xff000000) >> 24);
  102. return (uint)(a + b + c + d);
  103. }
  104. #endregion
  105. #region Puny Code
  106. /// <summary></summary>
  107. public static string ToPunyCode(string chinese)
  108. {
  109. if (string.IsNullOrEmpty(chinese)) return "";
  110. try { return new IdnMapping().GetAscii(chinese); }
  111. catch { return chinese; }
  112. }
  113. /// <summary></summary>
  114. public static string FromPunyCode(string punycode)
  115. {
  116. if (string.IsNullOrEmpty(punycode)) return "";
  117. try { return new IdnMapping().GetUnicode(punycode); }
  118. catch { return punycode; }
  119. }
  120. #endregion
  121. #region IP
  122. /// <summary>获取本地计算机的计算机名。</summary>
  123. public static string LocalHost
  124. {
  125. get => Dns.GetHostName() ?? "";
  126. }
  127. /// <summary>本地计算机的所有 IP 地址。</summary>
  128. public static IPAddress[] LocalIP
  129. {
  130. get => Dns.GetHostEntry(Dns.GetHostName()).AddressList;
  131. }
  132. /// <summary>判断 IPv4 地址格式是否正确。</summary>
  133. public static bool IsIP(string ipv4)
  134. {
  135. try
  136. {
  137. if (string.IsNullOrEmpty(ipv4)) return false;
  138. var split = ipv4.Split('.');
  139. if (split.Length != 4) return false;
  140. for (int i = 0; i < 4; i++)
  141. {
  142. var n = Convert.ToInt32(split[i]);
  143. if (n < 0 || n > 255) return false;
  144. if (n.ToString() != split[i]) return false;
  145. }
  146. return true;
  147. }
  148. catch { }
  149. return false;
  150. }
  151. /// <summary>对目标地址进行解析。</summary>
  152. public static string Resolve(string host)
  153. {
  154. try
  155. {
  156. if (IsIP(host))
  157. {
  158. var ip = IPAddress.Parse(host);
  159. var he = Dns.GetHostEntry(ip);
  160. return he.HostName;
  161. }
  162. else
  163. {
  164. var ip = "";
  165. var dn = host.ToLower();
  166. var he = Dns.GetHostEntry(dn);
  167. var ts = "";
  168. var on = he.Aliases;
  169. he = Dns.GetHostEntry(dn);
  170. for (int i = 0; i < on.Length; i++) ts = ts + on[i].ToString() + ",";
  171. ts = "";
  172. var al = he.AddressList;
  173. for (int i = 0; i < al.Length; i++) ts = ts + al[i].ToString() + ",";
  174. ip = ts;
  175. if (ip.Length > 0)
  176. {
  177. if (ip.Substring(ip.Length - 1, 1) == ",") ip = ip.Substring(0, ip.Length - 1);
  178. }
  179. return ip;
  180. }
  181. }
  182. catch { }
  183. return "";
  184. }
  185. /// <summary>将由字符串表示的 IPv4 地址转换为 32 位整数。</summary>
  186. public static int GetNumber(IPAddress ipv4)
  187. {
  188. try
  189. {
  190. var ba = ipv4.GetAddressBytes();
  191. return BitConverter.ToInt32(ba, 0);
  192. }
  193. catch { return 0; }
  194. }
  195. /// <summary>转换 IP 地址格式。</summary>
  196. public static string GetPlainAddress(IPAddress address)
  197. {
  198. try { return address.ToString(); }
  199. catch { return ""; }
  200. }
  201. /// <summary>转换 IP 地址格式。</summary>
  202. public static string GetPlainAddress(IPEndPoint endpoint)
  203. {
  204. try { return GetPlainAddress(endpoint.Address); }
  205. catch { return ""; }
  206. }
  207. /// <summary>转换 IP 地址格式。</summary>
  208. public static IPAddress GetIPAddress(string address)
  209. {
  210. try { return IPAddress.Parse(address); }
  211. catch { return new IPAddress(0); }
  212. }
  213. /// <summary>转换 IP 地址格式。</summary>
  214. public static IPEndPoint GetIPEndPoint(string address, int port)
  215. {
  216. try { return new IPEndPoint(IPAddress.Parse(address), NumberUtility.Restrict(port, 0, ushort.MaxValue)); }
  217. catch { return new IPEndPoint(0, 0); }
  218. }
  219. /// <summary>转换 IP 地址格式。</summary>
  220. public static IPEndPoint GetIPEndPoint(IPAddress address, int port)
  221. {
  222. try { return new IPEndPoint(address, NumberUtility.Restrict(port, 0, ushort.MaxValue)); }
  223. catch { return new IPEndPoint(0, 0); }
  224. }
  225. /// <summary>转换 IP 地址格式。</summary>
  226. public static IPEndPoint GetIPEndPoint(System.Net.EndPoint endpoint)
  227. {
  228. try { return (IPEndPoint)endpoint; }
  229. catch { return new IPEndPoint(0, 0); }
  230. }
  231. /// <summary>判断私有 IP 地址。</summary>
  232. public static bool FromLAN(string ipv4)
  233. {
  234. if (ipv4.IsEmpty()) return false;
  235. // localhost
  236. if (ipv4 == "::1") return true;
  237. if (ipv4 == "127.0.0.1") return true;
  238. // IPv4
  239. var a = ipv4.Split('.');
  240. if (a.Length != 4) return false;
  241. switch (a[0])
  242. {
  243. case "10":
  244. return true;
  245. case "172":
  246. var a1 = NumberUtility.Int32(a[1]);
  247. if (a1 >= 16 && a1 <= 31) return true;
  248. break;
  249. case "192":
  250. if (a[1] == "168") return true;
  251. break;
  252. }
  253. return false;
  254. }
  255. #endregion
  256. #region ICMP
  257. /// <summary>发送 PING 命令,命令中包含 32 位零数据。</summary>
  258. /// <param name="address">目标地址。</param>
  259. /// <param name="timeout">等待响应的超时时间(毫秒)。</param>
  260. /// <param name="ttl">命令的起始 TTL 值(在丢弃数据之前可以转发该数据的路由节点数)。</param>
  261. /// <param name="df">是否分段。</param>
  262. /// <returns>命令的返回结果。</returns>
  263. /// <exception cref="ArgumentNullException"></exception>
  264. /// <exception cref="ArgumentOutOfRangeException"></exception>
  265. /// <exception cref="PingException"></exception>
  266. public static PingReply Ping(string address, int timeout = 1000, byte ttl = 255, bool df = true)
  267. {
  268. if (string.IsNullOrEmpty(address)) throw new ArgumentNullException(nameof(address));
  269. if (timeout < 1) throw new ArgumentOutOfRangeException(nameof(timeout));
  270. var ip = IsIP(address) ? address : Resolve(address);
  271. if (ip.Contains(",")) ip = ip.Split(',')[0];
  272. var options = new PingOptions();
  273. options.DontFragment = df;
  274. options.Ttl = ttl;
  275. var buffer = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
  276. var ping = new Ping();
  277. var reply = ping.Send(ip, timeout, buffer, options);
  278. return reply;
  279. }
  280. #endregion
  281. #region HTTP
  282. /// <summary>解析 HTTP 方法。</summary>
  283. public static HttpMethod ParseHttpMethod(string method)
  284. {
  285. if (!string.IsNullOrEmpty(method))
  286. {
  287. var upper = TextUtility.Upper(method);
  288. if (upper.Contains("OPTIONS")) return HttpMethod.OPTIONS;
  289. else if (upper.Contains("POST")) return HttpMethod.POST;
  290. else if (upper.Contains("GET")) return HttpMethod.GET;
  291. else if (upper.Contains("CONNECT")) return HttpMethod.CONNECT;
  292. else if (upper.Contains("DELETE")) return HttpMethod.DELETE;
  293. else if (upper.Contains("HEAD")) return HttpMethod.HEAD;
  294. else if (upper.Contains("PATCH")) return HttpMethod.PATCH;
  295. else if (upper.Contains("PUT")) return HttpMethod.PUT;
  296. else if (upper.Contains("TRACE")) return HttpMethod.TRACE;
  297. }
  298. return HttpMethod.NULL;
  299. }
  300. /// <summary>GET</summary>
  301. public static HttpClient HttpGet(string url, int timeout = 30000) => HttpClient.Get(url, timeout);
  302. /// <summary>POST</summary>
  303. public static HttpClient HttpPost(string url, byte[] data, int timeout = 30000, string type = "application/octet-stream") => HttpClient.Post(url, data, timeout, type);
  304. /// <summary>POST text/plain</summary>
  305. public static HttpClient HttpPost(string url, string text, int timeout = 30000, string type = "text/plain") => HttpClient.Text(url, text, timeout, type);
  306. /// <summary>POST application/x-www-form-urlencoded</summary>
  307. public static HttpClient HttpPost(string url, IDictionary<string, string> form, int timeout = 30000) => HttpClient.Form(url, form, timeout);
  308. /// <summary>POST application/x-www-form-urlencoded</summary>
  309. public static HttpClient HttpPost(string url, Dictionary<string, string> form, int timeout = 30000) => HttpClient.Form(url, form, timeout);
  310. /// <summary>获取 HTTP 状态的文本。</summary>
  311. public static string HttpStatusDescription(int code)
  312. {
  313. switch (code)
  314. {
  315. case 100: return "Continue";
  316. case 101: return "Switching Protocols";
  317. case 102: return "Processing";
  318. case 200: return "OK";
  319. case 201: return "Created";
  320. case 202: return "Accepted";
  321. case 203: return "Non-Authoritative Information";
  322. case 204: return "No Content";
  323. case 205: return "Reset Content";
  324. case 206: return "Partial Content";
  325. case 207: return "Multi-Status";
  326. case 300: return "Multiple Choices";
  327. case 301: return "Moved Permanently";
  328. case 302: return "Found";
  329. case 303: return "See Other";
  330. case 304: return "Not Modified";
  331. case 305: return "Use Proxy";
  332. case 307: return "Temporary Redirect";
  333. case 400: return "Bad Request";
  334. case 401: return "Unauthorized";
  335. case 402: return "Payment Required";
  336. case 403: return "Forbidden";
  337. case 404: return "Not Found";
  338. case 405: return "Method Not Allowed";
  339. case 406: return "Not Acceptable";
  340. case 407: return "Proxy Authentication Required";
  341. case 408: return "Request Timeout";
  342. case 409: return "Conflict";
  343. case 410: return "Gone";
  344. case 411: return "Length Required";
  345. case 412: return "Precondition Failed";
  346. case 413: return "Request Entity Too Large";
  347. case 414: return "Request-Uri Too Long";
  348. case 415: return "Unsupported Media Type";
  349. case 416: return "Requested Range Not Satisfiable";
  350. case 417: return "Expectation Failed";
  351. case 422: return "Unprocessable Entity";
  352. case 423: return "Locked";
  353. case 424: return "Failed Dependency";
  354. case 426: return "Upgrade Required"; // RFC 2817
  355. case 500: return "Internal Server Error";
  356. case 501: return "Not Implemented";
  357. case 502: return "Bad Gateway";
  358. case 503: return "Service Unavailable";
  359. case 504: return "Gateway Timeout";
  360. case 505: return "Http Version Not Supported";
  361. case 507: return "Insufficient Storage";
  362. default: return null;
  363. }
  364. }
  365. /// <summary>按文件扩展名获取 Content-Type 值。</summary>
  366. public static string Mime(string extension)
  367. {
  368. const string Default = "application/octet-stream";
  369. if (string.IsNullOrEmpty(extension)) return Default;
  370. var split = extension.Split('.');
  371. var lower = split.Length < 1 ? null : split[split.Length - 1].Lower();
  372. switch (lower)
  373. {
  374. // text/plain; charset=utf-8
  375. case "css": return "text/css";
  376. case "htm": return "text/html";
  377. case "html": return "text/html";
  378. case "ini": return "text/ini";
  379. case "js": return "application/javascript";
  380. case "json": return "text/json";
  381. case "shtml": return "text/html";
  382. case "sh": return "text/plain";
  383. case "txt": return "text/plain";
  384. }
  385. switch (lower)
  386. {
  387. case "jad": return "text/vnd.sun.j2me.app-descriptor";
  388. case "m3u8": return "text/vnd.apple.mpegurl"; // application/vnd.apple.mpegurl
  389. case "xml": return "text/xml";
  390. case "htc": return "text/x-component";
  391. case "mml": return "text/mathml";
  392. case "wml": return "text/vnd.wap.wml";
  393. }
  394. switch (lower)
  395. {
  396. case "3gp": return "video/3gpp";
  397. case "3gpp": return "video/3gpp";
  398. case "7z": return "application/x-7z-compressed";
  399. case "ai": return "application/postscript";
  400. case "asf": return "video/x-ms-asf";
  401. case "asx": return "video/x-ms-asf";
  402. case "atom": return "application/atom+xml";
  403. case "avi": return "video/x-msvideo";
  404. case "bmp": return "image/x-ms-bmp";
  405. case "cco": return "application/x-cocoa";
  406. case "crt": return "application/x-x509-ca-cert";
  407. case "der": return "application/x-x509-ca-cert";
  408. case "doc": return "application/msword";
  409. case "docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
  410. case "ear": return "application/java-archive";
  411. case "eot": return "application/vnd.ms-fontobject";
  412. case "eps": return "application/postscript";
  413. case "flv": return "video/x-flv";
  414. case "gif": return "image/gif";
  415. case "hqx": return "application/mac-binhex40";
  416. case "ico": return "image/x-icon";
  417. case "jar": return "application/java-archive";
  418. case "jardiff": return "application/x-java-archive-diff";
  419. case "jng": return "image/x-jng";
  420. case "jnlp": return "application/x-java-jnlp-file";
  421. case "jpeg": return "image/jpeg";
  422. case "jpg": return "image/jpeg";
  423. case "kar": return "audio/midi";
  424. case "kml": return "application/vnd.google-earth.kml+xml";
  425. case "kmz": return "application/vnd.google-earth.kmz";
  426. case "m4a": return "audio/x-m4a";
  427. case "m4v": return "video/x-m4v";
  428. case "mid": return "audio/midi";
  429. case "midi": return "audio/midi";
  430. case "mkv": return "video/x-matroska";
  431. case "mng": return "video/x-mng";
  432. case "mov": return "video/quicktime";
  433. case "mp3": return "audio/mpeg";
  434. case "mp4": return "video/mp4";
  435. case "mpeg": return "video/mpeg";
  436. case "mpg": return "video/mpeg";
  437. case "odg": return "application/vnd.oasis.opendocument.graphics";
  438. case "odp": return "application/vnd.oasis.opendocument.presentation";
  439. case "ods": return "application/vnd.oasis.opendocument.spreadsheet";
  440. case "odt": return "application/vnd.oasis.opendocument.text";
  441. case "ogg": return "audio/ogg";
  442. case "pdb": return "application/x-pilot";
  443. case "pdf": return "application/pdf";
  444. case "pem": return "application/x-x509-ca-cert";
  445. case "pl": return "application/x-perl";
  446. case "pm": return "application/x-perl";
  447. case "png": return "image/png";
  448. case "ppt": return "application/vnd.ms-powerpoint";
  449. case "pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
  450. case "prc": return "application/x-pilot";
  451. case "ps": return "application/postscript";
  452. case "ra": return "audio/x-realaudio";
  453. case "rar": return "application/x-rar-compressed";
  454. case "rpm": return "application/x-redhat-package-manager";
  455. case "rss": return "application/rss+xml";
  456. case "rtf": return "application/rtf";
  457. case "run": return "application/x-makeself";
  458. case "sea": return "application/x-sea";
  459. case "sit": return "application/x-stuffit";
  460. case "svg": return "image/svg+xml";
  461. case "svgz": return "image/svg+xml";
  462. case "swf": return "application/x-shockwave-flash";
  463. case "tcl": return "application/x-tcl";
  464. case "tif": return "image/tiff";
  465. case "tiff": return "image/tiff";
  466. case "tk": return "application/x-tcl";
  467. case "ts": return "video/mp2t";
  468. case "war": return "application/java-archive";
  469. case "wbmp": return "image/vnd.wap.wbmp";
  470. case "webm": return "video/webm";
  471. case "webp": return "image/webp";
  472. case "wmlc": return "application/vnd.wap.wmlc";
  473. case "wmv": return "video/x-ms-wmv";
  474. case "woff": return "font/woff";
  475. case "woff2": return "font/woff2";
  476. case "xhtml": return "application/xhtml+xml";
  477. case "xls": return "application/vnd.ms-excel";
  478. case "xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
  479. case "xpi": return "application/x-xpinstall";
  480. case "xspf": return "application/xspf+xml";
  481. case "zip": return "application/zip";
  482. }
  483. return Default;
  484. }
  485. #endregion
  486. #region Port
  487. private static int[] ActivePorts(IPEndPoint[] endpoints)
  488. {
  489. var list = new List<int>(endpoints.Length);
  490. foreach (var endpoint in endpoints)
  491. {
  492. var port = endpoint.Port;
  493. if (list.Contains(port)) continue;
  494. list.Add(port);
  495. }
  496. list.Sort();
  497. list.Capacity = list.Count;
  498. return list.ToArray();
  499. }
  500. /// <summary>列出活动的 TCP 端口。</summary>
  501. public static int[] ActiveTcpPorts() => ActivePorts(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
  502. /// <summary>列出活动的 UDP 端口。</summary>
  503. public static int[] ActiveUdpPorts() => ActivePorts(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
  504. #endregion
  505. }
  506. }