NNetpath
原理入门预计 30 分钟

HTTP 代理与 SOCKS5:两种代理差在哪

一个懂 HTTP,一个只搬字节。搞清这点,配置里那些 http:// 与 socks5h:// 就不再靠猜。

学完这节你能做到

  • 说清正向代理、反向代理与透明代理三者的区别
  • 解释 HTTP CONNECT 隧道与普通 HTTP 代理的差异
  • 判断某个场景下域名该由本地还是代理侧解析(socks5 vs socks5h)

先分清三种"代理"

同一个词在不同场景里指的是完全不同的东西:

类型谁配置它典型用途
正向代理客户端主动指定客户端要访问的目标它自己去不了,或不想直接去
反向代理服务端部署,客户端不知情Nginx、Ingress —— 客户端以为它就是服务器
透明代理网络上强插,两端都不知情网关上做统一分流,设备不用改配置

L1 里讲的 Ingress 是反向代理,这一节讲的是正向代理。两者的方向完全相反: 反向代理保护和分发服务端,正向代理代表客户端出门。

HTTP 代理:它看得见你的 URL

普通的 HTTP 请求,请求行里只有路径:

GET /index.html HTTP/1.1
Host: example.com

走 HTTP 代理时,请求行变成完整 URL(absolute-form),因为代理得知道该替你连谁:

GET http://example.com/index.html HTTP/1.1
Host: example.com
Proxy-Connection: keep-alive

代理能看到完整 URL、能改请求头、能缓存、能按路径做策略——这是它的能力也是它的边界: 仅限明文 HTTP。

CONNECT:把代理降级成一根管子

HTTPS 的内容是加密的,代理没法改写,于是有了 CONNECT 方法:

CONNECT example.com:443 HTTP/1.1
Host: example.com

HTTP/1.1 200 Connection Established

这之后代理不再理解里面的内容,只在两个 socket 之间搬字节。TLS 握手是客户端和真实服务器直接完成的, 代理看不到证书、看不到路径、看不到 body。

i代理日志里能看到什么

这解释了一个常见困惑:企业代理的日志里,HTTP 请求能看到完整 URL, HTTPS 请求却只有一行 CONNECT example.com:443

能看到域名(CONNECT 的目标里有),看不到路径和内容。 想看内容只能做 TLS 中间人——那需要在客户端安装代理的根证书。

CONNECT 的目标可以是任意端口,不限于 443。所以 HTTP 代理其实是个通用 TCP 隧道—— 很多工具就靠这一点把 SSH、数据库连接也塞进 HTTP 代理里。

SOCKS5:不理解协议,只管转发

SOCKS5 工作在更低的层次:它不解析应用层协议,只负责"帮我连到某个地址的某个端口"。

一次连接分三步:

1. 客户端: 我支持这些认证方式 [无认证, 用户名密码]
   代理:   用「无认证」

2. (如需认证)用户名 + 密码

3. 客户端: CONNECT 到 example.com:443
           ↑ 地址类型可以是 IPv4 / IPv6 / 域名
   代理:   连上了,源地址是 x.x.x.x

三个关键点:

  • 地址类型可以直接是域名(ATYP=0x03),不需要客户端先解析
  • 支持 UDP(UDP ASSOCIATE),HTTP 代理做不到
  • 不区分协议,任何 TCP 应用都能走
维度HTTP 代理SOCKS5
理解应用协议是(明文 HTTP)
支持任意 TCP靠 CONNECT原生
支持 UDP
能改写内容 / 缓存能(明文时)不能
认证Proxy-Authorization内置用户名密码
客户端支持面极广(环境变量即可)需要程序自己支持

socks5 与 socks5h:一个字母的大坑

这是配置里最容易出错、症状最迷惑的一处:域名由谁来解析?

写法谁解析域名
socks5://本地解析成 IP,再让代理连这个 IP
socks5h://域名原样交给代理,由代理侧解析

curl 对应两个不同的参数:

# 本地解析(用本机 DNS)
curl --socks5 localhost:1080 https://example.com

# 代理侧解析(h = hostname)
curl --socks5-hostname localhost:1080 https://example.com
×用 socks5 而不是 socks5h 时的典型故障

本机 DNS 解析出来的 IP 可能是错的、被污染的,或者是一个只在代理那一侧才有意义的内网地址。

症状:代理明明是通的,某些域名就是访问不了,而 IP 直连却正常。 换成 socks5h 立刻好——因为域名交给了能正确解析它的那一侧。

规则很简单:代理的出口在哪,域名就该在哪解析。 除非你明确需要本地解析 (比如访问只有本地 DNS 知道的内网域名)。

环境变量:约定而非标准

大多数命令行工具认这套环境变量,但它是约定,没有 RFC,所以细节各家不同:

export HTTP_PROXY=http://127.0.0.1:8080
export HTTPS_PROXY=http://127.0.0.1:8080     # 注意:值通常仍是 http://
export ALL_PROXY=socks5h://127.0.0.1:1080
export NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,.internal,.cluster.local

四个反复踩到的坑:

  • HTTPS_PROXY 的值一般还是 http://——它表示"访问 https 时用哪个代理", 而不是"用 https 连代理"。
  • 大小写都要设。curl 只看小写的 http_proxy,其它工具偏好大写。稳妥做法是两套都设。
  • NO_PROXY 的语法各家不一致:有的支持 CIDR,有的只支持后缀匹配, 有的要求写 .example.com 才能匹配子域。K8s 环境里一定要包含 10.0.0.0/8.svc.cluster.local,否则集群内部请求也会绕出去。
  • 改了环境变量对已运行的进程无效。systemd 服务要改 unit 的 Environment=, Docker daemon 要改 /etc/systemd/system/docker.service.d/http-proxy.conf

压根不认代理的那些程序

环境变量只对"自己去读它"的程序有效。下面这些完全不理:

程序为什么怎么办
ping / tracerouteICMP,不是 TCP没有代理概念,改测 curl
dig / nslookupUDP 53用 DoH/DoT,或让代理侧解析
ssh自己一套配置ProxyCommand nc -x host:port %h %p
git over ssh走的是 ssh同上,配在 ~/.ssh/config
apt / yum自己的配置文件/etc/apt/apt.conf.d/95proxy
Docker daemon是系统服务改 systemd drop-in 后重启
静态编译的二进制可能没实现proxychains,或上 TUN 模式

兜底手段有两个层次:

  • proxychains / tsocks —— 用 LD_PRELOAD 劫持 socket 调用,对动态链接的程序有效
  • TUN 模式 —— 建一张虚拟网卡把流量整体接走,对所有程序有效,也是客户端软件里 "TUN / 虚拟网卡"选项存在的原因(后面 Mihomo 那节会讲)

检查点

检查点单选

企业代理的访问日志里,为什么 HTTPS 请求只能看到域名而看不到 URL 路径?

检查点单选

配了 SOCKS5 代理,IP 直连正常,但某些域名死活访问不了。最该先改什么?

检查点单选

在 K8s 节点上设置 HTTP_PROXY 后,集群内部请求开始报错。最可能漏了什么?

这节课的落点

  • 正向代理代表客户端出门,反向代理代表服务端接客,透明代理两端都不知情
  • HTTP 代理能看到明文 URL;HTTPS 走 CONNECT,代理只剩搬字节的能力(域名可见、内容不可见)
  • SOCKS5 不理解应用协议,支持任意 TCP 与 UDP,但需要程序自己支持
  • socks5 本地解析、socks5h 代理侧解析 —— 出口在哪,域名就在哪解析
  • 代理环境变量是约定不是标准:大小写都设、NO_PROXY 写全、改完要重启服务
  • ping/dig/ssh/apt/Docker daemon 都不认环境变量,各有各的配法;兜底靠 proxychains 或 TUN

延伸资料