Replace Probe with Agent across node runtime, Fleet contracts, backend state, API and storage. Report incoming sessions and outgoing link health through each Agent uplink, and retain directed link controls and retry. Update protocol fixtures and end-to-end coverage for the unified model; accept requested cancellation during the Fleet timeout test cleanup.
79 lines
3.2 KiB
C#
79 lines
3.2 KiB
C#
using System.Net;
|
||
using System.Net.Sockets;
|
||
using MoShiro.Measurement;
|
||
using MoShiro.Protocol;
|
||
using MoShiro.TestSupport;
|
||
|
||
namespace MoShiro.Backend.Tests;
|
||
|
||
/// <summary>数据面协议拒绝错误首帧、伪造包、重放包和垃圾数据。</summary>
|
||
[Trait("Category", "Slow")]
|
||
public sealed class AgentDataPlaneTests : IClassFixture<BackendFixture>
|
||
{
|
||
private readonly BackendFixture _backend;
|
||
|
||
public AgentDataPlaneTests(BackendFixture backend)
|
||
{
|
||
_backend = backend;
|
||
}
|
||
|
||
private async Task<(AgentHarness Agent, DialerSimulator Dialer, LineTask Udp)> LineAsync()
|
||
{
|
||
AgentHarness agent = await AgentHarness.StartAsync(_backend);
|
||
await agent.OverrideLineTasksAsync(rateHz: 30);
|
||
DialerSimulator dialer = await agent.ConnectDialerAsync();
|
||
LineTaskSet set = await dialer.WaitForTasksAsync(s => s.Tasks.Any(t => t.Kind == "udp_echo" && t.Port == agent.App.QuicPorts[0]));
|
||
return (agent, dialer, set.Tasks.Single(t => t.Kind == "udp_echo"));
|
||
}
|
||
|
||
[Fact]
|
||
public async Task TcpDataPlane_ClosesConnectionsWithoutAValidFirstFrame()
|
||
{
|
||
(AgentHarness agent, DialerSimulator dialer, _) = await LineAsync();
|
||
await using (agent)
|
||
await using (dialer)
|
||
{
|
||
using var client = new TcpClient();
|
||
await client.ConnectAsync(IPAddress.Loopback, agent.App.TcpPorts[0]);
|
||
await client.GetStream().WriteAsync(new byte[TcpWire.AuthBytes]); // 全零:magic 不对
|
||
byte[] buffer = new byte[16];
|
||
Assert.Equal(0, await ReadOrResetAsync(client.GetStream(), buffer)); // 连接被关闭:没有任何字节(Windows 上可能是 RST)
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(agent.App.Counters.DroppedInvalid >= 1));
|
||
}
|
||
}
|
||
|
||
[Fact]
|
||
public async Task DataPlane_DropsForgedReplayedAndGarbagePackets_WithoutAnswering()
|
||
{
|
||
(AgentHarness agent, DialerSimulator dialer, LineTask udp) = await LineAsync();
|
||
await using (agent)
|
||
await using (dialer)
|
||
{
|
||
long before = agent.App.Counters.DroppedInvalid;
|
||
await dialer.SendRawUdpAsync([1, 2, 3]);
|
||
await dialer.SendRawUdpAsync(new byte[64]);
|
||
await dialer.SendForgedEchoAsync(udp, 1);
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(agent.App.Counters.DroppedInvalid - before >= 3), because: "garbage and forged packets are counted");
|
||
|
||
// 重放:同一个序号发两次,只有第一次被接受
|
||
await dialer.SendEchoAsync(udp, 500);
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(agent.App.Counters.PacketsOut >= 1));
|
||
long dropped = agent.App.Counters.DroppedInvalid;
|
||
await dialer.SendEchoAsync(udp, 500);
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(agent.App.Counters.DroppedInvalid > dropped), because: "a replayed packet is dropped");
|
||
}
|
||
}
|
||
|
||
private static async Task<int> ReadOrResetAsync(Stream stream, byte[] buffer)
|
||
{
|
||
try
|
||
{
|
||
return await stream.ReadAsync(buffer).AsTask().WaitAsync(TimeSpan.FromSeconds(10));
|
||
}
|
||
catch (IOException)
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
}
|