feat(quic): replace probe UDP data plane with QUIC datagrams
This commit is contained in:
1 parent
4609048865
commit
b964dc2bbc
53 files changed
+2413
-411
No files matched your search
@@ -40,8 +40,8 @@ public sealed class ModelAndTestEvaluatorTests
|
||||
[Fact]
|
||||
public void BaseModel_PerfectLine_ScoresHigh_AndBadLine_ScoresLow()
|
||||
{
|
||||
double good = new BaseModel().Score(View(("rtt.p95", 25), ("loss.total", 0), ("jitter.down.p95", 1), ("jitter.up.p95", 1), ("loss.run_max", 1))).Score!.Value;
|
||||
double bad = new BaseModel().Score(View(("rtt.p95", 400), ("loss.total", 12), ("jitter.down.p95", 90), ("jitter.up.p95", 90), ("loss.run_max", 40))).Score!.Value;
|
||||
double good = new BaseModel().Score(View(("rtt.p95", 25), ("loss.total", 0), ("jitter.down.p95", 1), ("jitter.up.p95", 1), ("loss.run.up_max", 1), ("loss.run.down_max", 1))).Score!.Value;
|
||||
double bad = new BaseModel().Score(View(("rtt.p95", 400), ("loss.total", 12), ("jitter.down.p95", 90), ("jitter.up.p95", 90), ("loss.run.up_max", 40), ("loss.run.down_max", 40))).Score!.Value;
|
||||
Assert.True(good > 95);
|
||||
Assert.True(bad < 10);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ public sealed class ModelAndTestEvaluatorTests
|
||||
var ranges = new Dictionary<string, double[]>
|
||||
{
|
||||
["rtt.p95"] = [10, 30, 80, 150, 300, 600], ["loss.total"] = [0, 0.5, 1, 3, 10, 30],
|
||||
["jitter.down.p95"] = [0, 2, 8, 20, 80, 200], ["jitter.up.p95"] = [0, 2, 8, 20, 80, 200], ["loss.run_max"] = [1, 3, 10, 30, 60],
|
||||
["jitter.down.p95"] = [0, 2, 8, 20, 80, 200], ["jitter.up.p95"] = [0, 2, 8, 20, 80, 200], ["loss.run.up_max"] = [1, 3, 10, 30, 60], ["loss.run.down_max"] = [1, 3, 10, 30, 60],
|
||||
["tcp.rtt.p95"] = [10, 40, 100, 200, 400, 800], ["tcp.stall.per_min"] = [0, 1, 5, 20, 60, 200], ["tcp.stall_max"] = [50, 150, 500, 2000, 5000],
|
||||
["tcp.retrans"] = [0, 0.1, 1, 3, 10, 30], ["connect.tls_ms.p95"] = [50, 150, 400, 1000, 3000], ["hold.resets.per_h"] = [0, 1, 4, 12, 30],
|
||||
["connect.success"] = [100, 99, 95, 80, 50], ["tcp.bulk.peak.max"] = [800, 400, 100, 10, 1], ["hy2.sustainable_mbps"] = [100, 50, 25, 10, 0],
|
||||
|
||||
@@ -90,6 +90,28 @@ public sealed class OpTests
|
||||
Assert.Equal(2, ((RunLengthAcc)EvaluateOneBucket(chunk, "loss.run").Items["loss.run"]).Max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LossRuns_AreSeparatedByDirection_AndDownstreamExcludesRequestsLostUpstream()
|
||||
{
|
||||
bool[] upLost = [false, true, true, false, false, false, true, true];
|
||||
bool[] downLost = [false, false, true, true, false, true, false, true];
|
||||
RawChunk chunk = ChunkFactory.Echo(s_agent, "udp_echo", T0, upLost.Length, 100,
|
||||
i => new EchoPacket(upLost[i], downLost[i], 20_000, 20_000));
|
||||
|
||||
MetricSet set = EvaluateOneBucket(chunk, "loss.run.up", "loss.run.down");
|
||||
Assert.Equal(2, ((MaxAcc)set.Items["loss.run.up"]).Result);
|
||||
Assert.Equal(1, ((MaxAcc)set.Items["loss.run.down"]).Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LossRuns_NoLoss_ExportsZeroForBothDirections()
|
||||
{
|
||||
RawChunk chunk = ChunkFactory.UdpEchoClean(s_agent, T0, count: 8);
|
||||
MetricSet set = EvaluateOneBucket(chunk, "loss.run.up", "loss.run.down");
|
||||
Assert.Equal(0, ((MaxAcc)set.Items["loss.run.up"]).Result);
|
||||
Assert.Equal(0, ((MaxAcc)set.Items["loss.run.down"]).Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reorder_CountsLateArrivals_AndIgnoresDuplicates()
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed class AgentEndToEndTests
|
||||
AgentHarness agent = await AgentHarness.StartAsync(_backend);
|
||||
await agent.OverrideLineTasksAsync(rateHz: 30);
|
||||
ProbeSimulator probe = await agent.ConnectProbeAsync();
|
||||
LineTaskSet set = await probe.WaitForTasksAsync(s => s.Tasks.Any(t => t.Kind == "udp_echo" && t.Port == agent.App.UdpPorts[0]));
|
||||
LineTaskSet set = await probe.WaitForTasksAsync(s => s.Tasks.Any(t => t.Kind == "udp_echo" && t.Port == agent.App.QuicPorts[0]));
|
||||
return (agent, probe, set.Tasks.Single(t => t.Kind == "udp_echo"));
|
||||
}
|
||||
|
||||
@@ -159,8 +159,8 @@ public sealed class AgentEndToEndTests
|
||||
|
||||
// The probe process restarts: a new session with its own streams; sequence numbers start from 0 again
|
||||
await using ProbeSimulator second = first.Restarted();
|
||||
await second.ConnectAsync("127.0.0.1", agent.App.ControlPort, agent.TlsPublicKey, agent.App.UdpPorts[0], agent.NodeId);
|
||||
LineTaskSet set = await second.WaitForTasksAsync(s => s.Tasks.Any(t => t.Kind == "udp_echo" && t.Port == agent.App.UdpPorts[0]));
|
||||
await second.ConnectAsync("127.0.0.1", agent.App.ControlPort, agent.TlsPublicKey, agent.App.QuicPorts[0], agent.NodeId);
|
||||
LineTaskSet set = await second.WaitForTasksAsync(s => s.Tasks.Any(t => t.Kind == "udp_echo" && t.Port == agent.App.QuicPorts[0]));
|
||||
LineTask udp2 = set.Tasks.Single(t => t.Kind == "udp_echo");
|
||||
Assert.NotEqual(udp.StreamId, udp2.StreamId);
|
||||
|
||||
@@ -221,11 +221,11 @@ public sealed class AgentEndToEndTests
|
||||
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
|
||||
await agent.CreateProbeAsync();
|
||||
await using var stranger = new ProbeSimulator("prb_stranger", Fleet.Identity.NodeIdentity.Generate());
|
||||
await Assert.ThrowsAnyAsync<Exception>(() => stranger.ConnectAsync("127.0.0.1", agent.App.ControlPort, agent.TlsPublicKey, agent.App.UdpPorts[0], agent.NodeId));
|
||||
await Assert.ThrowsAnyAsync<Exception>(() => stranger.ConnectAsync("127.0.0.1", agent.App.ControlPort, agent.TlsPublicKey, agent.App.QuicPorts[0], agent.NodeId));
|
||||
|
||||
// 证书公钥不对(中间人)也不会连上
|
||||
await using ProbeSimulator probe = await agent.CreateProbeAsync();
|
||||
await Assert.ThrowsAnyAsync<Exception>(() => probe.ConnectAsync("127.0.0.1", agent.App.ControlPort, Fleet.Identity.NodeIdentity.Generate().PublicKeySpki, agent.App.UdpPorts[0], agent.NodeId));
|
||||
await Assert.ThrowsAnyAsync<Exception>(() => probe.ConnectAsync("127.0.0.1", agent.App.ControlPort, Fleet.Identity.NodeIdentity.Generate().PublicKeySpki, agent.App.QuicPorts[0], agent.NodeId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -335,7 +335,7 @@ public sealed class AgentEndToEndTests
|
||||
await using AgentHarness agent = await AgentHarness.StartAsync(_backend, config => config.Limits.MaxTotalMbps = 20);
|
||||
await agent.OverrideLineTasksAsync();
|
||||
|
||||
var phase = new TestPhase { Kind = "udp_train", DurationSeconds = 1, Params = { ["port"] = System.Text.Json.JsonSerializer.SerializeToElement(agent.App.UdpPorts[0]) } };
|
||||
var phase = new TestPhase { Kind = "udp_train", DurationSeconds = 1, Params = { ["port"] = System.Text.Json.JsonSerializer.SerializeToElement(agent.App.QuicPorts[0]) } };
|
||||
var run = new TestRunParams { TestId = new TestId("tst_x"), ProbeId = new ProbeId("prb_nobody"), Plan = "x", Phases = [phase] };
|
||||
Assert.Equal("probe_offline", agent.App.Tests.Start(run).Error.Code.Value);
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class AgentHarness : IAsyncDisposable
|
||||
var config = new AgentConfig
|
||||
{
|
||||
Fleet = { Server = backend.BaseAddress.ToString(), IdentityDir = directory },
|
||||
Listen = { Bind = "127.0.0.1", ControlPort = 0, UdpPorts = [0], TcpPorts = [0] },
|
||||
Listen = { Bind = "127.0.0.1", ControlPort = 0, QuicPorts = [0], TcpPorts = [0] },
|
||||
};
|
||||
configure?.Invoke(config);
|
||||
LoadedNodeIdentity identity = LoadedNodeIdentity.Load(directory);
|
||||
@@ -87,7 +87,7 @@ public sealed class AgentHarness : IAsyncDisposable
|
||||
public async Task<ProbeSimulator> ConnectProbeAsync()
|
||||
{
|
||||
ProbeSimulator probe = await CreateProbeAsync();
|
||||
await probe.ConnectAsync("127.0.0.1", App.ControlPort, TlsPublicKey, App.UdpPorts[0], NodeId);
|
||||
await probe.ConnectAsync("127.0.0.1", App.ControlPort, TlsPublicKey, App.QuicPorts[0], NodeId);
|
||||
return probe;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public sealed class AgentHarness : IAsyncDisposable
|
||||
}
|
||||
|
||||
string kind = task["kind"]!.GetValue<string>();
|
||||
var parameters = new JsonObject { ["port"] = kind == "udp_echo" ? App.UdpPorts[0] : App.TcpPorts[0] };
|
||||
var parameters = new JsonObject { ["port"] = kind == "udp_echo" ? App.QuicPorts[0] : App.TcpPorts[0] };
|
||||
// 默认的 connect 模板在这些用例里不需要(也占预算):对这个 Agent 关掉它
|
||||
bool enabled = kind != "connect";
|
||||
if (rateHz is { } hz && kind != "connect")
|
||||
|
||||
@@ -97,6 +97,7 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
["meta"] = new JsonObject
|
||||
{
|
||||
["probe_tcp_port"] = 7443,
|
||||
["probe_quic_ports"] = new JsonArray(7443),
|
||||
["probe_udp_ports"] = new JsonArray(7443),
|
||||
["public_host"] = publicHost,
|
||||
["kinds"] = new JsonArray((kinds ?? ["udp_echo", "tcp_echo", "udp_train", "connect", "hold", "tcp_bulk", "icmp_echo"]).Select(k => (JsonNode?)JsonValue.Create(k)).ToArray()),
|
||||
@@ -154,7 +155,8 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
TlsPublicKey = Convert.ToBase64String(_tlsKey.PublicKeySpki),
|
||||
Addresses = Addresses is null ? null : [.. Addresses],
|
||||
ProbeTcpPort = 7443,
|
||||
ProbeUdpPorts = [7443],
|
||||
ProbeQuicPorts = [7443],
|
||||
LegacyProbeUdpPorts = [7443],
|
||||
LocalLimits = new LocalLimits { MaxTotalMbps = 200, MaxTotalPps = 20_000, MaxConcurrentTests = 1 },
|
||||
},
|
||||
MoShiroJsonContext.Default.AgentHelloMeta);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\shared\MoShiro.Protocol\MoShiro.Protocol.csproj" />
|
||||
<ProjectReference Include="..\..\shared\MoShiro.Measurement\MoShiro.Measurement.csproj" />
|
||||
<ProjectReference Include="..\..\fleet\src\Fleet.Identity\Fleet.Identity.csproj" />
|
||||
<ProjectReference Include="..\..\fleet\src\Fleet.Link\Fleet.Link.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -8,13 +8,14 @@ using Fleet.Core;
|
||||
using Fleet.Identity;
|
||||
using Fleet.Link;
|
||||
using Fleet.Rpc;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace MoShiro.TestSupport;
|
||||
|
||||
/// <summary>
|
||||
/// 假 Probe:通过真实的点对点握手(TLS 固定证书 + 签名 + ECDH)连接真实的 Agent,接收 <c>line_task_set</c>,
|
||||
/// 在自己的 UDP 套接字上发回显包、接收单向数据流、估计时钟偏移,并把 Probe 一侧的列块经发件箱通道 17 交给 Agent。
|
||||
/// 通过 QUIC Datagram 发回显包、接收单向数据流、估计时钟偏移,并把 Probe 一侧的列块经发件箱通道 17 交给 Agent。
|
||||
/// 它实现 Probe 的协议职责,是将来真实 Probe 的参照。
|
||||
/// </summary>
|
||||
public sealed class ProbeSimulator : IAsyncDisposable
|
||||
@@ -30,8 +31,9 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
private readonly CancellationTokenSource _stop = new();
|
||||
private NodeId _agentId;
|
||||
private RpcSession<ProbeSimulator>? _rpc;
|
||||
private UdpClient? _udp;
|
||||
private IPEndPoint? _agentUdp;
|
||||
private QuicDatagramTransport? _quic;
|
||||
private DataSession? _dataSession;
|
||||
private IPEndPoint? _agentQuic;
|
||||
private PeerSessionInfo? _info;
|
||||
private Task? _receiver;
|
||||
private Task? _run;
|
||||
@@ -72,13 +74,22 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
(PeerSessionInfo info, TlsFrameTransport transport) = await PeerConnector.ConnectAsync(host, controlPort, agentPublicKey, _identity, ProbeId, "0.0.0-sim", 8 << 20, time ?? TimeProvider.System, ct);
|
||||
_info = info;
|
||||
_agentKey = agentPublicKey;
|
||||
_udp = new UdpClient(new IPEndPoint(IPAddress.Loopback, 0));
|
||||
_agentUdp = new IPEndPoint(IPAddress.Parse(host), udpPort);
|
||||
_dataSession = new DataSession(info.Credentials.SessionIdValue, info.Credentials.Key);
|
||||
_agentQuic = new IPEndPoint(IPAddress.Parse(host), udpPort);
|
||||
_quic = new QuicDatagramTransport(
|
||||
_dataSession,
|
||||
new MonoClock(time ?? TimeProvider.System),
|
||||
new DataPlaneCounters(),
|
||||
new TestNodeLog(),
|
||||
_agentQuic,
|
||||
agentPublicKey,
|
||||
0);
|
||||
_rpc = new RpcSession<ProbeSimulator>(transport, _router, this, time ?? TimeProvider.System);
|
||||
_rpc.OnBinary = (_, _) => ValueTask.CompletedTask;
|
||||
_run = _rpc.RunAsync(_stop.Token);
|
||||
_receiver = Task.Run(ReceiveLoopAsync, _stop.Token);
|
||||
_receiver = _quic.RunAsync(_stop.Token);
|
||||
await SyncClockAsync();
|
||||
await _quic.ConnectAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<LineTaskSet> WaitForTasksAsync(Func<LineTaskSet, bool> predicate, TimeSpan? timeout = null)
|
||||
@@ -123,19 +134,21 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
int size = (int)task.Params["size"].GetInt64();
|
||||
var collector = new EchoCollector();
|
||||
_echoes[task.StreamId] = collector;
|
||||
_quic!.Register(task.StreamId, OnDatagram);
|
||||
long startUtc = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds();
|
||||
byte[] packet = new byte[size];
|
||||
for (uint seq = 0; seq < count; seq++)
|
||||
{
|
||||
long t1 = NowUs();
|
||||
collector.Sent[seq] = t1;
|
||||
UdpPacket.Write(packet, new UdpHeader(UdpPacketType.EchoRequest, _info!.Credentials.SessionIdValue, task.StreamId, seq, t1), _info.Credentials.Key);
|
||||
await _udp!.SendAsync(packet, packet.Length, _agentUdp!);
|
||||
MeasurementDatagram.Write(packet, new MeasurementDatagramHeader(MeasurementDatagramType.EchoRequest, _info!.Credentials.SessionIdValue, task.StreamId, seq, t1), _info.Credentials.Key);
|
||||
_quic.Send(_dataSession!, _agentQuic!, packet);
|
||||
await Task.Delay(interval, ct);
|
||||
}
|
||||
|
||||
await Task.Delay(300, ct); // 等最后的回复
|
||||
_echoes.TryRemove(task.StreamId, out _);
|
||||
_quic.Unregister(task.StreamId);
|
||||
|
||||
var msg = new StreamBuilder(new StreamSpec("msg", [Col.Seq("seq"), Col.U16("size"), Col.TsProbe("t1"), Col.TsProbe("t4", nullable: true)]));
|
||||
foreach ((uint seq, long t1) in collector.Sent.OrderBy(p => p.Key))
|
||||
@@ -191,22 +204,28 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
}
|
||||
|
||||
/// <summary>发送任意字节到 Agent 的 UDP 端口(用来测试 Agent 对垃圾包的处理)。</summary>
|
||||
public Task SendRawUdpAsync(byte[] datagram) => _udp!.SendAsync(datagram, datagram.Length, _agentUdp!);
|
||||
public Task SendRawUdpAsync(byte[] datagram)
|
||||
{
|
||||
_quic!.Send(_dataSession!, _agentQuic!, datagram);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>用伪造的密钥发一个回显包。</summary>
|
||||
public Task SendForgedEchoAsync(LineTask task, uint seq, byte[]? key = null)
|
||||
{
|
||||
byte[] packet = new byte[64];
|
||||
UdpPacket.Write(packet, new UdpHeader(UdpPacketType.EchoRequest, _info!.Credentials.SessionIdValue, task.StreamId, seq, NowUs()), key ?? new byte[32]);
|
||||
return _udp!.SendAsync(packet, packet.Length, _agentUdp!);
|
||||
MeasurementDatagram.Write(packet, new MeasurementDatagramHeader(MeasurementDatagramType.EchoRequest, _info!.Credentials.SessionIdValue, task.StreamId, seq, NowUs()), key ?? new byte[32]);
|
||||
_quic!.Send(_dataSession!, _agentQuic!, packet);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>重放:用同一个序号、同样的密钥再发一次。</summary>
|
||||
public Task SendEchoAsync(LineTask task, uint seq)
|
||||
{
|
||||
byte[] packet = new byte[64];
|
||||
UdpPacket.Write(packet, new UdpHeader(UdpPacketType.EchoRequest, _info!.Credentials.SessionIdValue, task.StreamId, seq, NowUs()), _info.Credentials.Key);
|
||||
return _udp!.SendAsync(packet, packet.Length, _agentUdp!);
|
||||
MeasurementDatagram.Write(packet, new MeasurementDatagramHeader(MeasurementDatagramType.EchoRequest, _info!.Credentials.SessionIdValue, task.StreamId, seq, NowUs()), _info.Credentials.Key);
|
||||
_quic!.Send(_dataSession!, _agentQuic!, packet);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public int EchoRepliesSeen(uint streamId) => _echoes.TryGetValue(streamId, out EchoCollector? c) ? c.Received.Count : 0;
|
||||
@@ -227,7 +246,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _stop.CancelAsync();
|
||||
_udp?.Dispose();
|
||||
_quic?.Dispose();
|
||||
if (_rpc is not null)
|
||||
{
|
||||
await _rpc.DisposeAsync();
|
||||
@@ -277,41 +296,18 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
return header;
|
||||
}
|
||||
|
||||
private async Task ReceiveLoopAsync()
|
||||
private void OnDatagram(in QuicDatagramDelivery delivery)
|
||||
{
|
||||
while (!_stop.IsCancellationRequested)
|
||||
long now = delivery.ReceiveUs;
|
||||
MeasurementDatagramHeader header = delivery.Header;
|
||||
if (header.Type == MeasurementDatagramType.EchoReply && _echoes.TryGetValue(header.StreamId, out EchoCollector? echo))
|
||||
{
|
||||
UdpReceiveResult result;
|
||||
try
|
||||
{
|
||||
result = await _udp!.ReceiveAsync(_stop.Token);
|
||||
}
|
||||
catch (Exception ex) when (ex is OperationCanceledException or ObjectDisposedException or SocketException)
|
||||
{
|
||||
if (_stop.IsCancellationRequested || ex is ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
long now = NowUs();
|
||||
byte[] data = result.Buffer;
|
||||
if (!UdpPacket.TryParseHeader(data, out UdpHeader header) || !UdpPacket.VerifyMac(data, _info!.Credentials.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (header.Type == UdpPacketType.EchoReply && _echoes.TryGetValue(header.StreamId, out EchoCollector? echo))
|
||||
{
|
||||
echo.Received.TryAdd(header.Seq, now);
|
||||
}
|
||||
else if (header.Type == UdpPacketType.Data && _trains.TryGetValue(header.StreamId, out TrainCollector? train))
|
||||
{
|
||||
train.Received.TryAdd(header.Seq, now);
|
||||
train.Size = data.Length;
|
||||
}
|
||||
echo.Received.TryAdd(header.Seq, now);
|
||||
}
|
||||
else if (header.Type == MeasurementDatagramType.Data && _trains.TryGetValue(header.StreamId, out TrainCollector? train))
|
||||
{
|
||||
train.Received.TryAdd(header.Seq, now);
|
||||
train.Size = delivery.Payload.Length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,33 +362,39 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
|
||||
private readonly ConcurrentDictionary<uint, TestPhaseStartParams> _activePhases = new();
|
||||
|
||||
private async Task<bool> StartPhaseAsync(TestPhaseStartParams start, CancellationToken ct)
|
||||
private Task<bool> StartPhaseAsync(TestPhaseStartParams start, CancellationToken ct)
|
||||
{
|
||||
Phases.Enqueue(start);
|
||||
if (RefusePhases)
|
||||
{
|
||||
return false;
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
_activePhases[start.StreamId] = start;
|
||||
if (start.Kind == "udp_train")
|
||||
{
|
||||
_trains[start.StreamId] = new TrainCollector { StartUtcMs = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds() };
|
||||
_quic!.Register(start.StreamId, OnDatagram);
|
||||
// 先发几个探活包,让 Agent 学到(最新的)源地址,再回复 accepted
|
||||
byte[] hello = new byte[64];
|
||||
for (uint seq = 0; seq < 3; seq++)
|
||||
{
|
||||
UdpPacket.Write(hello, new UdpHeader(UdpPacketType.Data, _info!.Credentials.SessionIdValue, start.StreamId, seq, NowUs()), _info.Credentials.Key);
|
||||
await _udp!.SendAsync(hello.AsMemory(), _agentUdp!, ct);
|
||||
MeasurementDatagram.Write(hello, new MeasurementDatagramHeader(MeasurementDatagramType.Data, _info!.Credentials.SessionIdValue, start.StreamId, seq, NowUs()), _info.Credentials.Key);
|
||||
_quic.Send(_dataSession!, _agentQuic!, hello);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
_ = ct;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
private async Task<int> EndPhaseAsync(TestPhaseEndParams end, CancellationToken ct)
|
||||
{
|
||||
_activePhases.TryRemove(end.StreamId, out TestPhaseStartParams? start);
|
||||
if (start is not null)
|
||||
{
|
||||
_quic!.Unregister(start.StreamId);
|
||||
}
|
||||
if (start is null || DropFragments)
|
||||
{
|
||||
_trains.TryRemove(end.StreamId, out _);
|
||||
@@ -456,4 +458,9 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
|
||||
public long StartUtcMs { get; init; }
|
||||
}
|
||||
|
||||
private sealed class TestNodeLog : INodeLog
|
||||
{
|
||||
public void Write(NodeLogLevel level, string message, params (string Key, object? Value)[] fields) { }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user