measurement: name the kinds by protocol and action, add the http_request kind and QUIC settings
This commit is contained in:
1 parent
bf2a598891
commit
e77880d372
31 files changed
+938
-141
No files matched your search
@@ -0,0 +1,104 @@
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// 单个 Agent 任务的目标:<c>http</c> 是 <c>http(s)://主机[:端口][/路径]</c>,其余 Kind 是 <c>主机[:端口]</c>(IP 或域名,IPv6 带端口时用方括号)。
|
||||
/// Backend 只检查格式,不做 DNS 解析;解析在 Agent 上进行,每个解析出的地址都要在 <c>allowed_target_cidrs</c> 里。
|
||||
/// </summary>
|
||||
public static class TargetHost
|
||||
{
|
||||
public const string HttpKind = "http_request";
|
||||
|
||||
/// <summary>解析目标;<paramref name="port"/> 为 0 表示没有写端口,<paramref name="url"/> 只在 http(s) URL 时有值。</summary>
|
||||
public static bool TryParse(string? target, out string host, out int port, out Uri? url)
|
||||
{
|
||||
host = "";
|
||||
port = 0;
|
||||
url = null;
|
||||
if (string.IsNullOrWhiteSpace(target) || target.Length > 2048 || target.Any(char.IsWhiteSpace))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.Contains("://", StringComparison.Ordinal))
|
||||
{
|
||||
if (!Uri.TryCreate(target, UriKind.Absolute, out Uri? uri) || uri.Scheme is not ("http" or "https") || uri.UserInfo.Length > 0 || !ValidHost(uri.IdnHost))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
url = uri;
|
||||
host = uri.IdnHost.Trim('[', ']');
|
||||
port = uri.IsDefaultPort ? 0 : uri.Port;
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPAddress.TryParse 会把「[v6]:端口」的端口丢掉,带方括号的先按端点解析
|
||||
if (!target.StartsWith('[') && IPAddress.TryParse(target, out IPAddress? ip))
|
||||
{
|
||||
host = ip.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IPEndPoint.TryParse(target, out IPEndPoint? endpoint))
|
||||
{
|
||||
if (endpoint.Port is 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
host = endpoint.Address.ToString();
|
||||
port = endpoint.Port;
|
||||
return true;
|
||||
}
|
||||
|
||||
int colon = target.LastIndexOf(':');
|
||||
string name = colon < 0 ? target : target[..colon];
|
||||
if (colon >= 0 && (!int.TryParse(target.AsSpan(colon + 1), NumberStyles.None, CultureInfo.InvariantCulture, out port) || port is < 1 or > 65535))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidHost(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
host = name.ToLowerInvariant();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>按 Kind 检查并规范化:<c>http</c> 必须是 http(s) URL,其余 Kind 必须是主机[:端口];不合格返回 null。</summary>
|
||||
public static string? Normalize(string kindId, string? target)
|
||||
{
|
||||
if (!TryParse(target, out string host, out int port, out Uri? url) || (url is not null) != (kindId == HttpKind))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (url is not null)
|
||||
{
|
||||
return url.AbsoluteUri;
|
||||
}
|
||||
|
||||
string shown = IPAddress.TryParse(host, out IPAddress? ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6 && port != 0 ? "[" + host + "]" : host;
|
||||
return port == 0 ? host : shown + ":" + port.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static bool ValidHost(string name)
|
||||
{
|
||||
if (IPAddress.TryParse(name.Trim('[', ']'), out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name.Length is 0 or > 253 || name[^1] == '-' || name[0] is '-' or '.')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return name.TrimEnd('.').Split('.').All(label => label.Length is > 0 and <= 63 && label.All(c => char.IsAsciiLetterOrDigit(c) || c == '-'));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user