106 lines
3.2 KiB
C#
106 lines
3.2 KiB
C#
|
|
using System.Net;
|
||
|
|
using System.Net.NetworkInformation;
|
||
|
|
using System.Net.Sockets;
|
||
|
|
using System.Text;
|
||
|
|
|
||
|
|
// Args: [interface_ip] [interval_ms]
|
||
|
|
// Defaults: auto-detect LAN IP, 50ms
|
||
|
|
|
||
|
|
string localIp = args.Length > 0 ? args[0] : GetLanInterfaceIp() ?? "0.0.0.0";
|
||
|
|
int intervalMs = args.Length > 1 && int.TryParse(args[1], out int iv) ? iv : 50;
|
||
|
|
|
||
|
|
using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||
|
|
sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||
|
|
sock.EnableBroadcast = true;
|
||
|
|
|
||
|
|
var localEp = new IPEndPoint(IPAddress.Parse(localIp), 68);
|
||
|
|
sock.Bind(localEp);
|
||
|
|
|
||
|
|
Console.WriteLine($"Bound to {localEp} (SO_REUSEADDR)");
|
||
|
|
Console.WriteLine("Sending NOPs + listening. Press Ctrl+C to stop.");
|
||
|
|
Console.WriteLine();
|
||
|
|
|
||
|
|
var cts = new CancellationTokenSource();
|
||
|
|
var recvTask = Task.Run(() => ReceiveLoop(sock, cts.Token));
|
||
|
|
|
||
|
|
var destEp = new IPEndPoint(IPAddress.Broadcast, 67);
|
||
|
|
uint nonce = 0;
|
||
|
|
|
||
|
|
while (!cts.Token.IsCancellationRequested)
|
||
|
|
{
|
||
|
|
nonce++;
|
||
|
|
string msg = $"HKMSTR {nonce}\n";
|
||
|
|
byte[] payload = Encoding.UTF8.GetBytes(msg);
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
int sent = sock.SendTo(payload, destEp);
|
||
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] SENT n={nonce} {sent} bytes");
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] SEND FAILED: {ex.Message}");
|
||
|
|
}
|
||
|
|
|
||
|
|
try { Thread.Sleep(intervalMs); }
|
||
|
|
catch (OperationCanceledException) { break; }
|
||
|
|
}
|
||
|
|
|
||
|
|
cts.Cancel();
|
||
|
|
await recvTask;
|
||
|
|
|
||
|
|
static string? GetLanInterfaceIp()
|
||
|
|
{
|
||
|
|
foreach (var nic in NetworkInterface.GetAllNetworkInterfaces())
|
||
|
|
{
|
||
|
|
if (nic.OperationalStatus != OperationalStatus.Up)
|
||
|
|
continue;
|
||
|
|
if (nic.NetworkInterfaceType == NetworkInterfaceType.Loopback)
|
||
|
|
continue;
|
||
|
|
if (nic.Description.Contains("WireGuard", StringComparison.OrdinalIgnoreCase))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
foreach (var addr in nic.GetIPProperties().UnicastAddresses)
|
||
|
|
{
|
||
|
|
if (addr.Address.AddressFamily == AddressFamily.InterNetwork)
|
||
|
|
{
|
||
|
|
var ip = addr.Address.ToString();
|
||
|
|
if (ip.StartsWith("192.168.") || ip.StartsWith("10.") || ip.StartsWith("172."))
|
||
|
|
return ip;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ReceiveLoop(Socket sock, CancellationToken ct)
|
||
|
|
{
|
||
|
|
byte[] buffer = new byte[4096];
|
||
|
|
EndPoint fromEp = new IPEndPoint(IPAddress.Any, 0);
|
||
|
|
|
||
|
|
while (!ct.IsCancellationRequested)
|
||
|
|
{
|
||
|
|
int received;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
if (!sock.Poll(500_000, SelectMode.SelectRead))
|
||
|
|
continue;
|
||
|
|
received = sock.ReceiveFrom(buffer, ref fromEp);
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] RECV ERROR: {ex.Message}");
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
string text = Encoding.UTF8.GetString(buffer, 0, received).TrimEnd('\n', '\r');
|
||
|
|
if (!text.StartsWith("HKMSTR"))
|
||
|
|
{
|
||
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] RECV {received} bytes from {fromEp} (no magic)");
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] RECV {received} bytes from {fromEp}: {text}");
|
||
|
|
}
|
||
|
|
}
|