feat: declare every protocol method's direction and enforce it in the router; add the zero-trust design [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful
ci/woodpecker/push/main-amd64-build Pipeline was successful
Methods, roles and binary channels are declared in one table per plane (node and peer). RpcRouter checks direction, kind and role when handlers are registered, when messages arrive (fleet_protocol_violation, disconnect after repeated violations) and before sending. NodeConnection is split by responsibility.
This commit is contained in:
1 parent
ee22309e65
commit
1ff847e45a
36 files changed
+1799
-639
No files matched your search
@@ -34,6 +34,9 @@ public sealed class RpcSessionOptions
|
||||
|
||||
/// <summary>待处理的入站请求上限;超过时接收循环等待(背压)。</summary>
|
||||
public int InboundQueue { get; set; } = 256;
|
||||
|
||||
/// <summary>对端违反协议(方法没有声明、方向、类型或角色不对)的次数上限;超过就断开会话。</summary>
|
||||
public int MaxViolations { get; set; } = 20;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,6 +56,8 @@ public sealed class RpcSession<TContext> : IAsyncDisposable
|
||||
private readonly CancellationTokenSource _stop = new();
|
||||
private long _nextId;
|
||||
private long _lastReceive;
|
||||
private int _violations;
|
||||
private bool _violated;
|
||||
|
||||
public RpcSession(IRpcTransport transport, RpcRouter<TContext> router, TContext context, TimeProvider time, RpcSessionOptions? options = null)
|
||||
{
|
||||
@@ -81,7 +86,7 @@ public sealed class RpcSession<TContext> : IAsyncDisposable
|
||||
while (!linked.IsCancellationRequested)
|
||||
{
|
||||
RpcFrame? frame = await _transport.ReceiveAsync(linked.Token);
|
||||
if (frame is null)
|
||||
if (frame is null || Volatile.Read(ref _violated))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -184,8 +189,16 @@ public sealed class RpcSession<TContext> : IAsyncDisposable
|
||||
public ValueTask NotifyAsync<TParams>(string method, TParams parameters, JsonTypeInfo<TParams> paramsInfo, CancellationToken ct) =>
|
||||
SendAsync(RpcMessage.Notification(method, parameters, paramsInfo), ct);
|
||||
|
||||
public ValueTask SendAsync(RpcMessage message, CancellationToken ct) =>
|
||||
_transport.SendAsync(new RpcFrame(FrameKind.Text, JsonSerializer.SerializeToUtf8Bytes(message, FleetRpcContext.Default.RpcMessage)), ct);
|
||||
public ValueTask SendAsync(RpcMessage message, CancellationToken ct)
|
||||
{
|
||||
if (message.Method is not null)
|
||||
{
|
||||
// 请求和通知只能发本侧有资格发起的方法(响应没有方法名,不检查)
|
||||
_router.CheckSend(message.Method, message.Id is null ? MethodKind.Notification : MethodKind.Request);
|
||||
}
|
||||
|
||||
return _transport.SendAsync(new RpcFrame(FrameKind.Text, JsonSerializer.SerializeToUtf8Bytes(message, FleetRpcContext.Default.RpcMessage)), ct);
|
||||
}
|
||||
|
||||
public ValueTask SendBinaryAsync(ReadOnlyMemory<byte> frame, CancellationToken ct) =>
|
||||
_transport.SendAsync(new RpcFrame(FrameKind.Binary, frame), ct);
|
||||
@@ -205,10 +218,18 @@ public sealed class RpcSession<TContext> : IAsyncDisposable
|
||||
{
|
||||
await foreach (RpcMessage message in _inbound.Reader.ReadAllAsync(ct))
|
||||
{
|
||||
RpcMessage? response = await _router.DispatchAsync(_context, message, ct);
|
||||
if (response is not null)
|
||||
RpcDispatch outcome = await _router.DispatchCheckedAsync(_context, message, ct);
|
||||
if (outcome.Response is not null)
|
||||
{
|
||||
await SendAsync(response, ct);
|
||||
await SendAsync(outcome.Response, ct);
|
||||
}
|
||||
|
||||
if (outcome.Violation is not null && ++_violations > _options.MaxViolations)
|
||||
{
|
||||
// 只关闭传输,不取消接收:接收循环在下一次读到帧或对端确认关闭时正常结束
|
||||
Volatile.Write(ref _violated, true);
|
||||
await _transport.CloseAsync("protocol violations", CancellationToken.None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user