using System.Net; using System.Net.Sockets; using MoShiro.Measurement; using MoShiro.Protocol; using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; /// 数据面协议拒绝错误首帧、伪造包、重放包和垃圾数据。 [Trait("Category", "Slow")] public sealed class AgentDataPlaneTests : IClassFixture { private readonly BackendFixture _backend; public AgentDataPlaneTests(BackendFixture backend) { _backend = backend; } private async Task<(AgentHarness Agent, ProbeSimulator Probe, LineTask Udp)> LineAsync() { 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.QuicPorts[0])); return (agent, probe, set.Tasks.Single(t => t.Kind == "udp_echo")); } [Fact] public async Task TcpDataPlane_ClosesConnectionsWithoutAValidFirstFrame() { (AgentHarness agent, ProbeSimulator probe, _) = await LineAsync(); await using (agent) await using (probe) { 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, ProbeSimulator probe, LineTask udp) = await LineAsync(); await using (agent) await using (probe) { long before = agent.App.Counters.DroppedInvalid; await probe.SendRawUdpAsync([1, 2, 3]); await probe.SendRawUdpAsync(new byte[64]); await probe.SendForgedEchoAsync(udp, 1); await BackendFixture.EventuallyAsync(() => Task.FromResult(agent.App.Counters.DroppedInvalid - before >= 3), because: "garbage and forged packets are counted"); // 重放:同一个序号发两次,只有第一次被接受 await probe.SendEchoAsync(udp, 500); await BackendFixture.EventuallyAsync(() => Task.FromResult(agent.App.Counters.PacketsOut >= 1)); long dropped = agent.App.Counters.DroppedInvalid; await probe.SendEchoAsync(udp, 500); await BackendFixture.EventuallyAsync(() => Task.FromResult(agent.App.Counters.DroppedInvalid > dropped), because: "a replayed packet is dropped"); } } private static async Task ReadOrResetAsync(Stream stream, byte[] buffer) { try { return await stream.ReadAsync(buffer).AsTask().WaitAsync(TimeSpan.FromSeconds(10)); } catch (IOException) { return 0; } } }