refactor!: rename the project from Monitor to MoShiro

Namespaces, assemblies, projects and directories, the solution, binaries,
images, systemd units, config paths, environment variables, protocol method
names, the media type and the raw chunk magic all move to MoShiro/moshiro.
Existing data and deployments are not compatible and must be recreated.
Also fix the architecture tests after the solution layout change and make
the update tests independent of their order.
This commit is contained in:
justme committed 2026-10-07 01:49:04 +08:00
1 parent 17b35223aa
commit 83e5120377
309 files changed
+1505 -1499

No files matched your search

@@ -0,0 +1,132 @@
using System.Buffers.Binary;
using System.Security.Cryptography;
using Fleet.Core;
namespace MoShiro.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 MoShiro.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..]);
}
}