using System.Buffers.Binary;
using System.Security.Cryptography;
namespace MoShiro.Protocol;
public enum UdpPacketType : byte
{
EchoRequest = 1,
EchoReply = 2,
/// 单向数据(udp_train),以及 Probe 为了保持 NAT 映射和让 Agent 学到源地址而发的探活包。
Data = 3,
}
public readonly record struct UdpHeader(UdpPacketType Type, long SessionId, uint StreamId, uint Seq, long SendTsUs);
///
/// 数据面的 UDP 包(Fleet 之外的线格式):
/// magic(2) version(1) type(1) session_id(8) stream_id(4) seq(4) send_ts_us(8) | padding | mac(16),
/// mac = HMAC-SHA256(session_key, 前面全部字节) 截断到 16 字节。所有整数小端。
/// 接收方对不合法的包静默丢弃并计数,不回复(防放大)。
///
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;
/// 检查长度、magic、version、type 并解析头部(不校验 MAC)。
public static bool TryParseHeader(ReadOnlySpan 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;
}
/// 写入整个包(长度就是 的长度),填充为零,末尾是 MAC。
public static void Write(Span destination, in UdpHeader header, ReadOnlySpan 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);
}
/// 重新计算并写入末尾的 MAC(回复包复用请求包的缓冲区时使用)。
public static void Seal(Span packet, ReadOnlySpan key)
{
Span full = stackalloc byte[32];
HMACSHA256.HashData(key, packet[..^MacBytes], full);
full[..MacBytes].CopyTo(packet[^MacBytes..]);
}
public static bool VerifyMac(ReadOnlySpan packet, ReadOnlySpan key)
{
if (packet.Length < MinBytes)
{
return false;
}
Span full = stackalloc byte[32];
HMACSHA256.HashData(key, packet[..^MacBytes], full);
return CryptographicOperations.FixedTimeEquals(full[..MacBytes], packet[^MacBytes..]);
}
}