chore: initial project import
This commit is contained in:
commit
3fc4e5ca53
347 files changed
+56172
No files matched your search
@@ -0,0 +1,132 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Security.Cryptography;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// 数据面的 TCP 连接(Fleet 之外的线格式)。连接都由 Probe 发起;TLS 是可选的(ClientHello 以 0x16 开头,
|
||||
/// 明文首帧以 "MN" 开头,所以同一个端口可以区分)。首帧:
|
||||
/// <c>"MN"(2) version(1) session_id(8) stream_id(4) nonce(16) mac(16)</c>,认证失败直接关闭连接;
|
||||
/// 之后是 <c>VarInt 长度 | 正文</c> 的消息,回显类消息的正文是 <c>seq(u32) send_ts_us(i64) | padding</c>。
|
||||
/// </summary>
|
||||
public static class TcpWire
|
||||
{
|
||||
public const int AuthBytes = 2 + 1 + 8 + 4 + 16 + 16;
|
||||
public const int NonceBytes = 16;
|
||||
public const int MinEchoBody = 12;
|
||||
public const int MaxMessageBytes = 1 << 20;
|
||||
|
||||
public static void WriteAuth(Span<byte> destination, long sessionId, uint streamId, ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> key)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(destination, UdpPacket.Magic);
|
||||
destination[2] = UdpPacket.Version;
|
||||
BinaryPrimitives.WriteInt64LittleEndian(destination[3..], sessionId);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(destination[11..], streamId);
|
||||
nonce[..NonceBytes].CopyTo(destination[15..]);
|
||||
Span<byte> full = stackalloc byte[32];
|
||||
HMACSHA256.HashData(key, destination[..(AuthBytes - 16)], full);
|
||||
full[..16].CopyTo(destination[(AuthBytes - 16)..]);
|
||||
}
|
||||
|
||||
/// <summary>解析首帧(MAC 要在查到会话密钥之后用 <see cref="VerifyAuth"/> 校验)。</summary>
|
||||
public static bool TryParseAuth(ReadOnlySpan<byte> frame, out long sessionId, out uint streamId, out ReadOnlySpan<byte> nonce)
|
||||
{
|
||||
sessionId = 0;
|
||||
streamId = 0;
|
||||
nonce = default;
|
||||
if (frame.Length != AuthBytes || BinaryPrimitives.ReadUInt16LittleEndian(frame) != UdpPacket.Magic || frame[2] != UdpPacket.Version)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
sessionId = BinaryPrimitives.ReadInt64LittleEndian(frame[3..]);
|
||||
streamId = BinaryPrimitives.ReadUInt32LittleEndian(frame[11..]);
|
||||
nonce = frame.Slice(15, NonceBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool VerifyAuth(ReadOnlySpan<byte> frame, ReadOnlySpan<byte> key)
|
||||
{
|
||||
Span<byte> full = stackalloc byte[32];
|
||||
HMACSHA256.HashData(key, frame[..(AuthBytes - 16)], full);
|
||||
return CryptographicOperations.FixedTimeEquals(full[..16], frame[(AuthBytes - 16)..]);
|
||||
}
|
||||
|
||||
/// <summary>回显消息正文:<c>seq | send_ts_us | padding</c>,总长度 <paramref name="size"/>。</summary>
|
||||
public static void WriteEchoBody(Span<byte> destination, uint seq, long sendTsUs)
|
||||
{
|
||||
destination.Clear();
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(destination, seq);
|
||||
BinaryPrimitives.WriteInt64LittleEndian(destination[4..], sendTsUs);
|
||||
}
|
||||
|
||||
public static bool TryReadEchoBody(ReadOnlySpan<byte> body, out uint seq, out long sendTsUs)
|
||||
{
|
||||
seq = 0;
|
||||
sendTsUs = 0;
|
||||
if (body.Length < MinEchoBody)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
seq = BinaryPrimitives.ReadUInt32LittleEndian(body);
|
||||
sendTsUs = BinaryPrimitives.ReadInt64LittleEndian(body[4..]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>读取一条 <c>VarInt 长度 | 正文</c> 消息;连接结束或长度非法时返回 null。</summary>
|
||||
public static async ValueTask<byte[]?> ReadMessageAsync(Stream stream, CancellationToken ct)
|
||||
{
|
||||
ulong length = 0;
|
||||
int shift = 0;
|
||||
byte[] one = new byte[1];
|
||||
for (int i = 0; i < VarInt.MaxBytes; i++)
|
||||
{
|
||||
if (await stream.ReadAsync(one, ct) == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
length |= (ulong)(one[0] & 0x7F) << shift;
|
||||
if ((one[0] & 0x80) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
shift += 7;
|
||||
if (i == VarInt.MaxBytes - 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (length > MaxMessageBytes)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] body = new byte[length];
|
||||
int read = 0;
|
||||
while (read < body.Length)
|
||||
{
|
||||
int n = await stream.ReadAsync(body.AsMemory(read), ct);
|
||||
if (n == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
read += n;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
public static async ValueTask WriteMessageAsync(Stream stream, ReadOnlyMemory<byte> body, CancellationToken ct)
|
||||
{
|
||||
byte[] prefix = new byte[VarInt.MaxBytes];
|
||||
int n = VarInt.Write(prefix, (ulong)body.Length);
|
||||
await stream.WriteAsync(prefix.AsMemory(0, n), ct);
|
||||
await stream.WriteAsync(body, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
|
||||
public enum UdpPacketType : byte
|
||||
{
|
||||
EchoRequest = 1,
|
||||
EchoReply = 2,
|
||||
|
||||
/// <summary>单向数据(udp_train),以及 Probe 为了保持 NAT 映射和让 Agent 学到源地址而发的探活包。</summary>
|
||||
Data = 3,
|
||||
}
|
||||
|
||||
public readonly record struct UdpHeader(UdpPacketType Type, long SessionId, uint StreamId, uint Seq, long SendTsUs);
|
||||
|
||||
/// <summary>
|
||||
/// 数据面的 UDP 包(Fleet 之外的线格式):
|
||||
/// <c>magic(2) version(1) type(1) session_id(8) stream_id(4) seq(4) send_ts_us(8) | padding | mac(16)</c>,
|
||||
/// mac = HMAC-SHA256(session_key, 前面全部字节) 截断到 16 字节。所有整数小端。
|
||||
/// 接收方对不合法的包静默丢弃并计数,不回复(防放大)。
|
||||
/// </summary>
|
||||
public static class UdpPacket
|
||||
{
|
||||
public const ushort Magic = 0x4D4E; // "MN"
|
||||
public const byte Version = 1;
|
||||
public const int HeaderBytes = 28;
|
||||
public const int MacBytes = 16;
|
||||
public const int MinBytes = HeaderBytes + MacBytes;
|
||||
public const int MaxBytes = 1472;
|
||||
|
||||
/// <summary>检查长度、magic、version、type 并解析头部(不校验 MAC)。</summary>
|
||||
public static bool TryParseHeader(ReadOnlySpan<byte> packet, out UdpHeader header)
|
||||
{
|
||||
header = default;
|
||||
if (packet.Length is < MinBytes or > MaxBytes
|
||||
|| BinaryPrimitives.ReadUInt16LittleEndian(packet) != Magic
|
||||
|| packet[2] != Version
|
||||
|| packet[3] is < (byte)UdpPacketType.EchoRequest or > (byte)UdpPacketType.Data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
header = new UdpHeader(
|
||||
(UdpPacketType)packet[3],
|
||||
BinaryPrimitives.ReadInt64LittleEndian(packet[4..]),
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(packet[12..]),
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(packet[16..]),
|
||||
BinaryPrimitives.ReadInt64LittleEndian(packet[20..]));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>写入整个包(长度就是 <paramref name="destination"/> 的长度),填充为零,末尾是 MAC。</summary>
|
||||
public static void Write(Span<byte> destination, in UdpHeader header, ReadOnlySpan<byte> key)
|
||||
{
|
||||
if (destination.Length is < MinBytes or > MaxBytes)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(destination), "packet size must be between 44 and 1472 bytes");
|
||||
}
|
||||
|
||||
destination.Clear();
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(destination, Magic);
|
||||
destination[2] = Version;
|
||||
destination[3] = (byte)header.Type;
|
||||
BinaryPrimitives.WriteInt64LittleEndian(destination[4..], header.SessionId);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(destination[12..], header.StreamId);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(destination[16..], header.Seq);
|
||||
BinaryPrimitives.WriteInt64LittleEndian(destination[20..], header.SendTsUs);
|
||||
Seal(destination, key);
|
||||
}
|
||||
|
||||
/// <summary>重新计算并写入末尾的 MAC(回复包复用请求包的缓冲区时使用)。</summary>
|
||||
public static void Seal(Span<byte> packet, ReadOnlySpan<byte> key)
|
||||
{
|
||||
Span<byte> full = stackalloc byte[32];
|
||||
HMACSHA256.HashData(key, packet[..^MacBytes], full);
|
||||
full[..MacBytes].CopyTo(packet[^MacBytes..]);
|
||||
}
|
||||
|
||||
public static bool VerifyMac(ReadOnlySpan<byte> packet, ReadOnlySpan<byte> key)
|
||||
{
|
||||
if (packet.Length < MinBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Span<byte> full = stackalloc byte[32];
|
||||
HMACSHA256.HashData(key, packet[..^MacBytes], full);
|
||||
return CryptographicOperations.FixedTimeEquals(full[..MacBytes], packet[^MacBytes..]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user