
Envoy TLS Inspector 监听器过滤器在流量进入过滤链前识别 TLS、SNI 与 ALPN【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoyTLS Inspector 是 Envoy 提供的一个 listener filter它在连接被任何网络过滤链处理之前先对连接的前几个字节做非破坏性嗅探判断传输层是 TLS 还是明文若为 TLS 则从 ClientHello 中提取 Server Name IndicationSNI与 Application-Layer Protocol NegotiationALPN。提取出的信息会写入 socket 的元数据供FilterChainMatch的server_names与application_protocols字段做过滤链选择是实现SNI 路由、TLS/明文混合监听等场景的关键组件。读完本文你可以掌握该过滤器的完整配置参数、底层基于 BoringSSL 的解析实现、全部统计指标与动态元数据的含义以及如何在集成测试中验证其行为。一、TLS Inspector 在监听器流水线中的位置Listener filter 运行在 Envoy 的 accept 阶段连接被 accept 后Envoy 会按顺序执行各 listener filter只有当前 filter 返回继续后下一个 filter 才能处理该连接全部 listener filter 放行后连接才交给真正的传输层套接字如 TLS 终止 socket和网络过滤链。TLS Inspector 正是利用这个早期时机把连接是否为 TLS、客户端声明了哪个域名、客户端协商了哪些协议这三个信息提前探测出来。其工作原理可以概括为三点TLS/明文判定过滤器使用 BoringSSL 的握手解析逻辑读取连接首包若能解析出 ClientHello 即判定为 TLS否则判定为明文SNI / ALPN 提取通过注册select_certificate回调在 BoringSSL 解析 ClientHello 的过程中同步拿到 SNI 与 ALPN 扩展内容再写入 listener 的 socket 元数据过滤链选择FilterChainMatch依据server_names、application_protocols匹配定义见 listener_components.proto从而把不同域名或协议的流量导向不同的 filter chain。过滤器在 Envoy 中的注册名称为envoy.filters.listener.tls_inspector兼容旧名envoy.listener.tls_inspector配置类型 URL 为type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector注册逻辑位于 config.ccTlsInspectorConfigFactory通过REGISTER_FACTORY宏完成静态注册createListenerFilterFactoryFromProto中先做 proto 的 downcast 校验再构造共享的Config最后通过闭包把Filter以 accept filter 的形式挂到ListenerFilterManager上。二、配置说明与完整示例官方文档给出的最简示例如下原文见 tls_inspector.rstlistener_filters: - name: tls_inspector typed_config: type: type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspectorTlsInspector消息的完整字段定义在 tls_inspector.proto结合 tls_inspector.cc 中Config构造函数对字段的解析各字段说明如下字段类型校验范围默认值说明enable_ja3_fingerprintingBoolValue—false启用 JA3 指纹从 ClientHello 的 TLS 版本、密码套件、扩展、椭圆曲线与点格式计算 MD5 哈希并写入 socketinitial_read_buffer_sizeUInt32Value(255, 65537)max_client_hello_size即 16KiB过滤器首次向内核请求读取的字节数不足时按翻倍直到上限的策略追加读取close_connection_on_client_hello_parsing_errorsbool—false解析 ClientHello 失败时是否直接断开连接仅在确认所有入站连接都应为 TLS 时开启因为 Envoy 无法区分明文消息和畸形的 ClientHellomax_client_hello_sizeUInt32Value(255, 16384]16KiBClientHello 允许处理的最大字节数超过后停止处理并记为失败client_hello_too_large配置参数在源码中的落地细节max_client_hello_size若大于 BoringSSL 允许的上限SSL3_RT_MAX_PLAIN_LENGTHConfig构造时直接抛出EnvoyException见 tls_inspector.cc#L73-L76因此该值被 proto 校验硬性限制在 16384 字节以内initial_read_buffer_size会被std::min夹逼到不大于max_client_hello_size避免首次请求量超过上限内部 SSL_CTX 固定版本区间为 TLS 1.0 到 TLS 1.3TLS_MIN_SUPPORTED_VERSION/TLS_MAX_SUPPORTED_VERSION见 tls_inspector.cc#L51-L53并关闭会话缓存与票据SSL_OP_NO_TICKET、SSL_SESS_CACHE_OFF因为 Inspector 只做半程握手解析从不完成握手。一个启用 JA3/JA4 指纹、显式指定缓冲与解析错误策略的完整配置示例基于 proto 字段自行拼装供参考listener_filters: - name: tls_inspector typed_config: type: type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector enable_ja3_fingerprinting: true enable_ja4_fingerprinting: true initial_read_buffer_size: 8192 close_connection_on_client_hello_parsing_errors: true max_client_hello_size: 16384三、底层解析流程非破坏性窥读与状态机3.1 accept 与数据读取Filter实现了三个关键回调见 tls_inspector.h#L92-L104onAccept记录回调指针返回StopIteration暂停后续 filter等待数据maxReadBytes返回动态调整的requested_read_bytes_即当前期望读取的字节数onData每次事件触发时拿到的是累积的窥读数据底层采用类似MSG_PEEK的非破坏性读取已见过的字节会重复返回因此代码用read_跳过已处理部分只把新增字节送入parseClientHello。parseClientHello的实现在 tls_inspector.cc#L263-L288把数据段包进内存 BIO设置BIO_set_mem_eof_return(bio, -1)向 BoringSSL 声明后面还有数据再通过SSL_set0_rbioSSL_do_handshake驱动一次握手解析。由于select_certificate回调最终总是返回ssl_select_cert_error握手必然在中途失败退出——这正是 Envoy 想要的借 BoringSSL 的严格解析能力免费完成 ClientHello 校验而不用自己写 ASN.1 解析器。3.2 状态机与错误判定解析结果映射为三种ParseStateDone/Continue/Error定义于 tls_inspector.h#L43-L50核心决策在getParserStatetls_inspector.cc#L195-L261SSL_ERROR_WANT_READ数据还不够若已读到max_client_hello_size上限则记client_hello_too_large并写动态元数据返回Error关闭连接否则把请求字节数翻倍上限为配置最大值返回Continue等待下一次数据事件SSL_ERROR_SSL出现解析错误。若此前 SNI 回调已成功clienthello_success_为 true说明拿到了合法的 ClientHello记tls_found并按是否见到 ALPN 记alpn_found/alpn_not_found最后调用socket().setDetectedTransportProtocol(tls)标记该连接为 TLS若从未成功解析则按close_connection_on_client_hello_parsing_errors决定是断开Error还是把连接当作明文放行Done记tls_not_found其它错误一律ErroronData中会直接close()该连接。值得注意的是源码中的一处运行时开关当启用了envoy.reloadable_features.tls_inspector_enforce_client_tls_version时还会校验 ClientHello 声明的 TLS 版本是否落在 TLS 1.0–1.3 区间内越界则记tls_not_found并写入失败原因ClientHelloInvalidTlsVersion见 tls_inspector.cc#L219-L228。从源码结构看这是一个可热更的 runtime feature行为是否生效取决于部署时是否开启。3.3 SNI 与 ALPN 的提取在Config构造函数中Envoy 为 SSL_CTX 注册了select_certificate回调tls_inspector.cc#L82-L99当 BoringSSL 解析到 ClientHello 时依次调用setClientTlsVersion记录客户端声明的版本createJA3Hash/createJA4Hash按配置计算指纹见第五节onALPN用 CBSBoringSSL 的字节串解析器从 ALPN 扩展中逐个取出协议名写入socket().setRequestedApplicationProtocols并置alpn_found_解析失败时不报错而是交给真正的 TLS 栈去处理onServername通过SSL_get_servername取出 SNI非空则记sni_found并setRequestedServerName否则记sni_not_found无论是否取到都会置clienthello_success_ true作为这是合法 ClientHello的判据。这些 socket 元数据正是后续FilterChainMatch匹配server_names/application_protocols的数据来源。四、完整统计指标该过滤器维护一棵以tls_inspector.为前缀的统计树ALL_TLS_INSPECTOR_STATS宏定义于 tls_inspector.h#L26-L34官方文档tls_inspector.rst列出的全部指标如下名称类型说明client_hello_too_largeCounter收到超大型超过处理上限ClientHello 的总次数tls_foundCounter判定为 TLS 连接的总次数tls_not_foundCounter判定非 TLS 连接的总次数alpn_foundCounter成功解析出 ALPN 的次数alpn_not_foundCounter未解析出 ALPN 的次数sni_foundCounter发现 SNI 的次数sni_not_foundCounter未发现 SNI 的次数bytes_processedHistogram记录分析过程中处理的字节数。若连接为 TLS值为 ClientHello 大小超限场景按实现上限 16KiB 记录若非 TLS值为 Inspector 判定不是 TLS前处理的字节数连接提前终止且字节不足以判定两种情况时不记录bytes_processed的采样点在parseClientHello中只有当状态离开Continue即最终判定完成时才通过computeClientHelloSize计算并记录实际消耗的 BIO 字节数tls_inspector.cc#L281-L285。这些计数器是排查为什么流量没匹配到预期 filter chain的第一手线索sni_not_found偏高通常意味着客户端如某些 HTTP 客户端库未发送 SNI 扩展此时应改用不含server_names的兜底链。五、动态元数据与失败原因当过滤器未能检测到 TLS 时会向动态元数据的命名空间envoy.filters.listener.tls_inspector写入一个failure_reason字段取值有三种定义见 tls_inspector.cc#L418-L436失败原因触发条件ClientHelloTooLarge数据量达到max_client_hello_size仍未完成 ClientHello 解析ClientHelloNotDetected解析失败且未成功解析出 ClientHello判定为明文ClientHelloInvalidTlsVersionClientHello 可解析但声明的 TLS 版本超出支持区间需 runtime feature 开启写入逻辑在setDynamicMetadatatls_inspector.cc#L178-L183构造Struct将failure_reason设为字符串值后通过cb_-setDynamicMetadata发布。这意味着你可以把envoy.filters.listener.tls_inspector.failure_reason作为 access log 的%DYNAMIC_METADATA(envoy.filters.listener.tls_inspector:failure_reason)%格式项输出用于审计被拒/未识别的连接来源。测试用例 tls_inspector_test.cc#L430-L431 验证了超限场景下failureReasonClientHelloTooLarge会被准确写入元数据。六、JA3 / JA4 客户端指纹TLS Inspector 额外提供了两个可选的客户端指纹能力都从 ClientHello 中派生写入 socket 元数据后可用于访问日志、路由或风控JA3enable_ja3_fingerprinting按TLS版本,密码套件列表,扩展列表,椭圆曲线列表,点格式列表拼接字符串后取 MD5见 tls_inspector.cc#L384-L406结果经socket().setJA3Hash发布。JA4enable_ja4_fingerprintingJA4 是 JA3 的改进版格式为tXXdYYZZ_CIPHERHASH_EXTENSIONHASH——协议类型字符、TLS 版本、SNI 存在标记d有域名/i无 SNI、密码套件数量、扩展数量再加密码套件与扩展各自的 SHA-256 前 12 位十六进制计算时按 RFC 8701 过滤 GREASE 值。实现见 ja4_fingerprint.h 与 ja4_fingerprint.cc格式语义与 tls_inspector.proto 的字段注释一致。七、组合使用与验证方式7.1 与 FilterChainMatch 配合的 SNI 路由典型用法是在同一个 listener 上listener_filters配置 tls_inspectorfilter_chains中为不同域名各配一条链例如filter_chain_match: { server_names: [a.example.com] }走一套 HTTP 链、server_names: [b.example.com]走另一套并为无 SNI 的连接保留一条兜底链。FilterChainMatch的server_names/application_protocols字段语义见 listener_components.proto。Envoy 自带的示例配置 envoyproxy_io_proxy.yaml 也展示了 listener filter 的整体写法风格可作为参照。7.2 测试验证仓库提供了完整的单测与集成测试来验证本节所述行为可作为行为基准tls_inspector_test.cc覆盖 SNI/ALPN 提取sni_found_、tls_found_计数断言见 L265-L271、各类failure_reason的动态元数据写入EXPECT_CALL(cb_, setDynamicMetadata(...))tls_inspector_integration_test.cc在真实 listener 管道上验证端到端行为tls_inspector_ja4_test.cc 与 ja4_fingerprint_test.cc验证 JA4 指纹格式tls_inspector_benchmark.cc性能基准可用于评估该过滤器在 accept 热路径上的开销。7.3 配置注意事项initial_read_buffer_size与max_client_hello_size都有 255的下限校验取值过小的配置会在配置校验阶段直接报错close_connection_on_client_hello_parsing_errors: true只适用于入站必然为 TLS的端口——该开关下所有无法解析出 ClientHello 的明文连接都会被主动断开由于 Inspector 从不完成握手它对握手零开销的窥读依赖MSG_PEEK式非破坏读取数据会原样留给后续真正的 TLS 传输层套接字使用不会消耗或改写任何字节若客户端声明的 TLS 版本低于 1.0 或高于 1.3在开启对应 runtime feature 后会被判为tls_not_found并记录ClientHelloInvalidTlsVersion这一点在排查低版本客户端连不上时有直接参考价值。综上TLS Inspector 是 Envoy 中用 BoringSSL 换解析能力的典型设计以最小代码量获得严格的 ClientHello 校验、SNI/ALPN 提取与可选指纹能力是构建多域名、协议混合监听入口的基石组件。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考