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
@@ -33,7 +33,7 @@ public sealed class AgentApp : IDisposable
|
||||
private readonly Outbox _outbox = new(64 << 20);
|
||||
private readonly RestartUpdater? _update;
|
||||
private readonly BackendUplink _uplink;
|
||||
private readonly UdpMux _udp;
|
||||
private readonly QuicDatagramTransport _datagrams;
|
||||
private readonly TcpAcceptor _tcp;
|
||||
private readonly PeerListener _peers;
|
||||
private readonly PeerHub _hub;
|
||||
@@ -62,7 +62,7 @@ public sealed class AgentApp : IDisposable
|
||||
|
||||
_ipv6Supported = bind.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6;
|
||||
_admission = new AgentAdmission(_streams);
|
||||
_udp = new UdpMux(_sessions, _admission, _clock, _counters.Data, log, bind, config.Listen.UdpPorts);
|
||||
_datagrams = new QuicDatagramTransport(_sessions, _admission, _clock, _counters.Data, log, bind, config.Listen.EffectiveQuicPorts, identity.Certificate);
|
||||
// 预握手敲门:控制端口和数据端口共用一个关卡(限速和去重一起算);名册就是期望状态里的 peers
|
||||
_trust = TrustAnchor.Load(config.Fleet.IdentityDir, log);
|
||||
_knock = new KnockGate(identity.PeerIdentity, RosterKeys, time);
|
||||
@@ -74,7 +74,7 @@ public sealed class AgentApp : IDisposable
|
||||
Clock = _clock,
|
||||
Log = log,
|
||||
Session = () => null, // 每个 Probe 会话各用自己绑定的环境(BlockEnv.WithSession)
|
||||
Udp = _udp,
|
||||
Datagrams = _datagrams,
|
||||
TcpListen = _tcp,
|
||||
Admission = _admission,
|
||||
Counters = _counters.Data,
|
||||
@@ -83,14 +83,14 @@ public sealed class AgentApp : IDisposable
|
||||
};
|
||||
var chunks = new ChunkAssembler(_streams, _clock, _counters, _outbox, identity.NodeId, time, log);
|
||||
var targets = new TargetRunners(chunks, identity.NodeId, _clock, time, log);
|
||||
_tasks = new TaskManager(CreateGuard, _streams, env, targets, () => _udp.BoundPorts, () => _tcp.BoundPorts, _trust);
|
||||
_tasks = new TaskManager(CreateGuard, _streams, env, targets, () => _datagrams.BoundPorts, () => _tcp.BoundPorts, _trust);
|
||||
_hub = new PeerHub(_sessions, _clock, chunks, _tasks, _uplink, time, log);
|
||||
_tests = new TestRunner(_tasks, _sessions, _streams, env, _uplink, config.Limits.MaxConcurrentTests, transport => transport == "udp" ? _udp.BoundPorts : _tcp.BoundPorts, time, log);
|
||||
_tests = new TestRunner(_tasks, _sessions, _streams, env, _uplink, config.Limits.MaxConcurrentTests, transport => transport == "quic_datagram" ? _datagrams.BoundPorts : _tcp.BoundPorts, time, log);
|
||||
|
||||
_tasks.OnLineTasksChanged = (probe, set) => _ = _hub.PushToAsync(probe, set).AsTask();
|
||||
_tasks.OnPeersChanged = _hub.CloseNotIn;
|
||||
_hub.OnProbeLost = probe => _tests.ProbeLost(probe);
|
||||
_admission.OnEndpointChanged = probe => _ = _uplink.EmitEventAsync(AgentEventKinds.EndpointChanged, probe.UdpEndpoint?.ToString()).AsTask();
|
||||
_admission.OnEndpointChanged = probe => _ = _uplink.EmitEventAsync(AgentEventKinds.EndpointChanged, probe.DatagramRemoteEndPoint?.ToString()).AsTask();
|
||||
|
||||
_peers = new PeerListener(
|
||||
new PeerListenerOptions
|
||||
@@ -113,13 +113,13 @@ public sealed class AgentApp : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_udp.Dispose();
|
||||
_datagrams.Dispose();
|
||||
_ownedRestart?.Dispose();
|
||||
}
|
||||
|
||||
internal StreamRegistry Streams => _streams;
|
||||
|
||||
public IReadOnlyList<int> UdpPorts => _udp.BoundPorts;
|
||||
public IReadOnlyList<int> QuicPorts => _datagrams.BoundPorts;
|
||||
|
||||
public IReadOnlyList<int> TcpPorts => _tcp.BoundPorts;
|
||||
|
||||
@@ -146,23 +146,23 @@ public sealed class AgentApp : IDisposable
|
||||
_log.Info("moshiro agent starting", MoShiro.Protocol.BuildInfo.Fields);
|
||||
using CancellationTokenSource linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
// 数据面和控制端口先开始监听,再连接 Backend:hello 里上报的是实际端口
|
||||
_udp.Bind();
|
||||
_datagrams.Bind();
|
||||
_tcp.Bind();
|
||||
Task udp = _udp.RunAsync(linked.Token);
|
||||
Task quic = _datagrams.RunAsync(linked.Token);
|
||||
Task tcp = _tcp.RunAsync(linked.Token);
|
||||
Task peers = _peers.RunAsync(linked.Token);
|
||||
await Task.WhenAny(_peers.Started.Task, peers);
|
||||
if (peers.IsFaulted)
|
||||
{
|
||||
await linked.CancelAsync();
|
||||
await Task.WhenAll(udp, tcp, peers); // 抛出监听失败的原因(端口被占用等)
|
||||
await Task.WhenAll(quic, tcp, peers); // 抛出监听失败的原因(端口被占用等)
|
||||
}
|
||||
|
||||
_ready.TrySetResult();
|
||||
_log.Info("agent listening", ("control", ControlPort), ("udp", string.Join(',', UdpPorts)), ("tcp", string.Join(',', TcpPorts)));
|
||||
_log.Info("agent listening", ("control", ControlPort), ("quic", string.Join(',', QuicPorts)), ("tcp", string.Join(',', TcpPorts)));
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(udp, tcp, peers, _link.RunAsync(linked.Token), ReportHostAsync(linked.Token));
|
||||
await Task.WhenAll(quic, tcp, peers, _link.RunAsync(linked.Token), ReportHostAsync(linked.Token));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -203,7 +203,7 @@ public sealed class AgentApp : IDisposable
|
||||
MaxTotalPps = _config.Limits.MaxTotalPps,
|
||||
AllowedTargetCidrs = _config.Limits.AllowedTargetCidrs,
|
||||
MaxConcurrentTests = _config.Limits.MaxConcurrentTests,
|
||||
AllowedUdpPorts = _udp.BoundPorts,
|
||||
AllowedQuicPorts = _datagrams.BoundPorts,
|
||||
AllowedTcpPorts = _tcp.BoundPorts,
|
||||
},
|
||||
BlockCatalog.AgentKinds);
|
||||
@@ -310,7 +310,8 @@ public sealed class AgentApp : IDisposable
|
||||
Platform = MoShiro.Measurement.PlatformDetector.Detect(),
|
||||
TlsPublicKey = Convert.ToBase64String(_identity.Certificate.PublicKey.ExportSubjectPublicKeyInfo()),
|
||||
ProbeTcpPort = ControlPort,
|
||||
ProbeUdpPorts = [.. UdpPorts],
|
||||
ProbeQuicPorts = [.. QuicPorts],
|
||||
LegacyProbeUdpPorts = [.. QuicPorts],
|
||||
LocalLimits = new LocalLimits
|
||||
{
|
||||
MaxTotalMbps = _config.Limits.MaxTotalMbps,
|
||||
|
||||
@@ -51,8 +51,15 @@ public sealed class ListenSection
|
||||
[JsonPropertyName("control_port")]
|
||||
public int ControlPort { get; set; } = 7443;
|
||||
|
||||
[JsonPropertyName("quic_ports")]
|
||||
public List<int> QuicPorts { get; set; } = [7443];
|
||||
|
||||
/// <summary>Legacy config key accepted while deployments migrate to QUIC Datagram.</summary>
|
||||
[JsonPropertyName("udp_ports")]
|
||||
public List<int> UdpPorts { get; set; } = [7443];
|
||||
public List<int>? LegacyUdpPorts { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public IReadOnlyList<int> EffectiveQuicPorts => LegacyUdpPorts ?? QuicPorts;
|
||||
|
||||
[JsonPropertyName("tcp_ports")]
|
||||
public List<int> TcpPorts { get; set; } = [7444];
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed class ProbeSession : DataSession
|
||||
{
|
||||
private readonly ConcurrentDictionary<uint, ReplayWindow> _windows = new();
|
||||
private readonly ConcurrentDictionary<ulong, byte> _nonces = new();
|
||||
private SocketAddress? _udpSource;
|
||||
private SocketAddress? _datagramSource;
|
||||
|
||||
public ProbeSession(PeerSessionInfo info)
|
||||
: base(info.Credentials.SessionIdValue, info.Credentials.Key)
|
||||
@@ -71,11 +71,11 @@ public sealed class ProbeSession : DataSession
|
||||
public RpcSession<PeerContext>? Rpc { get; set; }
|
||||
|
||||
/// <summary>最近一次认证通过的 UDP 包的源地址(Agent 把单向数据发往这里)。</summary>
|
||||
public override IPEndPoint? UdpEndpoint
|
||||
public override IPEndPoint? DatagramRemoteEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
SocketAddress? source = Volatile.Read(ref _udpSource);
|
||||
SocketAddress? source = Volatile.Read(ref _datagramSource);
|
||||
return source is null ? null : (IPEndPoint)new IPEndPoint(source.Family == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any, 0).Create(source);
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ public sealed class ProbeSession : DataSession
|
||||
/// <summary>记录 UDP 源地址;NAT 映射变化后地址变了返回 true(上报 endpoint_changed)。第一次看到地址不算变化。</summary>
|
||||
public bool UpdateSource(SocketAddress source)
|
||||
{
|
||||
SocketAddress? previous = Volatile.Read(ref _udpSource);
|
||||
SocketAddress? previous = Volatile.Read(ref _datagramSource);
|
||||
if (previous is not null && previous.Equals(source))
|
||||
{
|
||||
return false;
|
||||
@@ -96,7 +96,7 @@ public sealed class ProbeSession : DataSession
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
Volatile.Write(ref _udpSource, copy);
|
||||
Volatile.Write(ref _datagramSource, copy);
|
||||
return previous is not null;
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ public sealed class AgentAdmission : IAdmission
|
||||
/// <summary>NAT 映射变化,源地址变了:上报 <c>endpoint_changed</c>。</summary>
|
||||
public Action<ProbeSession>? OnEndpointChanged { get; set; }
|
||||
|
||||
public Admission AdmitPacket(DataSession session, in UdpHeader header, SocketAddress source, long nowUs)
|
||||
public Admission AdmitDatagram(DataSession session, in MeasurementDatagramHeader header, SocketAddress source, long nowUs)
|
||||
{
|
||||
if (session is not ProbeSession probe || !_streams.TryGet(header.StreamId, out StreamInfo? stream) || !ReferenceEquals(stream.Owner, probe) || !probe.AcceptSequence(header.StreamId, header.Seq))
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class TaskManager
|
||||
private readonly StreamRegistry _streams;
|
||||
private readonly BlockEnv _env;
|
||||
private readonly TargetRunners _targets;
|
||||
private readonly Func<IReadOnlyList<int>> _udpPorts;
|
||||
private readonly Func<IReadOnlyList<int>> _quicPorts;
|
||||
private readonly Func<IReadOnlyList<int>> _tcpPorts;
|
||||
private readonly TrustAnchor _trust;
|
||||
private readonly Dictionary<ProbeSession, Dictionary<string, LineEntry>> _sessionLines = [];
|
||||
@@ -33,14 +33,14 @@ public sealed class TaskManager
|
||||
private long _version;
|
||||
private BudgetGuard? _guard;
|
||||
|
||||
public TaskManager(Func<BudgetGuard> guardFactory, StreamRegistry streams, BlockEnv env, TargetRunners targets, Func<IReadOnlyList<int>> udpPorts, Func<IReadOnlyList<int>> tcpPorts, TrustAnchor? trust = null)
|
||||
public TaskManager(Func<BudgetGuard> guardFactory, StreamRegistry streams, BlockEnv env, TargetRunners targets, Func<IReadOnlyList<int>> quicPorts, Func<IReadOnlyList<int>> tcpPorts, TrustAnchor? trust = null)
|
||||
{
|
||||
_trust = trust ?? TrustAnchor.None;
|
||||
_guardFactory = guardFactory;
|
||||
_streams = streams;
|
||||
_env = env;
|
||||
_targets = targets;
|
||||
_udpPorts = udpPorts;
|
||||
_quicPorts = quicPorts;
|
||||
_tcpPorts = tcpPorts;
|
||||
}
|
||||
|
||||
@@ -229,7 +229,8 @@ public sealed class TaskManager
|
||||
{
|
||||
Version = _version,
|
||||
Tasks = [.. entries.Values.Select(e => e.Task)],
|
||||
UdpPorts = [.. _udpPorts()],
|
||||
QuicPorts = [.. _quicPorts()],
|
||||
LegacyUdpPorts = [.. _quicPorts()],
|
||||
TcpPorts = [.. _tcpPorts()],
|
||||
};
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ public sealed class TestRunner
|
||||
|
||||
// 按需测试的数据端口由 Agent 决定:Backend 给的端口如果不是本机监听的端口(模板里是出厂默认值,每个 Agent 可以改,
|
||||
// 而 Backend 并不知道 TCP 数据端口),就用本机监听的第一个端口。块头里记录的是实际用的端口。
|
||||
IReadOnlyList<int> ports = _ports(kind.Blocks.Transport == "udp" ? "udp" : "tcp");
|
||||
IReadOnlyList<int> ports = _ports(kind.Blocks.Transport == "quic_datagram" ? "quic_datagram" : "tcp");
|
||||
bool listening = phase.Params.TryGetValue("port", out System.Text.Json.JsonElement requested) && requested.TryGetInt32(out int port) && ports.Contains(port);
|
||||
if (!listening)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user