多层CDN代理下源端口丢失怎么办?一文讲透原理与解决方案

当你使用多层CDN或代理时,服务器收到的源端口往往是最后一层代理的端口,而不是真实用户的源端口。本文从零解释什么是源端口、为什么丢失、以及如何通过PROXY protocol和X-Forwarded-For等方案恢复真实信息。适合网络小白理解底层逻辑。

多层CDN代理下源端口丢失怎么办?一文讲透原理与解决方案
封面图:ZuCDN · ZuCDN 原创

一个容易被忽略的问题:服务器看到的端口不是用户的端口——CDN Source

配置前的检查

如果你正在处理CDN Source,先别急着照搬网上的参数。假设你的网站使用了CDN加速,并且CDN又回源到了另一层反向代理(比如Nginx),最终才到达你的源服务器。此时你可能会发现,源服务器上记录的所有连接都来自同一个IP和同一个端口,比如 203.0.113.5:12345。但真实用户明明分布在全国各地,他们的源端口应该各不相同才对。这个现象就是多层CDN代理叠加场景下的源端口丢失

对大多数网站来说,丢失源端口似乎不影响访问——网页照样能打开。但如果你需要基于源端口做日志审计、DDoS溯源、或某些安全策略(比如源端口白名单),端口丢失就会带来大麻烦。更糟糕的是,你甚至可能误把CDN节点的IP当成攻击者而误封。

下面我们一步步拆解这个问题,即使你只有网络基础知识也能看懂。

什么是源端口?为什么它在多层代理中会丢失?与CDN Source

源端口是什么

每个TCP连接都由四元组唯一标识:源IP、源端口、目标IP、目标端口。当你访问百度时,你的浏览器会随机分配一个临时端口(比如 56789)和百度的80端口建立连接。这个临时端口就是源端口。服务器可以通过源端口区分来自同一客户端的多个并发连接。

普通代理场景:第一次丢失

当你只使用一层CDN时,CDN节点会替你转发请求。标准做法是:

  • CDN节点作为客户端去连接你的源服务器
  • 此时源服务器看到的是CDN节点的IP和端口,而不是你的真实源IP和端口

为了解决源IP丢失问题,大家用 X-Forwarded-For 头传递真实IP。但源端口没有类似的标准化机制。HTTP 头 X-Forwarded-For 只记录IP,不记录端口。所以第一层丢失的是源端口。

多层CDN代理:雪上加霜

如果你的架构中有两层CDN(比如Cloudflare → 又拍云 → 自建Nginx → 源站),那么:

  1. 用户请求到Cloudflare,Cloudflare向上游又拍云发起新的TCP连接,源端口变为Cloudflare节点的某个端口
  2. 又拍云收到后,再向你的Nginx发起连接,源端口变为又拍云节点的端口
  3. Nginx再转发到源站,源站看到的源端口是Nginx的连接端口

最终源服务器无法知道真实用户到底用了哪个源端口。而且不同代理层还可能复用端口,导致日志中大量“重复端口”,让溯源完全失效。

为什么源端口很重要?三个典型场景

1. 安全日志和攻击溯源

当你遭受DDoS或扫描攻击时,源端口可以帮助你区分不同攻击源。如果所有请求看起来都来自同一个端口(比如443),你无法判断是多个用户还是单个攻击者在不断重连。

2. 基于源端口的访问控制

有些企业内网应用使用端口白名单——只允许特定源端口段的连接。一旦经过CDN,所有请求的源端口都变了,白名单策略失效。

3. 连接数与会话管理

一些统计系统依赖源端口计算独立连接数。端口丢失后,统计结果会严重失真。

解决思路:从协议层面恢复源端口

方案一:使用 PROXY protocol

PROXY protocol 是一个在TCP连接开头附加的协议,专门用于在代理之间传递客户端的原始IP、端口等信息。它由HAProxy作者设计,广泛被Nginx、HAProxy、AWS ELB等支持。

  • 工作原理:在TCP连接建立后,代理首先发送一个文本头部,格式如:PROXY TCP4 192.0.2.1 203.0.113.5 56789 80,其中第4个字段是真实源端口(56789),第5个是目标端口(80)。
  • 要求:所有代理层都必须支持并启用PROXY protocol,并且只能在TCP层传输,不能用于HTTP/2或加密连接。
  • 配置示例(Nginx):在代理的listen指令后加上 proxy_protocol,并在后端配置中开启 set_real_ip_fromreal_ip_header proxy_protocol

PROXY protocol 是目前解决多层代理源端口问题的最可靠方案,它完整保留了源IP+源端口。

方案二:扩展HTTP头部传递端口

由于PROXY protocol 需要底层支持,有些CDN厂商提供了自定义HTTP头传递源端口。例如:

  • X-Forwarded-Port
  • X-Real-Port
  • 或者合并到 X-Forwarded-For 中,格式为 client-ip:port(非标准,但部分负载均衡器支持)

这种方式的缺陷是:

  • 多层代理可能覆盖或丢失头部值
  • 需要源服务器解析自定义头部,且无法保证所有代理都正确附加

方案三:利用四层透明代理(TProxy)

如果你完全控制网络层,可以在源站上使用 TProxy 技术,让代理服务器通过iptables规则拦截数据包并保持原始源IP和端口。但这需要非常底层的网络配置,且大部分CDN节点不支持。

实际部署建议:针对多层CDN场景的步骤

第一步:确认每一层是否支持PROXY protocol

检查你的CDN供应商文档:Cloudflare 支持(需要开启“Proxy Protocol”选项)、又拍云支持(需配置)、AWS CloudFront 支持(通过“Origin Protocol Policy”中的“PROXY protocol”)等。自建Nginx和HAProxy天然支持。

第二步:从最顶层CDN开始启用

在最外层的CDN(离用户最近的一层)开启PROXY protocol发送,然后在内层CDN或Nginx的 listen 指令加上 proxy_protocol 参数,同时配置 proxy_protocol 向后端传递。确保链条不断。

第三步:在源服务器上正确解析

如果源站是Nginx,需要在server块中添加:

listen 80 proxy_protocol;
set_real_ip_from 0.0.0.0/0;
real_ip_header proxy_protocol;
real_ip_recursive on;

之后,$remote_addr$remote_port 就会变成真实的客户端IP和端口。

第四步:验证

在源站访问日志中添加 $remote_port,然后使用curl从不同端口访问,检查日志是否记录了你客户端的随机端口。

常见误区与陷阱

误区1:X-Forwarded-For 也包含端口?

标准RFC 7239定义的X-Forwarded-For和Forwarded头部支持端口,但实际实现中很少使用。例如Forwarded: for=192.0.2.1:56789 是合法的,但很多CDN不填充端口部分。

陷阱1:PROXY protocol 会破坏非代理连接

如果客户端直接连接启用了proxy_protocol的端口,客户端发送的第一个字节会被解析成PROXY协议头,导致连接失败。所以需要确保只有代理层的连接走这个端口,或者单独开一个端口。

陷阱2:多层CDN中的时序问题

某些CDN为了性能,会在内部复用连接池,导致PROXY protocol头中的源端口不是用户端口而是CDN节点内部端口。需要确认CDN厂商是否真正透传原始信息。

总结

容易忽略的细节

源端口丢失是多层CDN架构中容易被忽视的问题。随着Web应用对安全审计和精确溯源的要求越来越高,恢复真实源端口变得必要。目前最成熟的解决方案是统一使用PROXY protocol,从用户端到源站每一层都开启支持。虽然配置有一定复杂度,但一旦配好,就如同给请求打上了“指纹”,让你能精准定位每一个连接。

如果你正在使用多层CDN,不妨检查一下日志中的remote_port,看看是否已经丢失。如果丢失,现在动手改造还不晚。把这些步骤跑通后,CDN Source基本就能稳定落地。

延伸阅读