Rust in Everything
基于 pingora-core 0.9.0

not implemented: No tls feature was specified

主线位置:请求主线里 TLS 那一步,下游是 Acceptor::tls_handshake,上游是连接器的 TLS connect。一个后端都不开时这些名字由 noop_tls 提供,见怎么读这一卷的「一个 TLS 后端都不开」和 TLS。本页把它当成一个坑:怎么撞上、怎么发现。

flowchart TB
  feat["没开 TLS feature"] --> noop["noop_tls 桩"]
  noop --> st["intermediate 返回 Ok"]
  st --> run["编译 启动都正常"]
  run --> down{"哪个方向"}
  down -->|下游 HTTPS| pan["tls_handshake panic"]
  down -->|上游 TLS| fake["假 SslStream"]
  fake --> eat["写入被吞掉"]
  eat --> r502["502 ConnectionClosed"]

实验 crate exp-pingora/pita-notls:pingora 只开 proxy;它的 fix feature 是 ["pingora/openssl"]。M2 Max、macOS 27、rustc 1.99.0,debug,tokio 1.53.2。

现象

依赖只写了 pingora = { version = "=0.9.0", features = ["proxy"] },代码照着 TLS 的样子写:上游 HttpPeer::new("127.0.0.1:19211", true, "localhost")(tls = true),再加一个 HTTPS 监听,证书路径故意不存在:

  let (cert, key) = (std::env::var("CERT").unwrap(), std::env::var("KEY").unwrap());
  if let Err(e) = proxy.add_tls("127.0.0.1:19212", &cert, &key) {
    eprintln!("add_tls: {e}");
    std::process::exit(1);
  }

编译没有任何警告。CERT=/nonexistent/cert.pem KEY=/nonexistent/key.pem 启动,打出 started,add_tls 返回了 Ok。19211 上是一个只收数据、不回应的 TCP 服务。先请求明文端口,再请求 HTTPS 端口,再请求一次明文端口:

http=502
http=000 exit=35
http=502

代理的日志:

[2026-10-29T07:00:05Z ERROR pingora_proxy] Fail to proxy: Upstream ConnectionClosed context: Peer: addr: 127.0.0.1:19211, scheme: HTTPS, sni: localhost cause:  context: while reading response headers, bytes already read: 0, status: 502, tries: 1, retry: false, GET /x, Host: 127.0.0.1:19210

thread 'Pingora HTTP Proxy Service' (5958028) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pingora-core-0.9.0/src/protocols/tls/noop_tls/mod.rs:102:13:
not implemented: No tls feature was specified

上游那边:

upstream: connection from 61192, 0 bytes received, closed
upstream: connection from 61195, 0 bytes received, closed
  • 上游方向不 panic,也不报 TLS 错误。 TCP 连上了,请求头写进了一个假的 SslStream,上游一个字节都没收到;读响应头立刻读到 0 字节,错误是 ConnectionClosed。日志里写着 scheme: HTTPS,看上去像上游把连接关了。
  • 下游方向在握手时 panic。 每个 HTTPS 连接 panic 一次,curl 看到的是握手失败(35)。panic 发生在连接自己的任务里,监听循环不受影响,所以第三次明文请求还是 502,这一点和 tokio 1.54 的 panic 不同。
  • 证书路径不存在,add_tls 却成功了:这个错误要到第一个 HTTPS 连接进来才暴露。

出处

pingora-core 在 not(any_tls) 时把 noop_tls 改名成 tls。这个模块的文档注释是「a set of stubs that provides the minimum types to let pingora work without any tls providers configured」:

  • TlsSettings::intermediate 直接 Ok(Self),参数被丢掉。
  • Acceptor::tls_handshake 是 unimplemented!("No tls feature was specified"),就是上面第 102 行。
  • 连接器的 connect 返回一个 SslStream::default();它的 poll_read 立刻返回 Ok、什么都不填,也就是 EOF,poll_write 吞掉全部数据并报告写完。

pingora 门面 crate 和 pingora-core 的 [features] 都是 default = [],不写 TLS feature 就是这个状态。HttpPeer::new 的 tls 参数、add_tls 方法都不受 feature 控制,所以代码怎么写都编得过。

📝NOTE
没有蠢问题

问: pingora-core 的 crate 文档写着 openssl 是「default if no TLS feature is specified」,怎么会落到 noop_tls?

答: 文档和代码不一致。lib.rs 的 crate 文档写的是「TLS backends (mutually exclusive)」和「openssl: ... (default if no TLS feature is specified)」,而 pingora-core/Cargo.toml 里是 default = [],没有任何 feature 会自动打开 openssl。上面 pita-notls 的 cargo tree -i pingora-core -f '{p} [{f}]' --depth 0 打出的是 pingora-core v0.9.0 []。「mutually exclusive」也只是约定,代码里没有互斥,见 the name tls is defined multiple times。

修法

开一个 TLS 后端。pita-notls 用 --features fix(即 pingora/openssl)重新编译,同样的不存在的路径:

add_tls:  TLSConfigError context: fail to read key file /nonexistent/key.pem cause: error:80000002:system library:file_ctrl:reason(2):crypto/bio/bss_file.c:299:calling fopen(/nonexistent/key.pem, r), error:10080002:BIO routines:file_ctrl:system lib:crypto/bio/bss_file.c:302:, error:0A080002:SSL routines:SSL_CTX_use_PrivateKey_file:system lib:ssl/ssl_rsa.c:411:
exit=1

openssl 后端的 intermediate 当场读证书,add_tls 返回 Err,进程在启动时就退出。换成真实的自签名证书再请求 HTTPS 端口,下游握手成功,到上游那一步失败,这次错误说的是实话:

https http=502
[2026-10-29T07:00:25Z ERROR pingora_proxy] Fail to proxy: Upstream TLSHandshakeFailure context: TLS connect() failed: unexpected EOF, SNI: localhost cause: local address 127.0.0.1:61231, status: 502, tries: 1, retry: false, GET /y, Host: 127.0.0.1:19212
upstream: connection from 61231, 1487 bytes received, closed

上游这次收到了 1487 字节的 ClientHello,因为它不是 TLS 服务、不回应,握手以 TLSHandshakeFailure 失败。

如果选 rustls,注意它的 intermediate 也只记路径、不读文件,证书错误同样推迟到启动服务时,见 Failed to load provided certificates。

怎么确认没中招

  • cargo tree -i pingora-core -f '{p} [{f}]' --depth 0:方括号里要有 any_tls,并且 openssl、boringssl、rustls、s2n 正好一个。pita-notls 不加 fix 是 [],加了是 [any_tls,openssl,openssl_derived,pingora-openssl]。
  • 日志里出现 noop_tls/mod.rs 或 No tls feature was specified,就是这个坑。
  • 上游明明是 HTTPS,却只看到 ConnectionClosed ... bytes already read: 0、从来没有 TLSHandshakeFailure 之类的 TLS 错误,也要怀疑是 noop_tls 在吞数据。

小结

  • 不开 TLS feature 时 pingora 用 noop_tls:编译、启动都不报错,add_tls 不读证书。
  • 下游 HTTPS 连接在握手时 panic(noop_tls/mod.rs:102),只杀那一个连接;上游 TLS 请求被假的 SslStream 吞掉,客户端拿到 502,日志说的是 ConnectionClosed。
  • pingora-core 的文档说 openssl 是默认,实际 default = []。开一个后端,并用 cargo tree 的 {f} 确认。

💬 关联讨论

发起讨论
加载中...