HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 代理技术全栈手册

    • 代理技术全栈手册 - HiHuo
    • 原理篇

      • 第01章 代理是什么:正向 / 反向 / 透明 / 隧道的统一模型
      • 第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量
      • 第03章 一个请求穿过代理的一生:连接生命周期全景
    • 协议篇

      • 第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池
      • 第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS
      • 第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
      • 第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战
      • 第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY
    • 层级与转发篇

      • 第09章 L4 代理:TCP/UDP 转发与连接级负载均衡
      • 第10章 L7 代理:协议感知与基于内容的路由
      • 第11章 透明代理:iptables REDIRECT/DNAT、TPROXY 与 eBPF 劫持
      • 第12章 数据搬运的艺术:splice / sendfile / 零拷贝 / io_uring
    • 组件横评篇

      • 第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程
      • 第14章 HAProxy:L4/L7、ACL、健康检查与 stick table
      • 第15章 Envoy:xDS 动态配置与 filter chain,为何是云原生数据面
      • 第16章 Traefik / Caddy:自动服务发现与自动 HTTPS
      • 第17章 Squid 与正向/缓存代理:企业出网、缓存与审计
      • 第18章 mitmproxy:抓包、改包、脚本化调试
      • 第19章 内网穿透与隧道:frp / gost / SSH 隧道 / ngrok
      • 第20章 科学上网生态的技术原理(技术中立)
    • 多语言手写篇

      • 第21章 Go:100 行手写 HTTP/CONNECT + SOCKS5 代理
      • 第22章 Rust:基于 tokio 的高性能 TCP 代理
      • 第23章 Python:asyncio 实现,适合调试与脚本
      • 第24章 C:epoll 裸写与零拷贝,及语言选型对比
    • 容器与K8s篇

      • 第25章 Docker 里的代理:HTTP_PROXY、build/pull 与 daemon 配置
      • 第26章 Sidecar 与流量劫持:Istio init-container 的 iptables 原理
      • 第27章 Ingress 与南北流量:Ingress-nginx 与 Gateway API
      • 第28章 Egress 与出网治理:出口网关、registry mirror、审计
      • 第29章 Service Mesh 数据面:Envoy Sidecar 全链路
    • 进阶篇

      • 第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
      • 第31章 性能调优:并发模型、连接池、超时与重试、压测
      • 第32章 排错决策树:502 / 504 / 握手失败 / 环路 / 泄漏
      • 第33章 代理安全:开放代理、SSRF、凭证泄漏与攻击面
    • 底层机制篇

      • 第34章 代理的背压与流控:一个代理最难的部分
      • 第35章 socket 与 TCP 状态机:半关闭、超时、连接生命周期
      • 第36章 HTTP/2 帧、流控与 HPACK:h2 代理的内部机制
      • 第37章 负载均衡算法推导与韧性状态机
      • 第38章 Capstone:把玩具代理改造成生产级骨架
    • 综合实战篇

      • 第39章 企业多跳转发链:拓扑、协议矩阵与贯穿性难题
      • 第40章 端到端实战:把 6 类流量全代理通
      • 第41章 更刁钻的流量:gRPC、长轮询、WebRTC、大文件、双向流
      • 第42章 可落地完整参考实现:一套能跑的多协议转发栈
    • 附录

      • 附录 A:代理协议报文速查(HTTP / SOCKS / PAC / PROXY protocol)
      • 附录 B:组件选型决策树
      • 附录 C:抓包与命令速查

第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 二进制,既能当正向代理也能当反向代理。真正的分水岭是两条:

  1. 谁知道代理在那儿:正向代理对客户端是"显式"的;反向代理对客户端是"透明"的(客户端以为在直连服务器)。
  2. 目标从哪来:正向代理的目标写在客户端的请求里(你告诉它去哪);反向代理的目标写在代理的配置里(运维告诉它去哪)。

这条差异会直接体现在 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 / L5L7L3-L7L4(隧道内)
代表实现Squid、ClashNginx、HAProxyIstio、网关劫持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章)

Next
第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量