6ec7cd8146
合并 proto 契约 + buf 工具链任务到 main(已含 apierr + idgen + CONVENTIONS.md): - server/proto/pangolin/agent/v1/agent.proto: AgentService 6 个 RPC(Enroll/Register/ Heartbeat/Subscribe/Ack/ReportUsage)及全量消息类型,隐私红线注释保留 - server/buf.yaml: v2 格式,STANDARD lint + FILE breaking 规则 - server/buf.gen.yaml: remote protoc-gen-go v1.36.4 + protoc-gen-go-grpc v1.5.1 插件 - server/internal/pb/: 生成的 agent.pb.go + agent_grpc.pb.go 提交入库 - server/Makefile: 新增 proto / proto-lint / proto-breaking 目标 - server/tools.go: 新增 buf v1.70.0、protoc-gen-go、protoc-gen-go-grpc 工具依赖 - server/go.mod / go.sum: 合并 buf 工具链依赖;github.com/google/uuid 保留为 direct 依赖(idgen 直接 import);go 版本升至 1.25.10 冲突解决:两侧均修改 go.mod,以 proto 分支(超集)为基础,将 google/uuid 移至 直接依赖 require 块,保留 apierr/idgen 的已有文件不变。 go build ./... ✓ | go test ./internal/apierr/... ./internal/idgen/... ✓ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
220 lines
9.4 KiB
Protocol Buffer
220 lines
9.4 KiB
Protocol Buffer
// agent.proto — Control-plane ↔ Agent 通信契约
|
||
//
|
||
// 安全红线(禁止违反):
|
||
// 1. 所有消息中只使用 dp_uuid 标识数据通道,严禁出现 user_id / email 等用户身份字段。
|
||
// 2. ReportUsage 不含目的地址、DNS 查询等任何可还原用户行为的字段(无日志口径)。
|
||
// 3. 本文件冻结后,任何 breaking change 须先通过 buf breaking 审查。
|
||
|
||
syntax = "proto3";
|
||
|
||
package pangolin.agent.v1;
|
||
|
||
import "google/protobuf/timestamp.proto";
|
||
|
||
option go_package = "github.com/wangjia/pangolin/server/internal/pb/pangolin/agent/v1;agentv1";
|
||
|
||
// ─── Service ──────────────────────────────────────────────────────────────────
|
||
|
||
// AgentService 是控制面暴露给 Agent 的唯一 gRPC 接口。
|
||
// 连接方向:Agent 主动拨号控制面。
|
||
// 除 Enroll 外,所有 RPC 均需通过 mTLS 客户端证书鉴权。
|
||
service AgentService {
|
||
// Enroll 供新节点申请证书。唯一不要求 mTLS 客户端证书的入口。
|
||
// Agent 提交 bootstrap_token(云初始化注入)与 CSR,控制面签发短期证书。
|
||
rpc Enroll(EnrollRequest) returns (EnrollResponse);
|
||
|
||
// Register 是 mTLS 建立后的首次调用,返回该节点的全量配置快照。
|
||
// ConfigSnapshot 直接作为响应类型(非 RegisterResponse),是有意设计——快照兼用于
|
||
// ApplyConfig 下发,避免重复定义。buf:lint:ignore 声明如下。
|
||
// buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
|
||
rpc Register(RegisterRequest) returns (ConfigSnapshot);
|
||
|
||
// Heartbeat 由 Agent 每 30 秒发送一次,上报实时负载并与控制面对账配置版本。
|
||
// 若 need_full_resync == true,Agent 应主动重新调用 Register。
|
||
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
|
||
|
||
// Subscribe 建立控制面 → Agent 的服务端流,用于下发 Command。
|
||
// Agent 重连时携带 last_command_id,控制面补发缺失命令(at-least-once 语义)。
|
||
// Command 直接作为流元素类型(非 SubscribeResponse),是有意设计——下游消费者
|
||
// 直接操作 Command,无需额外包装。buf:lint:ignore 声明如下。
|
||
// buf:lint:ignore RPC_RESPONSE_STANDARD_NAME
|
||
rpc Subscribe(SubscribeRequest) returns (stream Command);
|
||
|
||
// Ack 用于 Agent 向控制面回执命令的执行结果。
|
||
rpc Ack(AckRequest) returns (AckResponse);
|
||
|
||
// ReportUsage 上报各数据通道的流量用量,供计费/限速使用。
|
||
// 红线:items 中只含 dp_uuid 与字节/分钟计数,不含目的地址或 DNS 字段。
|
||
rpc ReportUsage(ReportUsageRequest) returns (ReportUsageResponse);
|
||
}
|
||
|
||
// ─── Enroll ───────────────────────────────────────────────────────────────────
|
||
|
||
message EnrollRequest {
|
||
// bootstrap_token 由云初始化(cloud-init)注入,单次有效,控制面验证后即失效。
|
||
string bootstrap_token = 1;
|
||
// csr_pem 是 Agent 生成的 X.509 证书签名请求(PEM 编码)。
|
||
string csr_pem = 2;
|
||
}
|
||
|
||
message EnrollResponse {
|
||
// cert_pem 是控制面签发的节点证书(PEM 编码)。
|
||
string cert_pem = 1;
|
||
// ca_pem 是控制面 CA 证书链(PEM 编码),供 Agent 验证后续 TLS。
|
||
string ca_pem = 2;
|
||
// not_after 是证书过期时间,Agent 应在到期前主动轮换。
|
||
google.protobuf.Timestamp not_after = 3;
|
||
}
|
||
|
||
// ─── Register ─────────────────────────────────────────────────────────────────
|
||
|
||
message RegisterRequest {
|
||
// agent_version 是 Agent 的语义化版本字符串,例如 "1.2.3"。
|
||
string agent_version = 1;
|
||
// capabilities 描述 Agent 支持的功能集,例如 ["hysteria2", "reality"]。
|
||
repeated string capabilities = 2;
|
||
}
|
||
|
||
// ─── Heartbeat ────────────────────────────────────────────────────────────────
|
||
|
||
message HeartbeatRequest {
|
||
// load 是节点实时负载快照。
|
||
NodeLoad load = 1;
|
||
// config_version 是 Agent 当前持有的配置版本号,用于对账。
|
||
uint64 config_version = 2;
|
||
}
|
||
|
||
message HeartbeatResponse {
|
||
// need_full_resync 为 true 时,Agent 应重新调用 Register 获取全量配置。
|
||
bool need_full_resync = 1;
|
||
}
|
||
|
||
// NodeLoad 节点实时负载,由 Heartbeat 携带。
|
||
message NodeLoad {
|
||
// online_count 是当前在线连接数(代理隧道数)。
|
||
uint32 online_count = 1;
|
||
// bandwidth_up 是上行带宽(bytes/s)。
|
||
uint64 bandwidth_up = 2;
|
||
// bandwidth_down 是下行带宽(bytes/s)。
|
||
uint64 bandwidth_down = 3;
|
||
// cpu_percent 是 CPU 使用率(0–100)。
|
||
float cpu_percent = 4;
|
||
}
|
||
|
||
// ─── Subscribe ────────────────────────────────────────────────────────────────
|
||
|
||
message SubscribeRequest {
|
||
// last_command_id 是 Agent 已成功处理的最后一个命令 ID。
|
||
// 首次订阅传 0,控制面将从头补发未 Ack 的命令。
|
||
uint64 last_command_id = 1;
|
||
}
|
||
|
||
// ─── Command ──────────────────────────────────────────────────────────────────
|
||
|
||
// Command 是控制面下发给 Agent 的指令,at-least-once 投递语义。
|
||
// command_id 单调递增,Agent 根据 last_command_id 实现幂等去重。
|
||
message Command {
|
||
// command_id 全局单调递增,用于续传与去重。
|
||
uint64 command_id = 1;
|
||
|
||
oneof payload {
|
||
UpsertCredential upsert_credential = 2;
|
||
RevokeCredential revoke_credential = 3;
|
||
RotateCredential rotate_credential = 4;
|
||
ApplyConfig apply_config = 5;
|
||
Lifecycle lifecycle = 6;
|
||
}
|
||
}
|
||
|
||
// UpsertCredential 新增或更新数据通道凭证。
|
||
// 红线:只含 dp_uuid,不含 user_id / email。
|
||
message UpsertCredential {
|
||
// dp_uuid 是数据通道的唯一标识符(Data-Plane UUID)。
|
||
string dp_uuid = 1;
|
||
// expires_at 是凭证到期时间,到期后 Agent 应拒绝该 dp_uuid 的连接请求。
|
||
google.protobuf.Timestamp expires_at = 2;
|
||
}
|
||
|
||
// RevokeCredential 立即吊销数据通道凭证,Agent 应断开对应连接。
|
||
// 红线:只含 dp_uuid,不含 user_id / email。
|
||
message RevokeCredential {
|
||
string dp_uuid = 1;
|
||
}
|
||
|
||
// RotateCredential 原子替换凭证,grace_seconds 内新旧 dp_uuid 均有效。
|
||
// 红线:只含 dp_uuid,不含 user_id / email。
|
||
message RotateCredential {
|
||
string old_dp_uuid = 1;
|
||
string new_dp_uuid = 2;
|
||
// grace_seconds 是新旧凭证共存的宽限期(秒),到期后 old_dp_uuid 自动失效。
|
||
uint32 grace_seconds = 3;
|
||
}
|
||
|
||
// ApplyConfig 将一份完整的 ConfigSnapshot 应用到 Agent。
|
||
message ApplyConfig {
|
||
ConfigSnapshot config = 1;
|
||
}
|
||
|
||
// Lifecycle 控制节点生命周期。
|
||
message Lifecycle {
|
||
enum Action {
|
||
ACTION_UNSPECIFIED = 0;
|
||
// ACTION_DRAIN 停止接受新连接,等待存量连接自然结束。
|
||
ACTION_DRAIN = 1;
|
||
// ACTION_SHUTDOWN 立即停止所有服务并退出进程。
|
||
ACTION_SHUTDOWN = 2;
|
||
}
|
||
Action action = 1;
|
||
}
|
||
|
||
// ─── ConfigSnapshot ───────────────────────────────────────────────────────────
|
||
|
||
// ConfigSnapshot 是节点运行所需的全量配置,由 Register 返回或 ApplyConfig 下发。
|
||
message ConfigSnapshot {
|
||
// config_version 单调递增,Agent 使用此版本号与控制面对账。
|
||
uint64 config_version = 1;
|
||
// credentials 是当前有效的数据通道凭证列表。
|
||
// 红线:只含 dp_uuid,不含 user_id / email。
|
||
repeated CredentialEntry credentials = 2;
|
||
// reality_sni 是 REALITY 协议使用的 SNI 域名(例如 "www.example.com")。
|
||
string reality_sni = 3;
|
||
// hy2_port_start / hy2_port_end 是 Hysteria2 监听的 UDP 端口范围。
|
||
uint32 hy2_port_start = 4;
|
||
uint32 hy2_port_end = 5;
|
||
}
|
||
|
||
// CredentialEntry 单条数据通道凭证。
|
||
// 红线:只含 dp_uuid 与有效期,不含 user_id / email。
|
||
message CredentialEntry {
|
||
string dp_uuid = 1;
|
||
google.protobuf.Timestamp expires_at = 2;
|
||
}
|
||
|
||
// ─── Ack ──────────────────────────────────────────────────────────────────────
|
||
|
||
message AckRequest {
|
||
uint64 command_id = 1;
|
||
bool ok = 2;
|
||
// error_message 仅在 ok == false 时填写,描述失败原因。
|
||
string error_message = 3;
|
||
}
|
||
|
||
message AckResponse {}
|
||
|
||
// ─── ReportUsage ──────────────────────────────────────────────────────────────
|
||
|
||
message ReportUsageRequest {
|
||
repeated UsageItem items = 1;
|
||
}
|
||
|
||
// UsageItem 单个数据通道的用量记录。
|
||
// 红线:只含 dp_uuid 与流量计数,不含目的地址、DNS 查询等可还原用户行为的字段。
|
||
message UsageItem {
|
||
string dp_uuid = 1;
|
||
uint64 bytes_up = 2;
|
||
uint64 bytes_down = 3;
|
||
uint32 minutes = 4;
|
||
}
|
||
|
||
message ReportUsageResponse {}
|