什么是 PAC?

PAC,一个自动代理配置脚本,包含了很多使用 JavaScript 编写的规则,它能够决定网络流量走默认通道还是代理服务器通道,控制的流量类型包括:HTTP、HTTPS 和 FTP。

PAC文件格式

PAC文件是纯文本格式的,实际上就是JavaScript文件。Chrome/Chromium的扩展Switchy!的”Auto Switch Mode”功能实际上也是创建和维护一个简单的PAC文件,但功能比较弱。

对于一般的应用,即使你几乎不懂JavaScript和编程,也可以通过本文的介绍实现基本的功能。

PAC文件FindProxyForURL函数

PAC文件中必须包含一个函数:FindProxyForURL(url, host)。

参数url是用户输入的url,参数host是url中的主机名。

比如url为http://www.lolicon.team/javascript/pac-proxy-setting,那么host就是www.lolicon.team

一个最简单的PAC文件内容如下:

1
2
3
function FindProxyForURL(url, host) {
return "DIRECT";
}

这个PAC文件实际上什么也没做,对任何URL,都将”DIRECT”(直接连网)。

PAC文件返回值类型

除了可以return “DIRECT”以外,还有两种常用方式:

PROXY proxysample.com:8080

http代理的主机和端口,主机也可以用IP表示

SOCKS5 socks5sample.com:1080

socks5代理的主机和端口,主机也可以用IP表示

那么,我们可以猜测到,用pac指定一个http代理应该这样写

1
2
3
function FindProxyForURL(url, host) {
return "PROXY 192.168.1.1:3128";
}

甚至可以指定多个代理

1
2
3
function FindProxyForURL(url, host) {
return "DIRECT; PROXY 192.168.1.1:3128; SOCKS5 linux.net:1080";
}

这句语句的意思是:

对所有URL,都直接连接;
如果不能直接连接,那么就使用192.168.1.1:3128这个http代理连接;
如果还是不能连接,则使用lilinux.net:1080这个socks5代理连接。
使用不同连接的顺序和语句中的顺序一致,你可以根据自己的实际情况更改。

也许你明确知道哪些网站不能直连,必须用PROXY或者SOCKS5连接,那么可以对站点分别指定代理配置

1
2
3
4
5
6
7
8
9
10
11
12
function FindProxyForURL(url, host) {
if (shExpMatch(url,"*.google.com/*")) {
return "PROXY 192.168.1.1:3128";
}
if (shExpMatch(url, "*.wikipedia.com:*/*")) {
return "SOCKS5 lilinux.net:1080";
}
if (isInNet(host, "10.0.0.0", "255.0.0.0")){
return "DIRECT";
}
return "DIRECT; PROXY 192.168.1.1:3128; SOCKS5 lilinux.net:1080";
}

这个PAC文件中引入了两个新的函数,但从字面意思上,我们也可以猜出代码的大概意思:

当url是.google.com/ 时,自动使用PROXY代理;
当url是.wikipedia.cm/时,自动使用SOCKS5代理;
当host是10.0.0.0 /255.0.0.0的子网内时,自动直连;
如果都不匹配,则依次按DIRECT、PROXY、SOCKS5的次序尝试。
shExpMatch函数用来匹配url或者host,匹配的方式和DOS的通配符相似。例如前面用到的”.google.com/“可以匹配任意包含”.google.com/“的字符串。

PAC 语法和函数

上面函数中,url 字段就是我们在浏览器地址栏输入的待访问地址,host 为该地址对应的 hostname,return 语句有三种指令:

  • DIRECT,表示无代理直接连接
  • PROXY host:port,表示走 host:port 的 proxy 服务
  • SOCKS host:port,表示走 host:port 的 socks 服务

而返回的接口可以是多个代理串联:

1
return "PROXY 222.20.74.89:8800; SOCKS 222.20.74.89:8899; DIRECT";

上面代理的意思是,默认走 222.20.74.89:8800 的 proxy 服务;如果代理挂了或者超时,则走 222.20.74.89:8899 的 socks 代理;如果 socks 也挂了,则无代理直接连接。从这里可以看出 PAC 的一大优势:自动容灾。

PAC 提供了几个内置的函数,下面一一介绍下:

dnsDomainIs

类似于 ==,但是对大小写不敏感,

1
2
3
4
if (dnsDomainIs(host, "google.com") || 
dnsDomainIs(host, "www.google.com")) {
return "DIRECT";
}

shExpMatch

Shell 正则匹配,* 匹配用的比较多,可以是 *.example.com,也是可以下面这样,

1
2
3
4
if (shExpMatch(host, "vpn.domain.com") ||
shExpMatch(url, "http://abcdomain.com/folder/*")) {
return "DIRECT";
}

isInNet

判断是否在网段内容,比如 10.1.0.0 这个网段,10.1.1.0 就在网段中,

1
2
3
if (isInNet(dnsResolve(host), "172.16.0.0", "255.240.0.0")) {
return "DIRECT";
}

myIpAddress

返回主机的 IP,

1
2
3
if (isInNet(myIpAddress(), "10.10.1.0", "255.255.255.0")) {
return "PROXY 10.10.5.1:8080";
}

dnsResolve

通过 DNS 查询主机 ip,

1
2
3
4
5
6
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") ||
isInNet(dnsResolve(host), "127.0.0.0", "255.255.255.0")) {
return "DIRECT";
}

isPlainHostName

判断是否为诸如 http://barret/http://server-name/ 这样的主机名,

1
2
3
if (isPlainHostName(host)) {
return "DIRECT";
}

isResolvable

判断主机是否可访问,

1
2
3
if (isResolvable(host)) {
return "PROXY proxy1.example.com:8080";
}

dnsDomainLevels

返回是几级域名,比如 dnsDomainLevels(barretlee.com) 返回的结果就是 1,

1
2
3
4
5
if (dnsDomainLevels(host) > 0) {
return "PROXY proxy1.example.com:8080";
} else {
return "DIRECT";
}

weekdayRange

周一到周五,

1
2
3
4
5
if (weekdayRange("MON", "FRI")) {
return "PROXY proxy1.example.com:8080";
} else {
return "DIRECT";
}

dateRange

一月到五月,

1
2
3
4
5
if (dateRange("JAN", "MAR"))  {
return "PROXY proxy1.example.com:8080";
} else {
return "DIRECT";
}

timeRange

八点到十八点,

1
2
3
4
5
if (timeRange(8, 18)) {
return "PROXY proxy1.example.com:8080";
} else {
return "DIRECT";
}

alert

据说这个函数可以用来调试,不过我在 Chrome 上测试并未生效,

1
2
resolved_host = dnsResolve(host);
alert(resolved_host);

isInNet函数用来返回请求的host是否在指定的域内。值得注意的是,isInNet的第二个参数必须是 IP,不能是主机名。因此需要把主机名转换成IP。比如”isInNet(host, dnsResolve(www.google.com), “255.255.255.0”)”讲到这里,应该可以解决你的问题了吧。

PAC文件可以使用的JavaScript函数

当然PAC也不止这么简单,它还提供了不少其它函数。

你也许想把pac文件发布到Internet上,这样其它用户就只需要在浏览器中指定pac文件的url即可。你得配置你的服务器映射 .pac 文件后缀到MIME类型: application/x-ns-proxy-autoconfig 如果使用的是Netscape服务器,编辑 config 目录下的 mime.types 文 件。如果是Apache, CERN or NCSA服务器,使用 AddType 指令。