Netpath
学习路径4 / 49 · 科学上网与隧道看全程 →
原理入门预计 30 分钟

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

线已经通了,回头看配置里抄过的那些 http:// 与 socks5h://:一个懂 HTTP,一个只搬字节。

学完这节你能做到

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

建议先学

跳过这几节会看不懂本节的部分推导

先分清三种"代理"

线已经搭通了,Mihomo 也跑起来了。但前面两节里有一堆东西是照着配的: mixed-port 到底同时听了几种协议、socks5 和 socks5h 差那一个字母是什么意思、 为什么 ping 走不了代理。这一节把它们补上。

先从最容易混的名词开始 —— 同一个"代理"在不同场景里指的是完全不同的东西:

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

后面 K8s 那一段讲的 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 配置 那节配过)

实操:让各个工具都走代理

假设本地代理是 127.0.0.1:1080(SOCKS5)和 127.0.0.1:8080(HTTP)。 先给自己配一组开关,比每次手敲省事:

proxy()   { export http_proxy=http://127.0.0.1:8080 https_proxy=$http_proxy; echo "proxy on"; }
unproxy() { unset http_proxy https_proxy; echo "proxy off"; }
with_proxy() { http_proxy=http://127.0.0.1:8080 https_proxy=http://127.0.0.1:8080 "$@"; }
# 用法:with_proxy curl -I https://example.com

上表里那些不认环境变量的程序,各有各的配法:

# Git(HTTPS 协议)
git config --global http.proxy socks5h://127.0.0.1:1080
# 只给某个域名走代理,避免影响内网仓库
git config --global http.https://github.com/.proxy socks5h://127.0.0.1:1080
# Git(SSH 协议)—— ssh 不认环境变量,写进 ~/.ssh/config
Host github.com
  User git
  ProxyCommand nc -x 127.0.0.1:1080 %h %p
  # 或者 ProxyCommand socat - SOCKS4A:127.0.0.1:%h:%p,socksport=1080
# Docker daemon 是系统服务,环境变量对它无效
# /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:8080"
Environment="HTTPS_PROXY=http://127.0.0.1:8080"
Environment="NO_PROXY=localhost,127.0.0.1,.internal,10.0.0.0/8"
# apt —— /etc/apt/apt.conf.d/95proxy
Acquire::http::Proxy "http://127.0.0.1:8080";
Acquire::https::Proxy "http://127.0.0.1:8080";

改完 Docker 的记得 systemctl daemon-reload && systemctl restart docker。

×容器里的 127.0.0.1 不是宿主机的 127.0.0.1

在容器或 Pod 里把代理配成 127.0.0.1:8080 一定不通 —— 那是容器自己的 loopback。 要用宿主机地址:Docker Desktop 用 host.docker.internal, Linux 上用 docker0 的网关地址(通常 172.17.0.1)或者 --network host。

同理,docker build 时用的是 daemon 的网络与代理配置,不是你 shell 里的环境变量, 要用 --build-arg HTTP_PROXY=... 传进去。这两件事每个人都会踩一次。

检查点

Checkpoint单选

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

Checkpoint单选

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

Checkpoint单选

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

Checkpoint单选

在容器里把代理配成 http://127.0.0.1:8080,结果完全不通。为什么?

这节课的落点

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

延伸资料