代理自动配置(Proxy Auto-Configuration,PAC)文件是一个 JavaScript 文件,浏览器和其他 user agent 会执行它,以确定针对某个给定 URL 应使用哪个代理服务器(或者不使用代理)。该机制可对客户端流量如何经由代理基础设施转发实现动态且精细的控制。
理解 PAC 文件
PAC 文件把代理配置逻辑集中放在一台 Web 服务器上,从而无需在客户端手动设置代理。当客户端被配置为使用 PAC 文件时,它会在每次 HTTP/HTTPS 请求之前下载并执行该脚本。脚本的核心函数 FindProxyForURL(url, host) 返回一个字符串,指示是直连、使用某个指定代理,还是使用 SOCKS 代理。
使用 PAC 文件的好处
部署 PAC 文件带来若干运维优势:
- 精细控制:可基于 URL 模式、主机名、IP 地址、时间段或网络位置定义具体的路由规则。
- 负载均衡:将流量分摊到多台代理服务器,避免过载。
- 故障转移:在主代理不可用时指定备用代理服务器,提升可靠性。
- 绕过特定流量:让某些内部或可信域名完全绕过代理,降低延迟和资源消耗。
- 简化客户端配置:客户端只需知道 PAC 文件的 URL,部署和更新更简单。代理逻辑的变更只需在服务器上改一次,而不必逐台客户端修改。
PAC 文件的结构与语法
PAC 文件是用 JavaScript 编写的纯文本文件,通常命名为 proxy.pac 或 wpad.dat。PAC 文件的核心是 FindProxyForURL(url, host) 函数,该函数必须存在。
FindProxyForURL(url, host) 函数
该函数接收两个参数:
* url:所请求对象的完整 URL(例如 http://www.example.com/index.html)。
* host:从 URL 中提取的主机名(例如 www.example.com)。
函数必须返回一个字符串值:
DIRECT:不使用代理,直接连接目标。PROXY <host>:<port>:使用指定的 HTTP 代理服务器。SOCKS <host>:<port>:使用指定的 SOCKS 代理服务器。- 多个选项:用分号分隔多个代理服务器,或分隔代理与直连(例如
PROXY proxy1.example.com:8080; PROXY proxy2.example.com:8080; DIRECT)。客户端会按指定顺序依次尝试连接。
PAC 文件中常用的 JavaScript 函数
除标准 JavaScript 函数外,还可使用若干专为代理自动配置设计的浏览器专用函数:
isPlainHostName(host):如果host不含域名则返回true(例如localhost、intranet-server)。dnsDomainIs(host, domain):如果host属于domain则返回true(例如dnsDomainIs("www.example.com", ".example.com"))。shExpMatch(str, pattern):如果str按 shell 表达式匹配规则匹配pattern则返回true(例如shExpMatch("http://www.example.com/index.html", "http://*.example.com/*"))。isInNet(host, pattern, mask):如果host的 IP 地址位于由pattern和mask指定的 IP 网段内则返回true(例如isInNet(host, "192.168.1.0", "255.255.255.0"))。host可以是主机名或 IP 地址。myIpAddress():返回运行浏览器的机器的 IP 地址。适用于基于位置的代理决策。dnsResolve(host):将主机名host解析为 IP 地址。解析失败时返回空字符串。weekdayRange(wd1, wd2, gmt):如果当前星期几处于指定范围内则返回true。wd1、wd2为缩写(例如 "MON"、"FRI")。gmt为可选参数,表示使用 GMT 时间。dateRange(day1, month1, year1, day2, month2, year2, gmt):如果当前日期处于指定范围内则返回true。timeRange(hour1, min1, sec1, hour2, min2, sec2, gmt):如果当前时间处于指定范围内则返回true。
PAC 文件示例
本示例演示如何绕过内部域名、按规则转发特定流量以及实现故障转移。
function FindProxyForURL(url, host) {
// 定义您的代理服务器
var PRIMARY_PROXY = "PROXY proxy.example.com:8080";
var SECONDARY_PROXY = "PROXY backup-proxy.example.com:8080";
var SOCKS_PROXY = "SOCKS socks.example.com:1080";
// 1. 本地主机与内部域名走直连
// 对纯主机名绕过代理(例如 "localhost"、"intranet-server")
if (isPlainHostName(host)) {
return "DIRECT";
}
// 对特定内部域名绕过代理
if (dnsDomainIs(host, ".internal-domain.com")) {
return "DIRECT";
}
// 对特定内网 IP 段绕过代理
// 注意: myIpAddress() 返回客户端 IP。此处判断的是 *目标* 是否为内网。
// 若要判断客户端 IP,请使用 myIpAddress() 与 isInNet。
if (isInNet(dnsResolve(host), "10.0.0.0", "255.0.0.0") ||
isInNet(dnsResolve(host), "172.16.0.0", "255.240.0.0") ||
isInNet(dnsResolve(host), "192.168.0.0", "255.255.0.0")) {
return "DIRECT";
}
// 2. 对特定应用或协议使用 SOCKS 代理
// 示例: 将全部 FTP 流量经 SOCKS 代理转发
if (url.substring(0, 4) == "ftp:") {
return SOCKS_PROXY;
}
// 3. 将特定外部域名经主代理转发并配置故障转移
// 示例: 将 partner-site.com 的流量经主代理转发,
// 并回退到备用代理。
if (dnsDomainIs(host, ".partner-site.com")) {
return PRIMARY_PROXY + "; " + SECONDARY_PROXY;
}
// 4. 屏蔽特定 URL(通过返回不存在的代理或错误)
// 注意: 部分浏览器可能把 "PROXY 0.0.0.0:0" 视为屏蔽。
// 更可靠的屏蔽机制通常由代理自身实现。
if (shExpMatch(url, "*bad-site.com*")) {
// 返回一个不存在的代理以实现屏蔽,或使用专用的屏蔽代理
return "PROXY 127.0.0.1:1"; // 或使用专用的屏蔽代理
}
// 5. 默认规则: 其余全部流量经主代理转发并带故障转移
return PRIMARY_PROXY + "; " + SECONDARY_PROXY + "; DIRECT";
}
部署与分发
客户端要使用 PAC 文件,必须先被配置为去获取它。
通过 HTTP/HTTPS 服务器
最常见的方式是把 PAC 文件托管在标准 Web 服务器(Apache、Nginx、IIS)上,并在客户端浏览器或操作系统中配置其 URL。
* 服务器配置:确保服务器以正确的 MIME type 提供 PAC 文件:application/x-ns-proxy-autoconfig。
* 客户端配置:在浏览器设置(或操作系统网络设置)中选择"使用自动代理配置脚本"选项,并填写完整 URL(例如 http://proxyconfig.example.com/proxy.pac)。
Web Proxy Auto-Discovery Protocol(WPAD)
WPAD 允许客户端在无需手动配置的情况下自动发现 PAC 文件的 URL。通常包括:
1. DNS:创建指向托管 wpad.dat 的 Web 服务器的 wpad.<domain> DNS 记录。
2. DHCP:配置 DHCP 选项(选项 252)来提供 PAC 文件 URL。
WPAD 虽然方便,但存在安全隐患(例如可能出现恶意 WPAD 服务器),因此常被禁用或谨慎使用。出于安全性和明确可控的考虑,一般更推荐直接配置 URL。
Windows 的组策略对象(GPO)
在 Windows 域环境中,可通过组策略把 PAC 文件 URL 集中下发到客户端计算机:
* 依次进入 User Configuration > Policies > Windows Settings > Internet Explorer Maintenance > Connection > Proxy Settings。
* 启用 "Automatic configuration" 并填写 PAC 文件 URL。
PAC 文件排错
PAC 文件的问题通常源于语法错误、缓存或网络故障。
- 语法错误:即使是很小的 JavaScript 错误也会导致 PAC 文件失效。请使用 JavaScript linter 或专用的 PAC 文件校验工具。浏览器可能会在开发者控制台中记录错误(例如 Chrome/Firefox 中按 F12)。
- MIME type 不正确:如果 Web 服务器以错误的 MIME type 提供 PAC 文件,客户端可能无法正确处理。请核对
application/x-ns-proxy-autoconfig。 - 缓存问题:浏览器会积极缓存 PAC 文件。修改后请清除浏览器缓存或强制刷新(例如
Ctrl+F5)。部分浏览器提供在调试时禁用 PAC 文件缓存的选项。 - 网络可达性:确认客户端能够访问托管 PAC 文件的 Web 服务器。核对 DNS 解析和防火墙规则。
FindProxyForURL函数:确认FindProxyForURL(url, host)函数定义正确并返回有效的代理字符串。- 测试工具:浏览器开发者工具(Network 标签页)可显示某个请求实际使用了哪个代理。在线 PAC 文件测试工具可针对您的脚本模拟请求。
PAC 文件与其他代理配置方式的对比
| 特性 | PAC 文件 | 直接代理设置 | WPAD |
|---|---|---|---|
| 配置方式 | JavaScript 脚本 URL | 手动填写 IP/端口 | 通过 DNS/DHCP 自动发现 |
| 精细控制 | 高(基于逻辑) | 低(全部流量走同一代理或直连) | 高(使用 PAC 文件) |
| 故障转移/负载均衡 | 支持(内置于脚本逻辑) | 不支持(单一代理) | 支持(内置于 PAC 文件逻辑) |
| 绕过规则 | 支持(内置于脚本逻辑) | 手动维护排除列表 | 支持(内置于 PAC 文件逻辑) |
| 部署复杂度 | 中等(在 Web 服务器托管文件并配置 URL) | 低(逐台客户端手动配置) | 高(DNS/DHCP 配置、Web 服务器) |
| 维护 | 集中式(在服务器上编辑脚本) | 分散式(逐台客户端手动维护) | 集中式(在服务器上编辑脚本、DNS/DHCP) |
| 安全考量 | 脚本注入、明文分发 | 基础 | DNS/DHCP 欺骗(风险更高)、脚本注入 |
安全考量
- 完整性:PAC 文件包含敏感的路由信息。请确保 PAC 文件托管在安全可信的 Web 服务器上,并通过 HTTPS 分发,以防下载过程中被篡改或截获。
- 恶意脚本:被篡改的 PAC 文件可将客户端流量重定向到恶意代理,从而便利中间人攻击、凭据窃取或数据外泄。只使用来自可信来源的 PAC 文件。
- 信息泄露:避免在可公开访问的 PAC 文件中包含敏感的内网细节。文件本身虽然是代码,但其逻辑可能暴露网络拓扑。
- WPAD 漏洞:如果攻击者能够控制 DNS 或 DHCP 响应,WPAD 的自动发现机制就可能被利用,把客户端引导至恶意 WPAD 服务器。请谨慎使用 WPAD 并配套适当的安全措施。
