| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- using System.Net;
- using System.Net.Sockets;
- using System.Runtime.InteropServices;
- using System.Text;
- using System.Text.Json;
- using System.Text.Json.Serialization;
- using NetworkTool.Client.Models;
- namespace NetworkTool.Client.Services;
- public sealed class DiscoveryService
- {
- private const int DiscoveryPort = 50000;
- [DllImport("iphlpapi.dll", ExactSpelling = true)]
- private static extern int SendARP(int destinationIp, int sourceIp, byte[] macAddress, ref int physicalAddressLength);
- public async Task<IReadOnlyList<DiscoveredDevice>> DiscoverManyAsync(string localIPv4, CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrWhiteSpace(localIPv4))
- {
- return [];
- }
- using var client = new UdpClient(AddressFamily.InterNetwork)
- {
- EnableBroadcast = true,
- };
- client.Client.Bind(new IPEndPoint(IPAddress.Parse(localIPv4), 0));
- var request = new
- {
- protocol_version = 1,
- message_type = "discover",
- request_id = Guid.NewGuid().ToString(),
- client_name = Environment.MachineName,
- };
- var payload = JsonSerializer.SerializeToUtf8Bytes(request);
- await client.SendAsync(payload, payload.Length, new IPEndPoint(IPAddress.Broadcast, DiscoveryPort));
- using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
- timeoutCts.CancelAfter(TimeSpan.FromSeconds(3));
- var devicesByIp = new Dictionary<string, DiscoveredDevice>(StringComparer.OrdinalIgnoreCase);
- while (!timeoutCts.IsCancellationRequested)
- {
- try
- {
- var result = await client.ReceiveAsync(timeoutCts.Token);
- var response = JsonSerializer.Deserialize<DiscoveryResponse>(result.Buffer);
- if (response is null
- || response.MessageType != "discover_response"
- || string.IsNullOrWhiteSpace(response.Lan2Ip)
- || !response.Lan2Ip.StartsWith("169.254.", StringComparison.Ordinal))
- {
- continue;
- }
- var mac = response.Mac;
- if (string.IsNullOrWhiteSpace(mac))
- {
- mac = ResolveMacAddress(result.RemoteEndPoint.Address);
- }
- devicesByIp[response.Lan2Ip] = new DiscoveredDevice
- {
- DeviceId = response.DeviceId ?? string.Empty,
- Hostname = response.Hostname ?? string.Empty,
- ServerVersion = response.ServerVersion ?? string.Empty,
- Mac = mac ?? string.Empty,
- Lan2Ip = response.Lan2Ip,
- HttpPort = response.HttpPort,
- AuthRequired = response.AuthRequired,
- };
- }
- catch (OperationCanceledException)
- {
- break;
- }
- }
- return devicesByIp.Values.OrderBy(device => device.Lan2Ip).ToList();
- }
- private static string ResolveMacAddress(IPAddress ipAddress)
- {
- if (ipAddress.AddressFamily != AddressFamily.InterNetwork)
- {
- return string.Empty;
- }
- var macAddress = new byte[6];
- var macAddressLength = macAddress.Length;
- var destinationIp = BitConverter.ToInt32(ipAddress.GetAddressBytes(), 0);
- if (SendARP(destinationIp, 0, macAddress, ref macAddressLength) != 0 || macAddressLength <= 0)
- {
- return string.Empty;
- }
- return string.Join(":", macAddress.Take(macAddressLength).Select(value => value.ToString("X2")));
- }
- private sealed class DiscoveryResponse
- {
- [JsonPropertyName("message_type")]
- public string? MessageType { get; set; }
- [JsonPropertyName("device_id")]
- public string? DeviceId { get; set; }
- [JsonPropertyName("hostname")]
- public string? Hostname { get; set; }
- [JsonPropertyName("server_version")]
- public string? ServerVersion { get; set; }
- [JsonPropertyName("mac")]
- public string? Mac { get; set; }
- [JsonPropertyName("lan2_ip")]
- public string? Lan2Ip { get; set; }
- [JsonPropertyName("http_port")]
- public int HttpPort { get; set; }
- [JsonPropertyName("auth_required")]
- public bool AuthRequired { get; set; }
- }
- }
|