DiscoveryService.cs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. using System.Net;
  2. using System.Net.Sockets;
  3. using System.Runtime.InteropServices;
  4. using System.Text;
  5. using System.Text.Json;
  6. using System.Text.Json.Serialization;
  7. using NetworkTool.Client.Models;
  8. namespace NetworkTool.Client.Services;
  9. public sealed class DiscoveryService
  10. {
  11. private const int DiscoveryPort = 50000;
  12. [DllImport("iphlpapi.dll", ExactSpelling = true)]
  13. private static extern int SendARP(int destinationIp, int sourceIp, byte[] macAddress, ref int physicalAddressLength);
  14. public async Task<IReadOnlyList<DiscoveredDevice>> DiscoverManyAsync(string localIPv4, CancellationToken cancellationToken = default)
  15. {
  16. if (string.IsNullOrWhiteSpace(localIPv4))
  17. {
  18. return [];
  19. }
  20. using var client = new UdpClient(AddressFamily.InterNetwork)
  21. {
  22. EnableBroadcast = true,
  23. };
  24. client.Client.Bind(new IPEndPoint(IPAddress.Parse(localIPv4), 0));
  25. var request = new
  26. {
  27. protocol_version = 1,
  28. message_type = "discover",
  29. request_id = Guid.NewGuid().ToString(),
  30. client_name = Environment.MachineName,
  31. };
  32. var payload = JsonSerializer.SerializeToUtf8Bytes(request);
  33. await client.SendAsync(payload, payload.Length, new IPEndPoint(IPAddress.Broadcast, DiscoveryPort));
  34. using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
  35. timeoutCts.CancelAfter(TimeSpan.FromSeconds(3));
  36. var devicesByIp = new Dictionary<string, DiscoveredDevice>(StringComparer.OrdinalIgnoreCase);
  37. while (!timeoutCts.IsCancellationRequested)
  38. {
  39. try
  40. {
  41. var result = await client.ReceiveAsync(timeoutCts.Token);
  42. var response = JsonSerializer.Deserialize<DiscoveryResponse>(result.Buffer);
  43. if (response is null
  44. || response.MessageType != "discover_response"
  45. || string.IsNullOrWhiteSpace(response.Lan2Ip)
  46. || !response.Lan2Ip.StartsWith("169.254.", StringComparison.Ordinal))
  47. {
  48. continue;
  49. }
  50. var mac = response.Mac;
  51. if (string.IsNullOrWhiteSpace(mac))
  52. {
  53. mac = ResolveMacAddress(result.RemoteEndPoint.Address);
  54. }
  55. devicesByIp[response.Lan2Ip] = new DiscoveredDevice
  56. {
  57. DeviceId = response.DeviceId ?? string.Empty,
  58. Hostname = response.Hostname ?? string.Empty,
  59. ServerVersion = response.ServerVersion ?? string.Empty,
  60. Mac = mac ?? string.Empty,
  61. Lan2Ip = response.Lan2Ip,
  62. HttpPort = response.HttpPort,
  63. AuthRequired = response.AuthRequired,
  64. };
  65. }
  66. catch (OperationCanceledException)
  67. {
  68. break;
  69. }
  70. }
  71. return devicesByIp.Values.OrderBy(device => device.Lan2Ip).ToList();
  72. }
  73. private static string ResolveMacAddress(IPAddress ipAddress)
  74. {
  75. if (ipAddress.AddressFamily != AddressFamily.InterNetwork)
  76. {
  77. return string.Empty;
  78. }
  79. var macAddress = new byte[6];
  80. var macAddressLength = macAddress.Length;
  81. var destinationIp = BitConverter.ToInt32(ipAddress.GetAddressBytes(), 0);
  82. if (SendARP(destinationIp, 0, macAddress, ref macAddressLength) != 0 || macAddressLength <= 0)
  83. {
  84. return string.Empty;
  85. }
  86. return string.Join(":", macAddress.Take(macAddressLength).Select(value => value.ToString("X2")));
  87. }
  88. private sealed class DiscoveryResponse
  89. {
  90. [JsonPropertyName("message_type")]
  91. public string? MessageType { get; set; }
  92. [JsonPropertyName("device_id")]
  93. public string? DeviceId { get; set; }
  94. [JsonPropertyName("hostname")]
  95. public string? Hostname { get; set; }
  96. [JsonPropertyName("server_version")]
  97. public string? ServerVersion { get; set; }
  98. [JsonPropertyName("mac")]
  99. public string? Mac { get; set; }
  100. [JsonPropertyName("lan2_ip")]
  101. public string? Lan2Ip { get; set; }
  102. [JsonPropertyName("http_port")]
  103. public int HttpPort { get; set; }
  104. [JsonPropertyName("auth_required")]
  105. public bool AuthRequired { get; set; }
  106. }
  107. }