backend: require confirmation for signed channel updates [CI]
This commit is contained in:
1 parent
7160de2a36
commit
84f9d211bc
62 files changed
+1717
-70
No files matched your search
@@ -0,0 +1,154 @@
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>诊断采集进程所在的网络环境。宿主网络容器看到的是主机;桥接容器看到自己的网络环境。</summary>
|
||||
public static class Ipv6Diagnostics
|
||||
{
|
||||
public static Ipv6Diagnostic Sample(bool? listening = null, string? networkScope = null, string? hostProcRoot = null)
|
||||
{
|
||||
bool supported = Socket.OSSupportsIPv6;
|
||||
bool? address = null;
|
||||
bool? route = null;
|
||||
if (supported)
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
address = ParseAddresses(Read("/proc/net/if_inet6"));
|
||||
route = ParseRoutes(Read("/proc/net/ipv6_route"));
|
||||
}
|
||||
else if (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
try
|
||||
{
|
||||
address = false;
|
||||
route = false;
|
||||
foreach (NetworkInterface adapter in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
if (adapter.OperationalStatus != OperationalStatus.Up || adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IPInterfaceProperties properties = adapter.GetIPProperties();
|
||||
foreach (UnicastIPAddressInformation unicast in properties.UnicastAddresses)
|
||||
{
|
||||
if (IsGlobal(unicast.Address))
|
||||
{
|
||||
address = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses)
|
||||
{
|
||||
if (gateway.Address.AddressFamily == AddressFamily.InterNetworkV6 && !gateway.Address.Equals(IPAddress.IPv6Any))
|
||||
{
|
||||
route = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is NetworkInformationException or PlatformNotSupportedException)
|
||||
{
|
||||
address = null;
|
||||
route = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string scope = networkScope is "host" or "container" ? networkScope
|
||||
: File.Exists("/run/.containerenv") || File.Exists("/.dockerenv") ? "container" : "host";
|
||||
bool? hostAddress = scope == "host" ? address : null;
|
||||
bool? hostRoute = scope == "host" ? route : null;
|
||||
if (scope == "container" && OperatingSystem.IsLinux() && hostProcRoot is not null)
|
||||
{
|
||||
// /host/proc/net 通常指向当前进程的网络空间;宿主 PID 1 才是宿主网络。
|
||||
hostAddress = ParseAddresses(Read(Path.Combine(hostProcRoot, "1/net/if_inet6")));
|
||||
hostRoute = ParseRoutes(Read(Path.Combine(hostProcRoot, "1/net/ipv6_route")));
|
||||
}
|
||||
|
||||
Ipv6Diagnostic result = Evaluate(supported, listening, address, route);
|
||||
result.NetworkScope = scope;
|
||||
result.HostHasGlobalAddress = hostAddress;
|
||||
result.HostHasDefaultRoute = hostRoute;
|
||||
if (scope == "container" && address == false && hostAddress == true)
|
||||
{
|
||||
result.State = "container_network_not_ready";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Ipv6Diagnostic Evaluate(bool supported, bool? listening, bool? address, bool? route) => new()
|
||||
{
|
||||
SocketSupported = supported,
|
||||
Listening = listening,
|
||||
HasGlobalAddress = address,
|
||||
HasDefaultRoute = route,
|
||||
State = !supported ? "unsupported" : address == false ? "no_global_address" : listening == false ? "not_listening"
|
||||
: route == false ? "no_default_route" : address is null || route is null ? "unknown" : "ready",
|
||||
};
|
||||
|
||||
public static bool? ParseAddresses(string? text)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (string line in text.Split('\n'))
|
||||
{
|
||||
string[] fields = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (fields.Length != 6 || fields[0].Length != 32 || fields[0][0] is not ('2' or '3') || fields[3] != "00")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// tentative、DAD failed 和 deprecated 地址不能作为就绪的公网地址。
|
||||
if (uint.TryParse(fields[4], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint flags) && (flags & 0x68) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool? ParseRoutes(string? text)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (string line in text.Split('\n'))
|
||||
{
|
||||
string[] fields = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (fields.Length == 10 && fields[0] == "00000000000000000000000000000000" && fields[1] == "00" && fields[9] != "lo"
|
||||
&& uint.TryParse(fields[8], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint flags) && (flags & 1) != 0 && (flags & 0x200) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsGlobal(IPAddress address) => address.AddressFamily == AddressFamily.InterNetworkV6 && (address.GetAddressBytes()[0] & 0xE0) == 0x20;
|
||||
|
||||
private static string? Read(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return File.Exists(path) ? File.ReadAllText(path) : null;
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user