注入与原生 HTTP 链

模块最核心的一句话:边缘送来的请求就是 nginx 的普通请求。

text
Cloudflare 边缘
   │  一条 h2 流(请求头、请求体 DATA)
   ▼
本模块:造一条虚拟连接 → ngx_http_init_connection()
   │
   ▼
nginx 原生 HTTP 处理链
   server_name 匹配 → location 匹配 → rewrite → proxy_pass / return / 变量 / access_log
   │
   ▼
本模块:把响应(含 chunked 解块、trailer)写回那条 h2 流

因此业务配置照旧写 —— 你在 cloudflared 时代为 127.0.0.1:8080 写的 server {}, 现在原样有效,只是「谁把请求送进来」从 cloudflared 变成了模块自己。

注入目标怎么定 ​

写法含义
cfd_tunnel_inject_listen 127.0.0.1:8080;请求送进这个 listen
不配 inject_listen(扁平)由注入器在已有监听器里自动挑一个(地址族/本地地址合适的那个)
不配 + 开了 remote_config由命中规则的 service 端口决定(见 远程配置下发)
块内 inject_listen 8090;只写端口:在该端口的监听地址里找唯一的那个,有歧义就跳过这条隧道

注入的虚拟连接是一条真正由 nginx 分配的连接:连接槽来自 ngx_get_connection()、连接池来自 ngx_create_pool()、c->fd 是 socketpair(AF_UNIX) 的真实 fd(只作连接身份载体,数据一个字节 都不经过它),本地地址借自监听器(c->local_sockaddr)。请求字节由模块通过 c->recv 喂进 nginx 的 HTTP 解析器 —— 所以仍然不需要 UDS、不需要额外进程、不需要共享内存。

详细机制(含「为什么绝不能碰 socketpair 的对端」这条不变式)见 注入层。

哪些东西是原生的 ​

能力是否原生说明
server_name / location / if / rewrite✅按注入请求的 Host 与 path 正常匹配
proxy_pass(含 upstream、keepalive、buffering)✅模块只是请求的来源,不是代理的替代
变量($host、$request_uri、$http_*…)✅由 nginx 求值
access_log / error_log✅与普通请求同形
gzip / gunzip 过滤器✅含上游 gzip 的透明处理
请求体流式上传✅content-length 已知与未知(chunked)都支持
响应体流式下载 + 背压✅高水位 CFD_RESP_HWM = 256 KiB
WebSocket / 升级类流✅升级头由模块合成,101 之后双向裸字节转发
set_real_ip_from / realip✅配合 inject_source_addr
裸 TCP / ssh / rdp / smb / bastion❌传输层直接 RST_STREAM(REFUSED_STREAM),不进 nginx

两条必须记住的例外 ​

1. 裸 TCP 隧道流:直接丢弃 ​

判据与 cloudflared 一致:流头里 Cf-Cloudflared-Proxy-Src 非空 ⇒ 这条流是 TypeTCP(非 HTTP 服务)。本模块的注入点在 nginx 的 HTTP 链上,承载不了裸 TCP,于是:

  • 回 RST_STREAM(REFUSED_STREAM)(0x7),不回 502、不进任何 location;
  • 立刻释放流,留一条 ERR 日志。

这是用户定案的规则:宁可响亮失败,也绝不静默降级成 HTTP。

2. 大请求要自己放开 nginx 的门槛 ​

指令默认影响
client_max_body_size1m超了 nginx 自己回 413
client_body_timeout60s上传太慢会断

这是 nginx 对普通请求的正常行为 —— 从 cloudflared 换过来时,原来能传的 1 MiB 以上文件 要显式放开:

nginx
server {
    listen 127.0.0.1:8080;
    client_max_body_size  512m;
    client_body_timeout   300s;
}

WebSocket ​

nginx
location /ws {
    proxy_pass         http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade    $http_upgrade;
    proxy_set_header   Connection "upgrade";
    proxy_read_timeout 3600s;    # 空闲长连接
}

升级由模块完成:请求侧合成 Upgrade / Connection,响应 101 被翻译成 h2 的 200 + cf-cloudflared-response-headers,之后两个方向都是裸字节转发(含 4 MiB 级别背压路径)。

一条流是怎么收尾的 ​

场景行为
正常结束响应写完后流关闭,记账释放(连接级配额一并归还)
隧道中途被掐断请求置错并摇醒写侧,避免请求永不收尾
源站返回错误按 nginx 的语义回 h2 状态码(不是伪装成 200)
半路收到 RST_STREAM放弃该请求,不污染同一连接上的其他流

模块文档 · 对齐依据是 cloudflared 的源码与实测行为