第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY
学习目标
- 理清客户端"发现代理"的五种机制:手动、环境变量、PAC、WPAD、系统代理
- 会写 PAC 文件用
FindProxyForURL做按规则分流 - 吃透环境变量代理的坑:大小写、
http_proxy值的 scheme、all_proxy、Httpoxy - 掌握
NO_PROXY的匹配规则,避免内网/集群域名被错误地走代理 - 理解 WPAD 自动发现的便利与它带来的中间人安全风险
前置知识
- 第04章 HTTP 代理协议、第03章 谁解析 DNS
- 后续 第25章 Docker 里的代理 会大量用到本章的环境变量与
NO_PROXY
原理
客户端怎么"知道"该不该用代理、用哪个?从最手动到最自动,五种机制:
① 手动静态 在浏览器/系统里填死代理地址
② 环境变量 http_proxy / https_proxy / no_proxy(命令行工具、容器、CI 的主力)
③ PAC 文件 一段 JS,按 URL/主机动态决定用哪个代理或直连
④ WPAD 自动发现 通过 DHCP/DNS 自动找到 PAC 文件的 URL(零配置,但有安全风险)
⑤ 系统代理 操作系统级设置,应用统一遵循(macOS/Windows/GNOME)
② 环境变量代理——命令行与容器的事实标准
curl、wget、git、各语言 SDK、Docker、CI 大多认这套环境变量:
export http_proxy=http://10.0.0.1:8080 # 代理"明文HTTP请求"
export https_proxy=http://10.0.0.1:8080 # 代理"HTTPS请求"——注意值通常仍是 http://!
export all_proxy=socks5://10.0.0.1:1080 # 兜底,常用于 SOCKS
export no_proxy=localhost,127.0.0.1,.svc.cluster.local,10.0.0.0/8
四个高频坑:
https_proxy的值是http://,不是https://。 它表示"用这个 HTTP 代理去处理 HTTPS 请求"(代理本身用明文 HTTP 接收CONNECT)。写成https://反而表示"与代理之间也要 TLS",多数代理不支持,会连不上。大小写之争。 事实标准是小写(
http_proxy)。curl同时认大小写,但有意忽略HTTP_PROXY(大写)——因为 CGI 环境会把请求头Proxy:注入成HTTP_PROXY环境变量,造成 Httpoxy 漏洞(CVE-2016-5385 等)。所以:统一用小写最安全。all_proxy走 SOCKS。 当只设all_proxy=socks5h://...(注意socks5h= 远端 DNS,对应 第03章)时,所有协议兜底走 SOCKS。不是所有程序都读环境变量。 Java 默认不读,要
-Dhttp.proxyHost=... -Dhttp.proxyPort=...;很多 GUI 程序、某些 Go 程序也各有各的读法。"设了环境变量没生效"十有八九是程序根本不读它。
③ PAC:用一段 JS 动态决定走哪个代理
PAC(Proxy Auto-Config)是一个 .pac/.dat 文件,里面有个固定入口函数:
function FindProxyForURL(url, host) {
// 内网直连,不走代理
if (isInNet(host, "10.0.0.0", "255.0.0.0") ||
shExpMatch(host, "*.internal.corp")) {
return "DIRECT";
}
// 特定网站走专用代理
if (dnsDomainIs(host, ".video.example.com")) {
return "PROXY video-proxy:3128";
}
// 其余走主代理,挂了则直连兜底(分号分隔,按序回退)
return "PROXY proxy-a:8080; PROXY proxy-b:8080; DIRECT";
}
- 返回值:
DIRECT(直连)、PROXY host:port、SOCKS host:port、SOCKS5 host:port,可用;串成回退链 - 内置辅助函数:
isInNet、dnsDomainIs、shExpMatch(通配匹配)、myIpAddress、dnsResolve、weekdayRange等 - 威力:能按"目标域名/IP/网段、本机 IP、时间"动态分流——比静态配置灵活得多,是企业大规模分流的主力
- 注意:浏览器原生支持 PAC,但
curl/wget不支持 PAC(它们只认环境变量/静态)。要测 PAC 用pacparser或浏览器。
④ WPAD:零配置自动发现 PAC——便利与风险并存
WPAD(Web Proxy Auto-Discovery)让客户端无需任何配置就找到 PAC 文件。浏览器开"自动检测代理"时,会按序尝试:
1. DHCP:向 DHCP 服务器请求 option 252,拿到 PAC 文件 URL
2. DNS:依次查 wpad.<本机域名>,如 wpad.dept.corp.com → http://wpad.dept.corp.com/wpad.dat
⚠️ 安全风险(重点):WPAD 的发现过程没有认证。如果攻击者能在局域网响应 DHCP option 252,或注册一个 wpad.<domain> 主机,就能让全网客户端把流量发到攻击者的代理——一举实现对所有人的中间人。历史上多个高危漏洞(如 BadTunnel)与之相关。所以:
- 企业应在内网 DNS 显式占用/管控
wpad记录,禁止外部解析 - 高安全环境建议关闭 WPAD 自动检测,改用受控的 PAC URL 或静态配置
⑤ 系统代理——操作系统级的统一设置
各 OS 有自己的系统代理存储,"系统代理"开启后,遵循它的应用统一走代理:
- macOS:
networksetup -setwebproxy/scutil --proxy读取 - Windows:WinINET(GUI 应用)/ WinHTTP(服务),
netsh winhttp管理 - Linux GNOME:
gsettings set org.gnome.system.proxy ...
坑在于:"系统代理"和"环境变量代理"是两套,互不自动同步。GUI 浏览器走系统代理,命令行 curl 走环境变量——经常一个能上网另一个不能,就是这个原因。
优先级与 NO_PROXY 例外
no_proxy / NO_PROXY 定义"哪些目标不走代理",匹配规则(各实现略有差异,取最保守写法):
no_proxy=localhost,127.0.0.1,::1,.example.com,10.0.0.0/8,*.svc.cluster.local
- 域名后缀:
.example.com或example.com匹配其子域(写法因实现而异,.前缀最稳) - IP / CIDR:
10.0.0.0/8(注意:不是所有客户端都支持 CIDR,老curl/某些 SDK 只认精确 IP) *:值为*表示全部直连- 端口:部分实现支持
host:port精确到端口
K8s/容器场景的头号坑(第25章):设了
HTTP_PROXY让 Pod 能出网,却忘了把集群内网域名/网段加进NO_PROXY,导致 Pod 访问kubernetes.default.svc、API Server、内部服务时也被发去外部代理 → 全部超时。NO_PROXY必须包含.svc、.cluster.local、Pod/Service CIDR、节点网段、169.254.169.254(云元数据)。
️ 实现 / 命令
实验一:环境变量代理 + NO_PROXY 例外
export http_proxy=http://127.0.0.1:3128
export https_proxy=http://127.0.0.1:3128
export no_proxy=localhost,127.0.0.1,.internal.test
# 走代理(-v 里能看到 "Connected to 127.0.0.1 (proxy)")
curl -v http://example.com/ 2>&1 | grep -i "proxy\|Connected"
# 命中 no_proxy → 直连,不经代理
curl -v http://svc.internal.test/ 2>&1 | grep -i "proxy\|Connected"
# Connected to svc.internal.test ... 没有走 127.0.0.1,说明直连了
实验二:本地起一个 PAC 文件并测试
# 写一个最小 PAC
cat > /tmp/proxy.pac <<'EOF'
function FindProxyForURL(url, host) {
if (shExpMatch(host, "*.internal.test")) return "DIRECT";
return "PROXY 127.0.0.1:3128; DIRECT";
}
EOF
# 用 pacparser 测试不同 URL 的判定(apt install pacparser / pip install pypac)
pactester -p /tmp/proxy.pac -u http://example.com/ # → PROXY 127.0.0.1:3128; DIRECT
pactester -p /tmp/proxy.pac -u http://x.internal.test/ # → DIRECT
实验三:查看与设置系统代理(macOS 示例)
# 读当前系统代理
scutil --proxy
# 设/清 Web 代理
sudo networksetup -setwebproxy "Wi-Fi" 127.0.0.1 3128
sudo networksetup -setwebproxystate "Wi-Fi" off
实验四:排查 WPAD 是否被启用/劫持
# 看本网段是否有人在应答 wpad(正常企业内网应由 IT 受控)
dig +short wpad.$(hostname -d)
# 若解析到一个陌生 IP,且你没主动配代理,警惕 WPAD 劫持
# 看 DHCP 是否下发了 option 252(代理自动配置 URL)
# (需在获取租约时抓包:nmap --script broadcast-dhcp-discover 等)
排错
| 现象 | 根因 | 解决 |
|---|---|---|
设了 HTTP_PROXY(大写)curl 不走代理 | curl 故意忽略大写(Httpoxy 防护) | 改用小写 http_proxy |
| HTTPS 不走代理、HTTP 走 | 只设了 http_proxy 没设 https_proxy | 两个都设 |
https_proxy=https://... 连不上 | 值不该带 https scheme | 改成 http://代理 |
| Pod 访问内部服务全超时 | NO_PROXY 没含 .svc/集群网段 | 补全集群域名与 CIDR(第25章) |
| Java 程序无视环境变量代理 | Java 不读环境变量 | 加 -Dhttp.proxyHost/-Dhttp.proxyPort/-Dhttp.nonProxyHosts |
| GUI 能上网、命令行不能(或反之) | 系统代理 ≠ 环境变量代理 | 两套都配,或统一来源 |
| 莫名其妙所有流量走了未知代理 | WPAD 被劫持 | 关 WPAD 自动检测;查 wpad DNS/DHCP |
curl 不认 PAC | curl 不支持 PAC | 用环境变量/静态;PAC 仅浏览器/支持库 |
本章小结
- 代理发现五机制:手动、环境变量、PAC、WPAD、系统代理;命令行/容器靠环境变量,浏览器/企业靠 PAC/WPAD。
- 环境变量四坑:统一小写(防 Httpoxy)、
https_proxy值用http://、all_proxy走 SOCKS、很多程序不读环境变量(Java 等)。 - PAC 用
FindProxyForURL做灵活分流;WPAD 零配置但易被劫持做中间人,高安全环境应关闭。 NO_PROXY必须涵盖内网/集群域名与网段,这是容器场景最常见的"出网把内网也代理了"故障源。
协议篇至此完结——HTTP、TLS、SOCKS、HTTP2/3、自动配置五大协议面你已尽收。下一篇 第三篇·网络层级与转发原理,从 第09章 L4 代理 开始,深入"字节到底怎么在两条连接间搬运"。