第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量
学习目标
- 理解"代理工作在第几层"到底意味着什么——它在哪一层重建了语义
- 分清 L4 代理与 L7 代理的本质差异:看得到什么、看不到什么、能做什么
- 掌握 SNI 嗅探这种"L4 之上、L7 未满"的中间技巧,及 ECH 对它的冲击
- 用
socat(L4)和nginx(L7)亲手验证"层级决定能力"
前置知识
- 第01章 代理统一模型
- 网络手册 · 网络模型与数据流转(OSI / TCP-IP 分层)
原理
"工作在第几层"到底在说什么
第01章 说过:代理把一条连接拆成两条。那么问题来了——拆开后,代理在转发前,把字节流解析到了哪一层?
代理"工作在第 N 层" = 它把经过的流量解析、理解、重建到了 OSI 第 N 层。 解析得越高,它懂得越多、能做的越多,但开销也越大。
一个形象的比喻:流量是一封层层套娃的信封(以太帧里装 IP 包,IP 包里装 TCP 段,TCP 段里装 HTTP 报文)。
- L4 代理只拆到 TCP 这层信封:它知道"这是去 443 端口的一串字节",但不拆 HTTP 那层,看不懂里面写了啥。
- L7 代理一路拆到 HTTP 报文:它能读 URL、方法、Header,甚至改写 Body。
逐层看代理
┌─────────────────────────────────────────────────────────┐
L7 │ 应用层 HTTP/gRPC/MySQL ← L7 代理在这里:懂 URL/Header │ Nginx, Envoy, HAProxy(http)
├─────────────────────────────────────────────────────────┤
L5/6 │ 会话/表示 TLS 会话/SOCKS ← SOCKS 代理在这里:管会话不解内容│ SOCKS5, stunnel
├─────────────────────────────────────────────────────────┤
L4 │ 传输层 TCP/UDP 端口 ← L4 代理在这里:只见端口+字节流 │ HAProxy(tcp), LVS, socat
├─────────────────────────────────────────────────────────┤
L3 │ 网络层 IP 路由/NAT ← 严格说是"转发"不是代理 │ iptables DNAT, IPVS
└─────────────────────────────────────────────────────────┘
L3:IP 层——这其实是"转发/NAT",不算真代理
L3 设备改写的是 IP 头(源/目的 IP),做的是路由和 NAT,并不会拆成两条独立 TCP 连接——它在同一条连接上改地址。所以严格说 NAT 不是代理。但它和代理关系密切:透明代理依赖 L3 的 DNAT/REDIRECT 把流量"拐"到代理端口(见 第11章)。记住这条边界:改地址 ≠ 代理;拆连接 = 代理。
L4:传输层——只见"端口 + 字节流"
L4 代理在两条 TCP(或 UDP)连接间搬运字节。它知道:源/目的 IP、端口、这是一条 TCP 连接。它不知道:里面是 HTTP 还是 SSH 还是 MySQL。
- 能做:按"目标端口/IP"转发、连接级负载均衡、TCP 层限流、做 TLS 透传(密文原样转发)
- 不能做:按 URL/Path 路由、改 HTTP Header、缓存、按域名分流(除非用下面的 SNI 嗅探)
- 优点:快、省、协议无关——一个 L4 代理能代理任意 TCP 协议
- 代表:HAProxy 的
mode tcp、LVS/IPVS、socat、kube-proxy 本质也是 L4
L5/L5.5:会话层——SOCKS 与隧道的位置
SOCKS 代理(第06章)和 HTTP CONNECT 隧道很特殊:它们建立了一个会话("帮我连到 host:port"),但建立之后对应用数据无感,只做盲转。所以常把它们归在会话层(L5)——比纯 L4 多了"按需建会话、可带认证、可解析目标域名"的能力,但又不像 L7 那样理解应用协议。
这解释了 SOCKS 的杀手锏:协议无关又能远端解析 DNS。它比 L4 多懂一点(目标地址、认证),又比 L7 通用(不挑应用协议)。
L7:应用层——懂得最多,也最重
L7 代理把字节流完整解析成应用层消息对象(一个 HTTP 请求、一个 gRPC 调用),转发前可以任意检视和改写。
- 能做:按 Host/Path/Header/Method/Cookie 路由、改写头与体、缓存、压缩、鉴权、限流、灰度、可观测(每请求埋点)
- 代价:要完整解析+缓冲,CPU 和延迟开销最大;且挑协议——一个 HTTP 代理代理不了 MySQL
- 代表:Nginx、Envoy、HAProxy 的
mode http、Traefik、API 网关
一张表收口:层级即能力
| 维度 | L3 转发 | L4 代理 | L5 SOCKS/隧道 | L7 代理 |
|---|---|---|---|---|
| 看得到 | IP | IP+端口+字节流 | 目标 host:port、认证 | 完整应用报文 |
| 看不到 | 端口以上 | 应用协议 | 应用数据 | —(全看得到) |
| 按域名/路径路由 | ❌ | ❌(除非 SNI 嗅探) | 按 host(不按 path) | ✅ |
| 改写 Header/Body | ❌ | ❌ | ❌ | ✅ |
| 协议无关(能转任意 TCP) | ✅ | ✅ | ✅ | ❌(挑协议) |
| TLS 处理 | 透传 | 透传 / SNI 嗅探 | 透传 | 终止 / MITM |
| 相对开销 | 最低 | 低 | 低 | 高 |
| 典型实现 | iptables/IPVS | HAProxy(tcp)/LVS | SOCKS5/CONNECT | Nginx/Envoy |
选层心法:能在低层解决就别上高层。要按 URL 路由、改头、缓存→只能上 L7;只是转发任意 TCP、要极致性能→用 L4;要协议无关又要按域名分流→L4 + SNI 嗅探或 SOCKS。
SNI 嗅探:L4 之上、L7 未满的灰色技巧
有个常见难题:我想做反向代理按域名分流 HTTPS,但又不想解密(不做 TLS 终止)。L4 代理看不到域名(在加密里),L7 又得解密。怎么办?
答案藏在 TLS 握手的第一个包——ClientHello 里的 SNI(Server Name Indication)扩展是明文的!客户端会在这里写明"我要访问 example.com",好让服务器选对证书。于是代理可以只偷看 ClientHello、读出 SNI、据此选后端,然后把整条 TLS 连接当字节流透传——既路由了,又没解密。
Client ──TLS ClientHello (SNI=api.example.com 明文!)──▶ [L4代理 peek SNI]
│ 读出 api.example.com
▼ 据此选后端,之后盲转密文
[api 后端]
- 用在哪:HAProxy
req.ssl_sni、Nginxssl_preread、Istio 的 SNI 路由、sing-box/Clash 的分流 - ⚠️ ECH 的冲击:TLS1.3 的 Encrypted Client Hello(ECH) 会把 SNI 也加密,SNI 嗅探将失效。这是当前代理路由演进的重要变量,详见 第05章。
回答第01章的思考题
CDN 节点对终端用户是反向代理;它"回源"拉源站内容时,这一跳更像正向还是反向?
更像正向——回源时 CDN 是"客户端",主动去访问源站,源站把 CDN 当成一个客户端。这印证了 第01章 的核心:正向/反向是立场,不是绝对身份。同一个节点,对下游是反向、对上游是正向。
️ 实现 / 命令
实验一:L4 代理"不懂"HTTP(socat 一行起)
# 用 socat 起一个纯 L4 代理:监听 8088,盲转到 example.com:80
socat -d -d TCP-LISTEN:8088,fork,reuseaddr TCP:example.com:80 &
# 通过它访问
curl -s -H "Host: example.com" http://127.0.0.1:8088/ -o /dev/null -w "%{http_code}\n"
# 200 —— 能转发
证明它"看不懂":socat 不会、也无法改写任何 HTTP 头或按 path 路由。你给它发什么字节,它原样转什么字节。它甚至不知道这是 HTTP——你把 example.com:80 换成 example.com:22,它照样能转 SSH。这就是 L4 的"协议无关"与"无能为力"的一体两面。
实验二:偷看 TLS ClientHello 里的明文 SNI
# 发起一次 TLS 握手并抓包,看 ClientHello 里的 SNI
sudo tcpdump -i any -n -A 'tcp port 443' &
openssl s_client -connect example.com:443 -servername api.example.com </dev/null 2>/dev/null | head -2
在 tcpdump 输出的第一个 TLS 包里,你能肉眼看到明文的 api.example.com——它没被加密。这就是 L4 代理能做 SNI 路由的物理基础。
# 更直观:用 tshark 直接提取 SNI 字段
sudo tshark -i any -f "tcp port 443" -Y "tls.handshake.extensions_server_name" \
-T fields -e tls.handshake.extensions_server_name
# api.example.com
实验三:L7 代理能改写、能按 path 路由(nginx)
server {
listen 8090;
# 按 path 分流——这是 L4 永远做不到的
location /api/ { proxy_pass http://backend-api; proxy_set_header X-Tier api; }
location /web/ { proxy_pass http://backend-web; proxy_set_header X-Tier web; }
# 改写响应头——也是 L7 专属
add_header X-Proxied-By "nginx-L7";
}
/api/ 和 /web/ 去了不同后端,还加了头——因为 Nginx 把字节解析成了 HTTP 对象。把这段和实验一的 socat 对比,"层级即能力"就具象了。
排错 / 选层误区
| 症状 | 根因 | 修正 |
|---|---|---|
| 用 L7(HTTP)代理去转 MySQL/SSH,连不通 | L7 代理挑协议,解析不了非 HTTP | 改用 L4(mode tcp/socat) |
| 想按 URL path 分流,却配了 L4 代理 | L4 看不到 path | 改用 L7,或前置 L7 |
| HTTPS 想按域名分流又不想给代理证书 | 该用 SNI 嗅探却做了 TLS 终止 | 用 ssl_preread/req.ssl_sni 透传 |
| 上了 ECH/加密 SNI 后 SNI 路由失效 | ClientHello 被加密,嗅探不到 | 退回 TLS 终止,或基于 IP/端口分流 |
| L4 代理后端拿不到真实客户端 IP | L4 不加 X-Forwarded-For | 用 PROXY protocol(第14章)或上 L7 |
本章小结
- 代理"工作在第几层" = 它把流量解析重建到了哪一层;越高越懂、越能改,但越重、越挑协议。
- L4:见端口+字节流,协议无关,快;L7:见完整报文,能路由/改写/缓存,但挑协议、开销大。
- SOCKS/隧道在 L5:管会话、可远端解析 DNS,但不碰应用数据,兼具通用性。
- SNI 嗅探让 L4 能按域名路由 HTTPS 而不解密;ECH 会终结这个技巧。
- 正向/反向是相对立场(CDN 回源 = 正向),层级才是能力的硬约束。
下一章 第03章,我们顺着一条真实请求,走完"DNS→连代理→认证→连源站→转发→回写→拆连接"的完整生命周期,把前两章的静态模型变成动态影片。