how to write and test proxy pac file - downgoon/hello-world GitHub Wiki

背景需求

在公司访问外网,必须走企业网关,否则出不去(但是22号端口可以出去,因为能直接SSH访问云主机)。 在家,自然是直接访问,无需网关。 但是无论在家,还是在公司,要访问 google.com 都必须翻墙。

要求:

  • 在公司,在家都能访问国内外网站。
  • 在公司和在家用同一个PAC文件,不要两个文件切来切去。

总结下网路访问策略链路:

序号 目标网站 环境 网路
1 公司内网主机 在家 X(拨公司VPN)
2 国内网站(比如百度) 在家 直接访问
3 国外网站(比如Google) 在家 私人网关
4 公司内网主机 在公司 直接访问
5 国内网站(比如百度) 在公司 公司网关或私人网关
6 国外网站(比如Google) 在公司 私人网关

备注

  • 对于第5项,既可以走公司网关,又可以走私人网关。如果访问百度,先私人网关到美国节点,再从美国访问国内百度,绕路了,而且浪费私人网关的流量。

  • 在公司环境时,私人网关必须是22号端口的,否则第一条就出不去。


PAC 脚本

脚本正常工作的前提是:

家里的网络IP地址是以“192.168.X.X”开头的;而公司的网络IP地址不是“192.168.X.X”开头的,比如可以是“10.X.X.X”。

/*
REFER: http://findproxyforurl.com/pac-functions/
*/

function FindProxyForURL(url, host) {

	///////////////////////////////////////////////////////////////
	//// Section 1: GLOBAL CONFIG
	///////////////////////////////////////////////////////////////

	// gateway settings
  COMPANY_GATEWAY = "PROXY 10.199.45.38:8080";
	OVERSEA_GATEWAY = "PROXY 12.45.55.67:22";

	// direct acess for company hosts
	COMPANY_HOSTS=[
		"mycompany.net"
	];

	// outside gateway access for oversea hosts
  OVERSEA_HOSTS=[
				"google.com"
				,"github.com","github.io","githubusercontent.com"
				,"facebook.com"
	];


    ///////////////////////////////////////////////////////////////
	//// Section 2: Gateway Routing Logic
	///////////////////////////////////////////////////////////////

	host = host.toLowerCase();

	/*
	PRECONDITION REQUIRED:
	my ip address in home always begins with '192.168.1.X',
		while '10.X.X.X' in company.
	*/
	var isHome = isInNet(myIpAddress(), "192.168.1.0", "255.255.255.0" );

	if (isHome) { // in home
    return isInSet(host, OVERSEA_HOSTS) ? OVERSEA_GATEWAY : "DIRECT";
	}

	// in company
	if (isInSet(host, COMPANY_HOSTS)) {
		 return "DIRECT";
	}

	if (isInSet(host, OVERSEA_HOSTS)) {
		return OVERSEA_GATEWAY;
	}

	return COMPANY_GATEWAY;

}



///////////////////////////////////////////////////////////////
//// Section 3: User Defined Function
///////////////////////////////////////////////////////////////

/*
Hash Search
@param host: the host of url
@param set: domain string array, such as ["example.com", "helloworld.org"]
*/
function isInSet(host, set) {
  return isInSetHash(host, set); // isInSetLoop(host, set)
}

function isInSetHash(host, set) {
  var domain = getDomain(host);
  return set.indexOf(domain) != -1;
}


/*
return 'example.com' if given host assigned to 'sub.example.com'
*/
function getDomain(host) {
  var blocks = host.split(".");
  if (blocks.length < 3) {
    return host;
  } else {
      return blocks[blocks.length - 2] + "." + blocks[blocks.length - 1];
  }
}

/*
another implementation: sequential search
@Depraved
*/
function isInSetLoop(host, set) {
	for(i = 0; i < set.length; i++){
    if( dnsDomainIs(host, set[i] ) ) {
			return true;
		}
	}
	return false;
}

简单解释下,以上脚本有四个配置项:

  • COMPANY_GATEWAY: 表示企业网关(公司内部的代理服务器地址)。
  • OVERSEA_GATEWAY: 访问国外网站需要的网关地址。
  • COMPANY_HOSTS: 公司内网域名列表。
  • OVERSEA_HOSTS: 外国网站列表。

测试PAC文件

PAC文件本质是Javascript代码,但是写完放入浏览器运行,即使错误了,也没有提示。如何调试PAC呢?

有开源项目: https://github.com/pacparser/pacparser

安装 pacparser

安装文档:https://github.com/pacparser/pacparser/blob/master/INSTALL

官方提供了C的安装和Python的安装,我们这里选Python:

$ git clone https://github.com/pacparser/pacparser
$ make -C src pymod
$ sudo make -C src install-pymod

pacparser python module:
  ------------------------------------------------
  To compile and install pacparser python module:
    => make -C src pymod
    => sudo make -C src install-pymod

使用 pacparser

官方提供的是SDK,可以写python代码,比如:

$ python

>>> import pacparser
>>> pacparser.init()
>>> pacparser.parse_pac('examples/wpad.dat')
>>> pacparser.find_proxy('http://www.google.com', 'www.google.com')
'DIRECT'
>>> pacparser.setmyip("192.168.1.134")
>>> pacparser.find_proxy('http://www.google.com', 'www.google.com')
'PROXY proxy1.manugarg.com:3128; PROXY proxy2.manugarg.com:3128; DIRECT'
>>> pacparser.find_proxy('http://www2.manugarg.com', 'www2.manugarg.com')
'DIRECT'
>>> pacparser.cleanup()
>>>

命令行工具:基于 pacparser SDK 的 python 命令行

我们期望的命令行:

$ python pactest.py -p /path/to/your/proxy.pac -u http://www.baidu.com/path/to/file.html
DIRECT

$ python pactest.py -p /path/to/your/proxy.pac -u http://google.com/path/to/file.html
12.4.12.67:8080

$ python pactest.py -p /path/to/your/proxy.pac -u http://www.baidu.com/path/to/file.html -i 192.168.1.102
DIRECT

$ python pactest.py -p /path/to/your/proxy.pac -u http://www.baidu.com/path/to/file.html -i 10.10.1.3
10.10.1.100:8080

pactest.py 代码如下:

import sys, getopt, pacparser, urllib
opts, args = getopt.getopt(sys.argv[1:], "hp:u:i:")
# opts, args = getopt.getopt(sys.argv[1:], "hp:u:i")

proxy=""
url=""
myip="192.168.1.102"

for op, value in opts:
  if op == "-p":
    proxy = value
  elif op == "-u":
    url = value
  elif op == "-i":
    myip = value

# if (proxy == "" or url == ""):
#    print "python pactest.py <-p pac_file> <-u url> [-i myip]"

print "---"
print "pac file: " + proxy
print "url: " + url
print "myip: " + myip
print "---"

pacparser.init()
pacparser.parse_pac(proxy)

pacparser.setmyip(myip)


# get host of url
protocol, s1 = urllib.splittype(url)
host, s2=  urllib.splithost(s1)
host, port = urllib.splitport(host)

# call find_proxy and return gateway
print "Routing: " + pacparser.find_proxy(url, host)

提醒 proxy.pacpactest.py 两个重要文件。


附录:

访问google需注意,google会重定向到国家后缀域名。 比如 google.co.th, google.co.xx,这样 pac 文件如果以域名判断,google.com 就不会走代理。 但是 google 提供了人为指定不要走国别域名的方式: https://google.com/ncr 其中: ncr = no country router

⚠️ **GitHub.com Fallback** ⚠️