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:抓包与命令速查

第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

四个高频坑:

  1. https_proxy 的值是 http://,不是 https://。 它表示"用这个 HTTP 代理去处理 HTTPS 请求"(代理本身用明文 HTTP 接收 CONNECT)。写成 https:// 反而表示"与代理之间也要 TLS",多数代理不支持,会连不上。

  2. 大小写之争。 事实标准是小写(http_proxy)。curl 同时认大小写,但有意忽略 HTTP_PROXY(大写)——因为 CGI 环境会把请求头 Proxy: 注入成 HTTP_PROXY 环境变量,造成 Httpoxy 漏洞(CVE-2016-5385 等)。所以:统一用小写最安全。

  3. all_proxy 走 SOCKS。 当只设 all_proxy=socks5h://...(注意 socks5h = 远端 DNS,对应 第03章)时,所有协议兜底走 SOCKS。

  4. 不是所有程序都读环境变量。 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 不认 PACcurl 不支持 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 代理 开始,深入"字节到底怎么在两条连接间搬运"。

Prev
第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战