using System;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
#nullable enable
namespace BililiveRecorder.WPF
{
internal static class NetworkChangeDetector
{
private static bool enabled = false;
private static readonly ILogger logger = Log.ForContext(typeof(NetworkChangeDetector));
private static readonly object debounceLock = new();
private static readonly TimeSpan debounceDelay = TimeSpan.FromSeconds(15);
private static CancellationTokenSource? debounceTokenSource = null;
internal static void Enable()
{
if (!enabled)
{
enabled = true;
logger.Debug("NetworkChangeDetector Enabled");
NetworkChange.NetworkAddressChanged += (sender, args) =>
{
logger.Debug("NetworkChange.NetworkAddressChanged");
LogNetworkInfo();
};
LogNetworkInfo();
}
}
private static void LogNetworkInfo()
{
lock (debounceLock)
{
debounceTokenSource?.Cancel();
debounceTokenSource = new CancellationTokenSource();
_ = Task.Delay(debounceDelay, debounceTokenSource.Token).ContinueWith(t =>
{
if (!t.IsCanceled)
{
try
{
LogNetworkInfoWithoutDebounce();
}
catch (Exception ex)
{
logger.Warning(ex, "检测网络状态时发生了错误");
}
}
}, TaskScheduler.Default);
}
}
private static void LogNetworkInfoWithoutDebounce()
{
var localV4 = ProbeLocalAddress(new IPEndPoint(IPAddress.Parse("8.8.8.8"), 53));
var localV6 = ProbeLocalAddress(new IPEndPoint(IPAddress.Parse("2001:4860:4860::8888"), 53));
var interfaces = NetworkInterface.GetAllNetworkInterfaces().Select(x => new NetInterface
{
Name = x.Name,
Description = x.Description,
NetworkInterfaceType = x.NetworkInterfaceType,
OperationalStatus = x.OperationalStatus,
Speed = x.Speed,
Addresses = x.GetIPProperties().UnicastAddresses.Select(x => x.Address).ToArray()
}).ToArray();
var info = new NetInfo
{
LocalIpv4 = MaskAddressForLogging(localV4),
LocalIpv6 = MaskAddressForLogging(localV6),
Interfaces = interfaces,
IsIpv6Enabled = localV6 is not null,
IsWifiEnabled = interfaces.Any(x => x.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
};
for (var i = 0; i < interfaces.Length; i++)
{
var ni = interfaces[i];
if (ni.Addresses.Contains(localV4))
{
ni.Flags |= NetInterfaceFlags.DefaultIpv4Interface;
if (ni.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
info.IsWifiUsed = true;
}
if (ni.Addresses.Contains(localV6))
{
ni.Flags |= NetInterfaceFlags.DefaultIpv6Interface;
if (ni.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
info.IsWifiUsed = true;
}
}
// Data collection completed, masking ips before logging
for (var i = 0; i < interfaces.Length; i++)
{
var ni = interfaces[i];
ni.Addresses = ni.Addresses.Select(x => MaskAddressForLogging(x)!).ToArray();
}
// Log
logger.Debug("Network Info: {@NetworkInfo}", info);
if (info.IsWifiUsed)
{
logger.Warning("检测到当前使用的是WiFi网络,可能不稳定,容易造成录播断开等问题。强烈建议使用有线网络录播。");
}
}
internal static IPAddress? MaskAddressForLogging(IPAddress? address)
{
switch ((address?.AddressFamily) ?? AddressFamily.Unknown)
{
case AddressFamily.InterNetwork:
{
var bytes = address!.GetAddressBytes();
bytes[3] = 0;
return new IPAddress(bytes);
}
case AddressFamily.InterNetworkV6:
{
var bytes = address!.GetAddressBytes();
if (address.IsIPv4MappedToIPv6)
{
bytes[15] = 0;
}
else
{
bytes[8] = 0;
bytes[9] = 0;
bytes[10] = 0;
bytes[11] = 0;
bytes[12] = 0;
bytes[13] = 0;
bytes[14] = 0;
bytes[15] = 0;
}
return new IPAddress(bytes);
}
default:
return address;
}
}
private static IPAddress? ProbeLocalAddress(IPEndPoint remote)
{
try
{
using var socket = new Socket(remote.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
socket.Connect(remote);
return (socket.LocalEndPoint as IPEndPoint)?.Address;
}
catch (Exception ex)
{
logger.Debug(ex, "Local address probing failed with remote address {RemoteAddress}", remote.Address);
return null;
}
}
public class NetInfo
{
///
/// Does wireless network interface exist
///
public bool IsWifiEnabled { get; set; }
///
/// Is wireless network being used
///
public bool IsWifiUsed { get; set; }
public bool IsIpv6Enabled { get; set; }
public IPAddress? LocalIpv4 { get; set; }
public IPAddress? LocalIpv6 { get; set; }
public NetInterface[] Interfaces { get; set; } = Array.Empty();
}
public class NetInterface
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public NetInterfaceFlags Flags { get; set; }
public NetworkInterfaceType NetworkInterfaceType { get; set; }
public OperationalStatus OperationalStatus { get; set; }
public long Speed { get; set; }
public IPAddress[] Addresses { get; set; } = Array.Empty();
}
[Flags]
public enum NetInterfaceFlags
{
None = 0,
DefaultIpv4Interface = 1 << 0,
DefaultIpv6Interface = 1 << 1,
}
}
}