shared: add independent IPv4 and IPv6 collection [CI]
ci/woodpecker/pr/verify Pipeline was successful
ci/woodpecker/pr/web-build Pipeline was successful

This commit is contained in:
justme committed 2026-10-07 11:44:01 +08:00
1 parent c974a7dddb
commit 0eda0600cc
60 files changed
+699 -137

No files matched your search

@@ -69,6 +69,7 @@ public static class ChunkFinalizer
Phase = source.Phase,
AgentId = agent,
ProbeId = source.ProbeId,
IpVersion = source.IpVersion,
Target = null,
Params = new Dictionary<string, JsonElement>(resolvedParams),
Port = port,
@@ -39,7 +39,7 @@ public sealed class TcpLink : IDisposable
/// </summary>
public static async Task<TcpLink> OpenAsync(BlockEnv env, int port, bool tls, uint streamId, bool sendAuth, CancellationToken ct)
{
var client = new TcpClient { NoDelay = true };
var client = new TcpClient(env.PeerAddress!.AddressFamily) { NoDelay = true };
try
{
long start = env.Clock.NowUs();
@@ -153,6 +153,11 @@ public sealed class TcpAcceptor
foreach (int port in _ports)
{
var listener = new TcpListener(_bind, port);
if (_bind.Equals(IPAddress.IPv6Any))
{
listener.Server.DualMode = true;
}
listener.Start(128);
_listeners.Add(listener);
bound.Add(((IPEndPoint)listener.LocalEndpoint).Port);
@@ -0,0 +1,13 @@
namespace MoShiro.Protocol;
/// <summary>测量路径的地址族。旧契约缺省为 IPv4;IPv6 使用独立的线路和序列。</summary>
public static class IpVersions
{
public const string V4 = "ipv4";
public const string V6 = "ipv6";
public static bool IsValid(string? value) => value is V4 or V6;
public static string LineSeries(string probeId, string ipVersion) =>
RawChunkHeader.LinePrefix + probeId + (ipVersion == V6 ? ":ipv6" : "");
}
@@ -58,6 +58,10 @@ public sealed class StreamHeader
/// <summary>块头(JSON,自描述):列定义写在块里,新增列或新增 Kind 不需要升级 container_version。</summary>
public sealed class RawChunkHeader
{
[JsonPropertyName("ip_version")]
[JsonPropertyOrder(1)]
public string IpVersion { get; set; } = IpVersions.V4;
[JsonPropertyName("chunk_id")]
public ChunkId ChunkId { get; set; }
@@ -113,10 +117,10 @@ public sealed class RawChunkHeader
/// <summary>
/// "line:&lt;probe_id&gt;" 或 "target:&lt;task_id&gt;":存储和分析用的序列标识。
/// 一条线路是一对(Probe,Agent):同一个 Agent 上每个 Probe 的数据各是一个序列,互不混合。
/// 同一个 Agent 上每个 Probe 的每种地址族各是一个序列,IPv6 追加 :ipv6,互不混合。
/// </summary>
[JsonIgnore]
public string Series => TaskId is { } id && Target is not null ? "target:" + id.Value : LinePrefix + (ProbeId?.Value ?? "");
public string Series => TaskId is { } id && Target is not null ? "target:" + id.Value : IpVersions.LineSeries(ProbeId?.Value ?? "", IpVersion);
public const string LinePrefix = "line:";
}
@@ -61,6 +61,9 @@ public sealed class TestPhase
public sealed class TestRunParams
{
[JsonPropertyName("ip_version")]
public string IpVersion { get; set; } = IpVersions.V4;
[JsonPropertyName("test_id")]
public TestId TestId { get; set; }
@@ -127,6 +130,9 @@ public sealed class AgentEventParams
public sealed class AgentEndpoint
{
[JsonPropertyName("ipv6_host")]
public string? Ipv6Host { get; set; }
[JsonPropertyName("agent_id")]
public NodeId AgentId { get; set; }
@@ -289,6 +295,10 @@ public sealed class ProbeHelloMeta
/// <summary><c>fleet.hello</c> 的附加字段。</summary>
public sealed class AgentHelloMeta
{
/// <summary>本机是否启用了 IPv6 监听。旧 Agent 缺省为 false,面板不会给它安排 IPv6 会话。</summary>
[JsonPropertyName("ipv6_supported")]
public bool Ipv6Supported { get; set; }
[JsonPropertyName("platform")]
public PlatformInfo? Platform { get; set; }
@@ -353,6 +363,9 @@ public sealed class HostSnapshot
/// <summary>Agent 眼中的一个已连接 Probe。</summary>
public sealed class ConnectedProbe
{
[JsonPropertyName("ip_version")]
public string IpVersion { get; set; } = IpVersions.V4;
[JsonPropertyName("id")]
public ProbeId Id { get; set; }
@@ -8,6 +8,21 @@ namespace MoShiro.Protocol.Tests;
public sealed class RawChunkCodecTests
{
[Fact]
public void Header_LegacyDefaultsToIpv4_AndIpv6RoundTripsToAnIndependentSeries()
{
RawChunkHeader legacy = System.Text.Json.JsonSerializer.Deserialize("{\"probe_id\":\"prb_legacy\"}", MoShiroJsonContext.Default.RawChunkHeader)!;
Assert.Equal(IpVersions.V4, legacy.IpVersion);
Assert.Equal("line:prb_legacy", legacy.Series);
RawChunk chunk = SampleChunk();
chunk.Header.ProbeId = new ProbeId("prb_dual");
chunk.Header.IpVersion = IpVersions.V6;
RawChunk decoded = RawChunkCodec.Decode(RawChunkCodec.Encode(chunk)).Value;
Assert.Equal("ipv6", decoded.Header.IpVersion);
Assert.Equal("line:prb_dual:ipv6", decoded.Header.Series);
Assert.Equal(chunk.Find("msg")!.Count, decoded.Find("msg")!.Count);
}
private static readonly NodeId s_agent = new("agt_test");
private static RawChunk SampleChunk()