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
@@ -109,14 +109,16 @@ internal static unsafe class MsQuicApi
|
||||
|
||||
public static void RegistrationClose(nint handle) => s_registrationClose(handle);
|
||||
|
||||
public static nint ConfigurationOpen(nint registration, byte[] alpn)
|
||||
public static nint ConfigurationOpen(nint registration, byte[] alpn, QuicSettings? settings = null)
|
||||
{
|
||||
byte[] native = settings is { IsDefault: false } ? settings.ToNative() : [];
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* alpnBytes = alpn)
|
||||
fixed (byte* settingsBytes = native)
|
||||
{
|
||||
var buffer = new QuicBuffer { Length = (uint)alpn.Length, Buffer = (nint)alpnBytes };
|
||||
uint status = s_configurationOpen(registration, ref buffer, 1, 0, 0, 0, out nint configuration);
|
||||
uint status = s_configurationOpen(registration, ref buffer, 1, native.Length == 0 ? 0 : (nint)settingsBytes, (uint)native.Length, 0, out nint configuration);
|
||||
Check(status, "ConfigurationOpen");
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@@ -156,7 +156,8 @@ internal sealed class MsQuicConnection : IAsyncDisposable, IDatagramConnection
|
||||
byte[] expectedPeerSpki,
|
||||
MsQuicDatagramCallback datagramHandler,
|
||||
INodeLog log,
|
||||
CancellationToken ct)
|
||||
CancellationToken ct,
|
||||
QuicSettings? settings = null)
|
||||
{
|
||||
nint registration = 0;
|
||||
nint configuration = 0;
|
||||
@@ -165,7 +166,7 @@ internal sealed class MsQuicConnection : IAsyncDisposable, IDatagramConnection
|
||||
try
|
||||
{
|
||||
registration = MsQuicApi.RegistrationOpen("moshiro-dialer");
|
||||
configuration = MsQuicApi.ConfigurationOpen(registration, QuicAlpn.Value);
|
||||
configuration = MsQuicApi.ConfigurationOpen(registration, QuicAlpn.Value, settings);
|
||||
LoadClientCredentials(configuration);
|
||||
var endpoint = new IPEndPoint(address.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any, localPort);
|
||||
SocketAddress serialized = endpoint.Serialize();
|
||||
|
||||
@@ -21,6 +21,7 @@ internal sealed class MsQuicListener : IAsyncDisposable
|
||||
private readonly INodeLog _log;
|
||||
private readonly Action<IPEndPoint>? _peerAddressChanged;
|
||||
private readonly X509Certificate2 _certificate;
|
||||
private readonly QuicSettings? _settings;
|
||||
private readonly GCHandle _self;
|
||||
private readonly ConcurrentDictionary<nint, MsQuicConnection> _connections = new();
|
||||
private readonly List<nint> _listeners = [];
|
||||
@@ -34,8 +35,10 @@ internal sealed class MsQuicListener : IAsyncDisposable
|
||||
X509Certificate2 certificate,
|
||||
MsQuicDatagramCallback datagramHandler,
|
||||
INodeLog log,
|
||||
Action<IPEndPoint>? peerAddressChanged)
|
||||
Action<IPEndPoint>? peerAddressChanged,
|
||||
QuicSettings? settings = null)
|
||||
{
|
||||
_settings = settings;
|
||||
// MsQuic opens dual-mode wildcard sockets, but filters incoming connections using
|
||||
// the address family supplied to ListenerStart. An IPv6Any listener alone rejects
|
||||
// IPv4-mapped connections with TLS alert 120 (no_application_protocol).
|
||||
@@ -62,7 +65,7 @@ internal sealed class MsQuicListener : IAsyncDisposable
|
||||
_registration = MsQuicApi.RegistrationOpen("moshiro-agent");
|
||||
try
|
||||
{
|
||||
_configuration = MsQuicApi.ConfigurationOpen(_registration, QuicAlpn.Value);
|
||||
_configuration = MsQuicApi.ConfigurationOpen(_registration, QuicAlpn.Value, _settings);
|
||||
MsQuicConnection.LoadServerCredentials(_configuration, _certificate);
|
||||
var bound = new List<int>(_ports.Count * _bindAddresses.Count);
|
||||
foreach (int port in _ports)
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed class QuicDatagramTransport : IDisposable, IAsyncDisposable
|
||||
private readonly DataSession? _clientSession;
|
||||
private readonly IPEndPoint? _remote;
|
||||
private readonly byte[]? _peerSpki;
|
||||
private readonly QuicSettings? _settings;
|
||||
private int _previousSourcePort;
|
||||
private readonly TaskCompletionSource _clientReady = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private MsQuicListener? _listener;
|
||||
@@ -28,8 +29,9 @@ public sealed class QuicDatagramTransport : IDisposable, IAsyncDisposable
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>Constructs an Agent-side QUIC listener.</summary>
|
||||
public QuicDatagramTransport(IDataSessions sessions, IAdmission? admission, MonoClock clock, DataPlaneCounters counters, INodeLog log, IPAddress bind, IReadOnlyList<int> ports, X509Certificate2 serverCertificate)
|
||||
public QuicDatagramTransport(IDataSessions sessions, IAdmission? admission, MonoClock clock, DataPlaneCounters counters, INodeLog log, IPAddress bind, IReadOnlyList<int> ports, X509Certificate2 serverCertificate, QuicSettings? settings = null)
|
||||
{
|
||||
_settings = settings;
|
||||
_router = new MeasurementDatagramRouter(sessions, admission, clock, counters, log);
|
||||
_log = log;
|
||||
_onDatagram = _router.Dispatch;
|
||||
@@ -39,8 +41,9 @@ public sealed class QuicDatagramTransport : IDisposable, IAsyncDisposable
|
||||
}
|
||||
|
||||
/// <summary>Constructs a 拨号方-side QUIC dialer for one Agent session.</summary>
|
||||
public QuicDatagramTransport(DataSession session, MonoClock clock, DataPlaneCounters counters, INodeLog log, IPEndPoint remote, byte[] peerSpki)
|
||||
public QuicDatagramTransport(DataSession session, MonoClock clock, DataPlaneCounters counters, INodeLog log, IPEndPoint remote, byte[] peerSpki, QuicSettings? settings = null)
|
||||
{
|
||||
_settings = settings;
|
||||
_router = new MeasurementDatagramRouter(new SingleSession(session), null, clock, counters, log);
|
||||
_log = log;
|
||||
_onDatagram = _router.Dispatch;
|
||||
@@ -73,7 +76,7 @@ public sealed class QuicDatagramTransport : IDisposable, IAsyncDisposable
|
||||
throw new InvalidOperationException("QUIC listener is already bound.");
|
||||
}
|
||||
|
||||
_listener = new MsQuicListener(_bind, _ports, _serverCertificate!, _onDatagram, _log, null);
|
||||
_listener = new MsQuicListener(_bind, _ports, _serverCertificate!, _onDatagram, _log, null, _settings);
|
||||
_listener.Start();
|
||||
BoundPorts = _listener.BoundPorts;
|
||||
}
|
||||
@@ -92,7 +95,7 @@ public sealed class QuicDatagramTransport : IDisposable, IAsyncDisposable
|
||||
ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
|
||||
await CloseClientAsync().ConfigureAwait(false);
|
||||
int sourcePort = SelectSourcePort(_remote.AddressFamily, _previousSourcePort);
|
||||
MsQuicConnection client = await MsQuicConnection.ConnectAsync(_remote.Address, _remote.Port, sourcePort, _peerSpki, _onDatagram, _log, ct).ConfigureAwait(false);
|
||||
MsQuicConnection client = await MsQuicConnection.ConnectAsync(_remote.Address, _remote.Port, sourcePort, _peerSpki, _onDatagram, _log, ct, _settings).ConfigureAwait(false);
|
||||
_client = client;
|
||||
_clientSession.BindDatagramConnection(client);
|
||||
BoundPorts = client.LocalEndPoint is { } local ? [local.Port] : [sourcePort];
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>
|
||||
/// 数据通道 QUIC 连接的传输参数(<c>agent.json</c> 的 <c>quic</c> 段);null 的项用 MsQuic 默认值。
|
||||
/// 一条 QUIC 连接由这个 Agent 与对端之间的所有任务共用,所以这些参数属于节点,不属于某个任务。
|
||||
/// 监听方用它接受连接,拨号方用它发起连接,两边各自生效。
|
||||
/// </summary>
|
||||
public sealed record QuicSettings
|
||||
{
|
||||
/// <summary>MsQuic 默认的最小、最大 MTU 是 1248 和 1500(IP 包大小,含 IP 和 UDP 头)。</summary>
|
||||
public const int MinAllowedMtu = 1200;
|
||||
|
||||
public const int MaxAllowedMtu = 65535;
|
||||
|
||||
/// <summary>路径 MTU 探测的起点;链路的真实 MTU 更小时探测会往下收缩,更大时往上探测到 <see cref="MaxMtu"/>。</summary>
|
||||
public int? MinMtu { get; init; }
|
||||
|
||||
/// <summary>路径 MTU 探测的上限。设成链路的真实 MTU 可以省掉探测,设小可以避开会丢大包的路径。</summary>
|
||||
public int? MaxMtu { get; init; }
|
||||
|
||||
public bool? Pacing { get; init; }
|
||||
|
||||
public int? IdleTimeoutMs { get; init; }
|
||||
|
||||
public int? KeepAliveMs { get; init; }
|
||||
|
||||
public bool IsDefault => MinMtu is null && MaxMtu is null && Pacing is null && IdleTimeoutMs is null && KeepAliveMs is null;
|
||||
|
||||
/// <summary>参数是否合法;不合法返回原因。</summary>
|
||||
public string? Validate()
|
||||
{
|
||||
if (MinMtu is { } min && min is < MinAllowedMtu or > MaxAllowedMtu)
|
||||
{
|
||||
return $"min_mtu must be {MinAllowedMtu}..{MaxAllowedMtu}";
|
||||
}
|
||||
|
||||
if (MaxMtu is { } max && max is < MinAllowedMtu or > MaxAllowedMtu)
|
||||
{
|
||||
return $"max_mtu must be {MinAllowedMtu}..{MaxAllowedMtu}";
|
||||
}
|
||||
|
||||
if (MinMtu is { } lo && MaxMtu is { } hi && lo > hi)
|
||||
{
|
||||
return "min_mtu must not exceed max_mtu";
|
||||
}
|
||||
|
||||
if (IdleTimeoutMs is < 0)
|
||||
{
|
||||
return "idle_timeout_ms must not be negative";
|
||||
}
|
||||
|
||||
return KeepAliveMs is < 0 ? "keep_alive_ms must not be negative" : null;
|
||||
}
|
||||
|
||||
// QUIC_SETTINGS(msquic.h)的布局:IsSetFlags(u64) 后面依次是 u64、u32、u16 各一组字段,再是位域字节。
|
||||
// 这里只写用到的几项,其余字段保持 0 且不置位。长度只算到 MtuDiscoveryMissingProbeCount,更靠后的字段 MsQuic 不会读。
|
||||
private const int Size = 112;
|
||||
private const int IdleTimeoutOffset = 24;
|
||||
private const int KeepAliveOffset = 88;
|
||||
private const int MinMtuOffset = 102;
|
||||
private const int MaxMtuOffset = 104;
|
||||
private const int BitFieldOffset = 106;
|
||||
private const int PacingBit = 1 << 1;
|
||||
|
||||
private const int IdleTimeoutFlag = 2;
|
||||
private const int KeepAliveFlag = 16;
|
||||
private const int MinMtuFlag = 22;
|
||||
private const int MaxMtuFlag = 23;
|
||||
private const int PacingFlag = 25;
|
||||
|
||||
internal byte[] ToNative()
|
||||
{
|
||||
byte[] native = new byte[Size];
|
||||
ulong isSet = 0;
|
||||
if (IdleTimeoutMs is { } idle)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(native.AsSpan(IdleTimeoutOffset), (ulong)idle);
|
||||
isSet |= 1UL << IdleTimeoutFlag;
|
||||
}
|
||||
|
||||
if (KeepAliveMs is { } keepAlive)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(native.AsSpan(KeepAliveOffset), (uint)keepAlive);
|
||||
isSet |= 1UL << KeepAliveFlag;
|
||||
}
|
||||
|
||||
if (MinMtu is { } min)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(native.AsSpan(MinMtuOffset), (ushort)min);
|
||||
isSet |= 1UL << MinMtuFlag;
|
||||
}
|
||||
|
||||
if (MaxMtu is { } max)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(native.AsSpan(MaxMtuOffset), (ushort)max);
|
||||
isSet |= 1UL << MaxMtuFlag;
|
||||
}
|
||||
|
||||
if (Pacing is { } pacing)
|
||||
{
|
||||
native[BitFieldOffset] = (byte)(pacing ? PacingBit : 0);
|
||||
isSet |= 1UL << PacingFlag;
|
||||
}
|
||||
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(native, isSet);
|
||||
return native;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user