news 2026/9/10 3:59:45

gRPC Security Handshakers 源码深度解析:TLS/ALTS 握手框架与安全端点实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
gRPC Security Handshakers 源码深度解析:TLS/ALTS 握手框架与安全端点实现

gRPC Security Handshakers 源码深度解析:TLS/ALTS 握手框架与安全端点实现

【免费下载链接】grpcC++ based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc

导读

本篇文章聚焦 gRPC 核心仓库中负责安全握手的实现模块——src/core/handshaker/security/。该目录承载了 gRPC 安全基础设施中至关重要的一环:通过 TLS、ALTS 等传输安全机制(TSI)完成客户端与服务端之间的身份认证与通信加密。阅读本文后,你将理解SecurityHandshaker如何驱动 TSI 握手状态机、SecureEndpoint/PipelinedSecureEndpoint如何对grpc_endpoint进行加解密包装、握手完成后如何校验对端身份并移交 auth context,以及握手管道(pipelining)启发式选择器的调优参数。

一、模块定位:安全握手在 gRPC 连接建立中的角色

1.1 为什么需要 handshaker 框架

在 gRPC 中,一条连接从底层 TCP 就绪到真正承载 RPC 流量之间,需要先执行若干"握手"步骤,例如 HTTP CONNECT 代理协商、TLS 握手等。gRPC 为此设计了可插拔的 handshaker 框架(src/core/handshaker/handshaker.h):每个Handshaker代表一次握手操作,HandshakeManager按照优先级依次调用它们。

核心抽象定义如下(见 handshaker.h):

  • HandshakerArgs:握手过程中的输入/输出参数,包含endpoint(可被握手器替换为包装后的端点)、ChannelArgs、未被消费的read_bufferexit_early标志、EventEngine 指针、截止时间deadline以及 channelz 追踪节点等;
  • Handshaker:抽象基类,暴露DoHandshake()Shutdown()name()三个纯虚接口;
  • HandshakeManager:按加入顺序串行执行握手器,并通过on_handshake_done回调把最终结果交还调用方。

HandshakerFactory通过HandshakerPriority枚举(handshaker_factory.h)声明自己的执行优先级,安全握手器注册在kSecurityHandshakers这一档,位于 TCP 连接建立、HTTP CONNECT 之后,负责"连接建立之后的传输安全握手"。

1.2 security/ 目录在整个框架中的位置

src/core/handshaker/下的兄弟模块包括http_connect/(HTTP CONNECT 代理握手)、tcp_connect/(TCP 基础握手)、endpoint_info/(端信息数据结构)以及proxy_mapper相关注册表(见 src/core/handshaker/AGENTS.md)。security/是其中专门承担TLS / ALTS 等安全握手的目录,其职责正如目录内 AGENTS.md 所述:"负责在客户端与服务端之间建立安全连接,使用 TLS 或 ALTS 等传输安全机制认证服务端并加密双方全部通信"

二、目录结构与核心文件职责

src/core/handshaker/security/下共有 7 个文件,对应关系如下(依据 AGENTS.md 的 Files 小节):

文件职责
security_handshaker.h / security_handshaker.cc定义SecurityHandshaker类,是所有安全握手器的基类(实际为对 TSItsi_handshaker的封装驱动),并提供客户端/服务端两个HandshakerFactory
secure_endpoint.h / secure_endpoint.cc定义SecureEndpoint,对底层grpc_endpoint的包装,提供加解密后的安全通道
pipelined_secure_endpoint.cc支持读取管道化(read pipelining)的SecureEndpoint实现
pipelining_heuristic_selector.h选择是否启用读管道化的启发式策略(含四种实现)

三、SecurityHandshaker:TSI 握手状态机的驱动者

3.1 创建入口与工厂注册

SecurityHandshakerCreate()是安全握手器的统一创建入口(security_handshaker.cc),其逻辑为:

  • 若传入的absl::StatusOr<tsi_handshaker*>携带错误状态,则返回一个FailHandshaker,它会在DoHandshake()时直接以失败状态回调(name()"security_fail");
  • 若 TSI 握手器指针为空,同样返回FailHandshaker(错误码为UnknownError);
  • 否则构造真正的SecurityHandshaker

工厂注册发生在SecurityRegisterHandshakerFactories()(security_handshaker.cc),分别向HANDSHAKER_CLIENTHANDSHAKER_SERVER注册ClientSecurityHandshakerFactoryServerSecurityHandshakerFactory。该函数由核心插件注册表在构建CoreConfiguration时调用(见 src/core/plugin_registry/grpc_plugin_registry.cc)。

两个工厂的实现非常精简(security_handshaker.cc):从ChannelArgs中取出对应的安全连接器对象(客户端为grpc_channel_security_connector,服务端为grpc_server_security_connector),然后委托其add_handshakers()方法把具体的 TSI 握手器加入HandshakeManager。它们声明的优先级均为HandshakerPriority::kSecurityHandshakers

3.2 安全连接器如何产生 TSI 握手器

以 SSL/TLS 为例,SslChannelSecurityConnector::add_handshakers()(src/core/credentials/transport/ssl/ssl_security_connector.cc)展示了完整链路:

  1. 调用tsi_ssl_client_handshaker_factory_create_handshaker()创建 TSI 层握手器,传入覆盖目标名(overridden target name)、可选 channel args(如GRPC_ARG_TRANSPORT_PROTOCOLS)以及用于遥测的CollectionScope、backend service、locality 等信息;
  2. 将得到的tsi_handshaker*连同this(连接器)和args一起交给SecurityHandshakerCreate()
  3. 最终通过handshake_mgr->Add(...)挂到握手管理器上。

同理,ALTS 安全连接器(src/core/credentials/transport/alts/alts_security_connector.cc)、TLS 连接器(src/core/credentials/transport/tls/tls_security_connector.cc)、local 与 insecure 连接器都遵循同一模式。这正是 AGENTS.md 中"SecurityHandshaker类被设计为可扩展的,可以轻松添加新的安全机制"的具体体现——新增一种安全机制,只需提供对应的 TSI 握手器工厂与grpc_security_connector子类。

3.3 握手状态机:读-写-再读的循环驱动

SecurityHandshaker::DoHandshake()(security_handshaker.cc)是握手入口,其核心是一个围绕 TSItsi_handshaker_next()的状态循环:

DoHandshake └─> MoveReadBufferIntoHandshakeBuffer() // 把已读数据拷入握手缓冲 └─> DoHandshakerNextLocked() // 调用 tsi_handshaker_next() ├─ TSI_INCOMPLETE_DATA → grpc_endpoint_read() 等待更多数据 ├─ bytes_to_send > 0 → grpc_endpoint_write() 向对端发送握手消息 │ └─ 写完后若无 handshaker_result → 继续读对端数据 └─ 拿到 handshaker_result → CheckPeerLocked() 校验对端身份

关键实现细节(均可在 security_handshaker.cc 中验证):

  • 初始缓冲GRPC_INITIAL_HANDSHAKE_BUFFER_SIZE为 256 字节,MoveReadBufferIntoHandshakeBuffer()(第 L151-L165 行)在数据超出时会通过gpr_realloc动态扩容;
  • 异步支持tsi_handshaker_next()可能返回TSI_ASYNC,此时回调OnHandshakeNextDoneGrpcWrapper会在 TSI 线程被调用,代码通过self.release()把引用释放给异步路径(第 L419-L440 行);
  • 防死锁设计:读写回调通过OnHandshakeDataReceivedFromPeerFnScheduler/OnHandshakeDataSentToPeerFnScheduler投递到 EventEngine 上异步执行,避免在持有互斥锁mu_时内联回调造成死锁(源码注释明确指出这是 EventEngine 迁移前的临时方案);
  • 并发保护:整个握手状态机运行在Mutex mu_保护之下,所有*Locked方法都要求持锁调用(ABSL_EXCLUSIVE_LOCKS_REQUIRED)。

3.4 对端校验与握手收尾

当 TSI 报告握手完成并给出tsi_handshaker_result后,CheckPeerLocked()(第 L314-L339 行)做三件事:

  1. 通过tsi_handshaker_result_extract_peer()提取对端身份信息tsi_peer
  2. 调用连接器的check_peer()异步校验对端(TLS 场景下校验证书链与主机名,见 ssl_security_connector.cc),并把校验结果通过on_peer_checked_闭包回调;
  3. 校验通过后,从 auth context 中查询GRPC_TRANSPORT_SECURITY_LEVEL_PROPERTY_NAME属性,若不存在或为TSI_SECURITY_NONE,则通过global_stats().IncrementInsecureConnectionsCreated()统计一次"非安全连接"。

校验完成后进入OnPeerCheckedFn()(第 L216-L312 行)的收尾阶段:

  • tsi_handshaker_result取回"未使用字节"(tsi_handshaker_result_get_unused_bytes)——这些字节属于握手消息尾部夹带的早期应用数据,必须原样交还;
  • 查询 frame protector 类型(TSI_FRAME_PROTECTOR_ZERO_COPY/TSI_FRAME_PROTECTOR_NORMAL/TSI_FRAME_PROTECTOR_NONE),据此创建零拷贝或普通 protector;
  • 若存在 protector,则用grpc_secure_endpoint_create()把原 endpoint 包装为安全端点,未使用字节作为 leftover slices 传入;否则把未使用字节直接追加回read_buffer
  • 把 auth context 写入ChannelArgs,并在有 protector 时写入由 auth context 生成的 channelz 安全信息(MakeChannelzSecurityFromAuthContext,目前以 TLS 类型记录远端证书);
  • 最后调用Finish(absl::OkStatus())通知握手成功。

Shutdown()(第 L519-L527 行)则负责中途取消:置位is_shutdown_、调用connector_->cancel_check_peer()取消对端校验、tsi_handshaker_shutdown()关闭 TSI 握手器并释放 endpoint。

四、SecureEndpoint:加解密通道的端点包装

4.1 创建接口与 channel arg

SecureEndpoint本质上是把tsi_frame_protector/tsi_zero_copy_grpc_protector与底层grpc_endpoint组合成一个对外透明的安全端点。创建函数在 secure_endpoint.h:

  • grpc_secure_endpoint_create():包装 iomgr 风格的grpc_endpoint,可接收握手遗留的 leftover slices;
  • grpc_pipelined_secure_endpoint_create():EventEngine 风格、支持管道化的变体。

该头文件还定义了三个可调 channel arg(整数类型):

Channel Arg含义
grpc.secure_endpoint.decryption_offload_threshold单次读取达到该大小时,将解密卸载到 EventEngine 线程执行
grpc.secure_endpoint.encryption_offload_threshold单次写入达到该大小时,将加密卸载到 EventEngine 线程执行
grpc.secure_endpoint.encryption_offload_max_buffered_writes加密卸载路径上允许缓冲的最大写请求数

这些参数允许在高吞吐场景下把 CPU 密集的加解密操作移出 I/O 线程,以换取更低的尾延迟与更高的吞吐(阈值以下则内联执行以降低调度开销)。

4.2 数据面:protector 与零拷贝路径

从 pipelined_secure_endpoint.cc 的FrameProtector实现可以看到数据面的设计:

  • FrameProtector同时持有普通与零拷贝两种 protector,二者互斥:is_zero_copy_protector_标记当前使用哪种;
  • 读/写各分配一个STAGING_BUFFER_SIZE(8192 字节)的暂存 slice,普通 protector 模式下读写数据经暂存缓冲中转;
  • 内存全部来自ResourceQuotaMemoryOwner(通过memory_owner_.MakeSlice(...)分配),并记录self_reservation_保证自身存活期间的内存配额;
  • 读与写各自持有独立的互斥锁(read_mu_/write_mu_),允许并发读写方向上的加解密;
  • 构造时若传入 leftover slices,会复制进内部SliceBuffer,保证握手阶段遗留字节在安全端点内被正确消费;
  • 通过GRPC_TRACE_LOG(secure_endpoint, INFO)GRPC_TRACE_FLAG_ENABLED(secure_endpoint)提供细粒度的调试追踪。

4.3 管道化读取与启发式选择器

PipelinedSecureEndpoint的"管道化"指在读取路径上提前发起下一次读,让解密与网络读取并行,从而摊薄大消息场景下的往返延迟。但持续管道化在小消息(如普通 RPC 头部)场景下会浪费资源,因此引入 pipelining_heuristic_selector.h 动态决策:

启发式行为关键阈值
AlwaysOffHeuristic永远关闭管道化
AlwaysOnHeuristic永远开启管道化
ConsecutiveSmallReadsHeuristic连续出现小读取时关闭管道化,出现大读取时重新开启小读取阈值950 * 1024字节;连续 30 次小读取即关闭
MovingAverageHeuristic(默认)对读取大小做指数移动平均(新值权重 0.05),低于阈值关闭、高于阈值开启关闭阈值64 * 1024字节;开启阈值950 * 1024字节

PipeliningHeuristicSelector默认采用kMovingAverage,可通过SetHeuristicType()在四种策略间切换。从阈值设计可以推断:gRPC 认为单次读取超过约 950 KiB 属于"大读取",值得保持管道化,而 64 KiB 以下的持续小读取则更适合内联解密、关闭管道化以节省开销。

五、源码佐证:测试用例与调用链印证

  • 端到端验证:test/core/handshake/secure_endpoint_test.cc 与 test/core/handshake/secure_endpoint_read_coalescing_test.cc 直接测试grpc_secure_endpoint_create的读写加解密与读取合并行为;test/core/event_engine/posix/posix_endpoint_test.cc 则覆盖了管道化安全端点在 EventEngine 路径上的行为。
  • 注册链路SecurityRegisterHandshakerFactories仅在 src/core/plugin_registry/grpc_plugin_registry.cc 声明并在第 L151 行调用,印证了安全握手器作为核心插件随 gRPC Core 一同注册的事实。
  • 连接器协作:除 SSL 外,TLS(tls_security_connector.cc)、ALTS(alts_security_connector.cc)、local(local_security_connector.cc)、insecure(insecure_security_connector.cc)等连接器均调用SecurityHandshakerCreate注入握手器,验证了"安全连接器决定机制、SecurityHandshaker 统一驱动"的分层设计。

六、总结

src/core/handshaker/security/是 gRPC 传输安全的核心实现层:SecurityHandshaker以统一的状态机驱动 TLS/ALTS 等 TSI 握手,SecureEndpointPipelinedSecureEndpoint负责握手完成后的数据加解密与读取管道化,PipeliningHeuristicSelector则提供了可调优的性能开关。理解这一模块,即可掌握 gRPC 从"TCP 就绪"到"安全通道就绪"的完整链路,以及grpc.secure_endpoint.*系列 channel arg 和读取管道化阈值的调优入口。

【免费下载链接】grpcC++ based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 3:56:37

基于灰狼算法改进扰动观察法的光伏MPPT多峰寻优仿真

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 3:53:53

SpringBoot+Vue考务报名系统设计与实现全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 3:53:04

Claude Code高效实战:安装配置、模型切换与工作流优化完全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 3:45:38

Linux驱动多设备支持:设备树匹配与私有数据隔离实战

在瑞芯微平台做Linux驱动开发&#xff0c;总有那么一个瞬间让你怀疑人生&#xff1a;驱动代码明明写得很顺&#xff0c;一个外设跑得好好的&#xff0c;但只要同类设备接到第二个&#xff0c;系统就开始出幺蛾子。要么第二个设备根本没被probe&#xff0c;要么两个设备的数据互…

作者头像 李华