第01章 代理是什么:正向 / 反向 / 透明 / 隧道的统一模型
学习目标
- 用一句话说清"代理"的本质,建立可迁移的统一心智模型
- 彻底分清正向代理、反向代理、透明代理、隧道代理四种类型的本质差异(不是"方向不同"这么肤浅)
- 能从一条 HTTP 请求的第一行,反推出它面对的是正向还是反向代理
- 用
curl+tcpdump亲手观察四类代理在字节流上的不同表现
前置知识
- 网络手册 · 第5章 应用层协议(HTTP/TLS/QUIC)
- 知道 HTTP 请求由"请求行 + 头 + 体"组成,知道 TCP 是字节流
原理
一句话定义
代理(Proxy)= 一个替你收发数据的中间人。 通信双方本可以直连,现在中间多了一跳:数据先到代理,代理再转发。
就这么简单。难的不是"代理是什么",而是这个中间人站在谁的立场、替谁隐身、在第几层拆包、要不要看懂你说的话。把这四个问题问清楚,任何"代理"都能被你一眼看穿。
统一模型:一根链路上的三个角色
所有代理都是这张图的变体:
┌────────┐ ┌────────┐ ┌────────┐
│ Client │ ─────▶ │ Proxy │ ─────▶ │ Origin │
│ 客户端 │ ◀───── │ 代理 │ ◀───── │ 源站 │
└────────┘ └────────┘ └────────┘
A B C
两段独立的连接:A↔B 一条,B↔C 另一条。
代理 = 在 B 处把"读到的"重新"写出去"。
关键认知:代理把一条端到端连接,拆成了两条。 Client 和 Origin 之间不再有直接的 TCP 连接——它们各自只和代理建连。这条"一拆为二"是理解一切代理行为的总开关:
- 因为拆成两条,代理可以改写经过的内容(加头、压缩、缓存、拦截)
- 因为拆成两条,代理可以隐藏某一端(让 Origin 看不到真实 Client,或让 Client 看不到真实 Origin)
- 因为拆成两条,两条连接可以用不同协议(Client 用 HTTP/1.1,Origin 用 HTTP/2)
四个决定性问题
区分代理类型,只需回答四个问题:
| 问题 | 含义 |
|---|---|
| 谁配置了代理? | 客户端主动设置,还是网络/服务端替它决定 |
| 谁知道代理存在? | 客户端有感知,还是被蒙在鼓里 |
| 代理面向哪些目标? | 任意目标(你说去哪就去哪),还是固定的一组后端 |
| 代理替谁隐身? | 隐藏客户端,还是隐藏服务端 |
用这四个问题套下去,四类代理自然分开:
四类代理的本质
① 正向代理(Forward Proxy)—— 替客户端出面
[内网 Client] ──▶ [Forward Proxy] ──▶ [Internet 任意网站]
我显式配置它 它替我访问外网 源站看到的是代理的 IP
- 谁配置:客户端自己(浏览器代理设置、
HTTP_PROXY环境变量、curl -x) - 谁感知:客户端清楚知道自己在用代理
- 面向目标:任意——客户端在请求里告诉代理"我要去 example.com"
- 隐身谁:隐藏客户端(源站只看到代理 IP)
- 典型场景:企业统一出网与审计、Squid 缓存、科学上网客户端
记忆点:正向代理是"客户端的代言人",源站不知道你是谁。
② 反向代理(Reverse Proxy)—— 替服务端出面
[Internet Client] ──▶ [Reverse Proxy] ──▶ [后端 1/2/3 固定集群]
我以为它就是网站 它替网站接客 客户端不知道后端长啥样
- 谁配置:服务端运维(在 Nginx/HAProxy 上配
proxy_pass) - 谁感知:客户端完全无感,以为代理就是真正的服务器
- 面向目标:固定——代理背后是一组预先配置好的后端
- 隐身谁:隐藏服务端(客户端不知道真实后端的 IP、数量、拓扑)
- 典型场景:负载均衡、TLS 终止、Nginx/HAProxy、K8s Ingress、CDN 回源
记忆点:反向代理是"服务端的门面",客户端不知道门后有几台机器。
关键澄清:正向 vs 反向,差的不是"方向"
很多人以为"正向是出去、反向是进来",这是错的。同一个 Nginx 二进制,既能当正向代理也能当反向代理。真正的分水岭是两条:
- 谁知道代理在那儿:正向代理对客户端是"显式"的;反向代理对客户端是"透明"的(客户端以为在直连服务器)。
- 目标从哪来:正向代理的目标写在客户端的请求里(你告诉它去哪);反向代理的目标写在代理的配置里(运维告诉它去哪)。
这条差异会直接体现在 HTTP 请求的第一行上——下文 实现 会用 tcpdump 给你看铁证。
③ 透明代理(Transparent Proxy)—— 客户端根本不知情
[Client] ──▶ [网关/内核劫持] ──▶ [Transparent Proxy] ──▶ [Origin]
我以为我在直连 iptables/eBPF 实际被截胡了
目标地址,没配任何代理 悄悄改了数据包走向
- 谁配置:网络管理员/平台(在网关或主机内核上用 iptables/TPROXY/eBPF 劫持)
- 谁感知:客户端毫不知情,没设置任何代理,以为在直连
- 面向目标:任意(劫持所有匹配流量)
- 隐身谁:对客户端隐藏"代理这一跳本身"
- 典型场景:运营商/企业网关旁路审计、家庭路由器、Istio Sidecar 对 Pod 流量的零侵入劫持
记忆点:透明代理是"看不见的中间人",连客户端都不知道它存在。 它靠的不是应用层配置,而是网络层把包"拐"过来——这部分原理见 第11章 透明代理。
④ 隧道代理(Tunnel Proxy)—— 只搬字节,不看内容
前三类是从"立场"分的,隧道是从"是否理解载荷"分的,它常和正向代理叠加出现:
[Client] ──CONNECT example.com:443──▶ [Proxy]
[Client] ◀──── 200 Connection Established ──── [Proxy]
[Client] ══════ 之后是加密字节,代理看不懂,原样转发 ══════▶ [Origin]
- 本质:代理在两条 TCP 连接间逐字节双向拷贝,不解析、不理解上层协议
- 为什么需要:HTTPS 流量是加密的,正向代理无法像处理明文 HTTP 那样改写它,只能"开个隧道让密文通过"
- 两种主力实现:HTTP 的
CONNECT方法、SOCKS5协议 - 典型场景:浏览器经 HTTP 代理访问 HTTPS 网站、SSH 隧道、SOCKS 代理
记忆点:隧道代理是"蒙着眼睛的搬运工",只管把字节从左手倒到右手。
一张总表收口
| 维度 | 正向代理 | 反向代理 | 透明代理 | 隧道代理 |
|---|---|---|---|---|
| 谁配置 | 客户端自己 | 服务端运维 | 网络/平台 | 客户端自己 |
| 客户端是否感知 | ✅ 知道 | ❌ 以为直连服务器 | ❌ 毫不知情 | ✅ 知道 |
| 目标从哪来 | 客户端请求里 | 代理配置里 | 被劫持的原始目标 | 客户端的 CONNECT 里 |
| 面向目标 | 任意 | 固定后端 | 任意(劫持) | 任意 |
| 隐藏谁 | 客户端 | 服务端 | 代理这一跳 | —(只搬运) |
| 是否看懂载荷 | 看明文 HTTP | 看明文 HTTP | 看/不看皆可 | ❌ 不看 |
| 工作层(典型) | L7 / L5 | L7 | L3-L7 | L4(隧道内) |
| 代表实现 | Squid、Clash | Nginx、HAProxy | Istio、网关劫持 | CONNECT、SOCKS5 |
这张表是全书的"坐标系"。后面每讲一个组件,你都可以回来对一对:它落在哪一格。
️ 实现
理论讲完,我们用最小的命令把四类代理在字节流上的差异逼出来。核心结论先说:正向代理和反向代理的区别,肉眼可见地写在 HTTP 请求行里。
- 正向代理收到的请求行是 absolute-form(绝对 URI):
GET http://example.com/ HTTP/1.1 - 反向代理收到的请求行是 origin-form(相对路径):
GET / HTTP/1.1
这不是约定俗成,是 RFC 7230 §5.3 明文规定的:客户端知道自己在对代理说话时,必须把完整 URL 放进请求行,好让代理知道转发去哪。
数据流:正向代理处理明文 HTTP
1. 客户端 → 代理: GET http://example.com/ HTTP/1.1 ← 绝对 URI!
2. 代理解析出目标 example.com:80,自己发起到源站的连接
3. 代理 → 源站: GET / HTTP/1.1 ← 转成相对路径
4. 源站 → 代理 → 客户端:原样回传响应
数据流:隧道代理处理 HTTPS(CONNECT)
1. 客户端 → 代理: CONNECT example.com:443 HTTP/1.1 ← 只给主机:端口
2. 代理 → 源站: 建立 TCP 连接到 example.com:443
3. 代理 → 客户端: HTTP/1.1 200 Connection Established
4. 此后双向裸转字节:TLS ClientHello/握手/加密数据,代理一概看不懂
命令
实验一:起一个最小正向代理,看绝对 URI
用 Python 自带的能力起一个一目了然的"伪代理"来观察请求行(生产别这么干,仅作演示):
# 终端 A:用 ncat 假装代理,把客户端发来的第一坨字节打印出来
ncat -l -k -p 8080 -c 'head -c 200 | sed "s/^/[代理收到] /"'
# 终端 B:让 curl 把它当正向代理
curl -x http://127.0.0.1:8080 http://example.com/
终端 A 会打印出客户端发给代理的原始请求行:
[代理收到] GET http://example.com/ HTTP/1.1 ← 绝对 URI,正向代理的铁证
[代理收到] Host: example.com
[代理收到] User-Agent: curl/8.5.0
[代理收到] Accept: */*
[代理收到] Proxy-Connection: Keep-Alive
看到
GET http://example.com/这个完整 URL 了吗?这是"客户端知道自己在对代理说话"的语法标志。同时注意多出来的Proxy-Connection头——这是 hop-by-hop 头,只在客户端↔代理这一跳有意义。
实验二:反向代理收到的是相对路径
用 Nginx 配一个最小反向代理:
# /etc/nginx/conf.d/demo.conf
server {
listen 8081;
location / {
proxy_pass http://example.com; # 后端写死在配置里
proxy_set_header Host example.com;
}
}
sudo nginx -s reload
curl -v http://127.0.0.1:8081/
此时如果你在 Nginx 与后端之间抓包:
sudo tcpdump -i any -n -A 'tcp port 80 and host example.com' 2>/dev/null | grep -A1 "GET"
会看到 Nginx 发给后端的是:
GET / HTTP/1.1 ← 相对路径,目标来自配置而非请求
Host: example.com
对比结论:客户端 curl http://127.0.0.1:8081/ 自以为在直连一台 8081 的服务器(origin-form),完全不知道背后是 example.com——这就是"反向代理隐藏服务端"。
实验三:HTTPS 走代理时的 CONNECT 隧道
# 复用实验一的 ncat 伪代理(终端 A 继续监听 8080)
curl -x http://127.0.0.1:8080 https://example.com/ # 注意是 https
终端 A 这次打印的第一行变成:
[代理收到] CONNECT example.com:443 HTTP/1.1 ← 隧道请求,只有主机:端口
[代理收到] Host: example.com:443
没有路径、没有方法体——因为后面是 TLS 密文,代理只被告知"帮我连到 example.com:443"。代理回一个
200 Connection Established后就闭眼搬运。这就是为什么HTTP 正向代理也能代理 HTTPS:它不解密,只开隧道。
实验四:透明代理的"无配置"特征(概念演示)
透明代理的标志是客户端不设任何代理,流量靠内核劫持。一行 iptables 就能把出站 80 端口的包"拐"到本地代理:
# 把本机发往外部 80 端口的流量,透明重定向到本地 3128(Squid 等)
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 3128
此后 curl http://example.com/(没有 -x!)也会经过本地 3128。代理用 getsockopt(SO_ORIGINAL_DST) 取回"客户端原本想去哪"。完整原理见 第11章,这里只需记住:没配代理却走了代理 = 透明代理。
# 实验完清理这条规则,别污染本机网络
sudo iptables -t nat -D OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 3128
实验小结:从一行字节反推代理类型
把上面的现象做成一张"侦探速查表"——抓到一段流量,看第一行就能判型:
| 你抓到的请求行 | 它面对的是 |
|---|---|
GET http://host/path HTTP/1.1 | 正向代理(absolute-form) |
GET /path HTTP/1.1 + Host: | 反向代理 / 直连源站(origin-form) |
CONNECT host:443 HTTP/1.1 | 隧道代理(HTTP CONNECT) |
上来就是 0x05 二进制握手 | SOCKS5 代理(见 第06章) |
| 客户端没配代理,包却进了代理端口 | 透明代理(内核劫持) |
排错 / 常见误区
误区:"正向代理和反向代理只是方向不同。" → 错。真正的区别是"客户端是否感知"和"目标从哪来"。同一个 Nginx 既能正向也能反向;方向相同但语义完全不同。
误区:"反向代理比正向代理高级/更新。" → 错。两者是不同立场的工具,没有高低。CDN(反向)和企业出网(正向)解决的是完全不同的问题。
误区:"HTTP 代理不能代理 HTTPS。" → 错。HTTP 代理用
CONNECT开隧道就能代理 HTTPS,只是看不到明文(除非做 MITM)。排查:"配了
HTTP_PROXY但不生效" → 多半是程序只认http_proxy(小写)或被NO_PROXY命中,或目标是 HTTPS 而你只配了HTTP_PROXY没配HTTPS_PROXY。环境变量代理的坑见 第08章。
本章小结
- 代理的本质:把一条端到端连接拆成两条,从而获得改写、隐藏、协议转换的能力。
- 四类代理用四个问题区分:谁配置、谁感知、目标从哪来、隐藏谁。
- 正向 vs 反向的铁证写在 HTTP 请求行:绝对 URI(正向)vs 相对路径(反向)。
- 隧道代理(CONNECT/SOCKS)只搬字节不看内容,是 HTTP 代理能代理 HTTPS 的关键。
- 透明代理靠内核劫持,客户端零配置零感知。
下一章我们把这四类代理放回 OSI 网络层级,看清楚每一类到底工作在第几层、在哪个点上"截断"了流量。
思考题:CDN 节点对终端用户是反向代理,但它"回源"去拉源站内容时,这一跳更像正向还是反向?想清楚这个,你就真懂了立场的相对性。(答案在第02章)