From 015f2a17407ed916509429be06bb072cba0e16c7 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Mon, 16 Jun 2014 22:43:28 +0800 Subject: [PATCH 1/4] Support PAC script generation. chnroutes only works when using VPN with modified route table. But it could became a more useful script. This commit makes chnroute support the PAC file generation, as a new "pac" platform. Now, HTTP/Sock4/Sock5 users could take benefits of chnroutes, including users of SSH and Shadowsocks. Signed-off-by: Tom Li --- chnroutes.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/chnroutes.py b/chnroutes.py index 1053854..fc71f2a 100755 --- a/chnroutes.py +++ b/chnroutes.py @@ -152,6 +152,54 @@ def generate_win(metric): print "For pptp on windows only, run vpnup.bat before dialing to vpn," \ "and run vpndown.bat after disconnected from the vpn." +def generate_pac(metric): + results = fetch_ip_data() + + proxy_pac = open("./proxy.pac", "wb") + + proxy_pac.write('PROXY = "SOCKS5 127.0.0.1:1984";\n') + proxy_pac.write('CHINESE_SUBNETS = [\n') + + for ip, mask, _ in results: + proxy_pac.write(' ["%s", "%s"],\n' % (ip, mask)) + proxy_pac.write("];\n\n") + + logic = textwrap.dedent("""\ + function inChina(host) + { + var ip = dnsResolve(host); + for (var i = 0; i < CHINESE_SUBNETS.length; i++) { + var subnet = CHINESE_SUBNETS[i][0]; + var netmask = CHINESE_SUBNETS[i][1]; + if (isInNet(ip, subnet, netmask)) { + return true; + } + } + return false; + } + + function FindProxyForURL(url, host) + { + if (inChina(host)) { + return "DIRECT"; + } + else { + return PROXY; + } + } + + """) + + proxy_pac.write(logic) + proxy_pac.close() + + print 'The first line of proxy.pac - TYPE "ip:port"' + print 'TYPE can be PROXY (HTTP), SOCKS (Socks 4), or SOCKS5' + print 'The default value is PROXY = "SOCKS5 127.0.0.1:1984", PLEASE CHANGE it.' + print '' + print "Note: Some browser doesn't support Socks 5 in PAC, although they may support Socks 5 in the settings." + print 'Tip: You can use "file://path" as the URL of the local file.' + def generate_android(metric): results = fetch_ip_data() @@ -235,7 +283,7 @@ def fetch_ip_data(): default='openvpn', nargs='?', help="Target platforms, it can be openvpn, mac, linux," - "win, android. openvpn by default.") + "win, android and pac. openvpn by default.") parser.add_argument('-m','--metric', dest='metric', default=5, @@ -255,6 +303,8 @@ def fetch_ip_data(): generate_win(args.metric) elif args.platform.lower() == 'android': generate_android(args.metric) + elif args.platform.lower() == 'pac': + generate_pac(args.metric) else: print>>sys.stderr, "Platform %s is not supported."%args.platform exit(1) From 4ab389391c6b750793982838429c84fee12524e5 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Wed, 18 Jun 2014 19:19:12 +0800 Subject: [PATCH 2/4] PAC: add a hack for Google's domain. --- chnroutes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chnroutes.py b/chnroutes.py index fc71f2a..d726e83 100755 --- a/chnroutes.py +++ b/chnroutes.py @@ -167,6 +167,10 @@ def generate_pac(metric): logic = textwrap.dedent("""\ function inChina(host) { + if (shExpMatch(host, "*.google.*")) { + return false; + } + var ip = dnsResolve(host); for (var i = 0; i < CHINESE_SUBNETS.length; i++) { var subnet = CHINESE_SUBNETS[i][0]; From db411bf4f0c9cf36a3af5f3ce55a90a5d7d2e9ba Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 21 Sep 2014 14:54:33 +0800 Subject: [PATCH 3/4] add pydata target, cleanup code. --- chnroutes.py | 115 ++++++++++++++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/chnroutes.py b/chnroutes.py index d726e83..46e3cce 100755 --- a/chnroutes.py +++ b/chnroutes.py @@ -9,7 +9,7 @@ def generate_ovpn(metric): - results = fetch_ip_data() + results = fetch_ip_data() rfile=open('routes.txt','w') for ip,mask,_ in results: route_item="route %s %s net_gateway %d\n"%(ip,mask,metric) @@ -24,35 +24,35 @@ def generate_linux(metric): upscript_header=textwrap.dedent("""\ #!/bin/bash export PATH="/bin:/sbin:/usr/sbin:/usr/bin" - + OLDGW=`ip route show | grep '^default' | sed -e 's/default via \\([^ ]*\\).*/\\1/'` - + if [ $OLDGW == '' ]; then exit 0 fi - + if [ ! -e /tmp/vpn_oldgw ]; then echo $OLDGW > /tmp/vpn_oldgw fi - + """) - + downscript_header=textwrap.dedent("""\ #!/bin/bash export PATH="/bin:/sbin:/usr/sbin:/usr/bin" - + OLDGW=`cat /tmp/vpn_oldgw` - + """) - + upfile=open('ip-pre-up','w') downfile=open('ip-down','w') - + upfile.write(upscript_header) upfile.write('\n') downfile.write(downscript_header) downfile.write('\n') - + for ip,mask,_ in results: upfile.write('route add -net %s netmask %s gw $OLDGW\n'%(ip,mask)) downfile.write('route del -net %s netmask %s\n'%(ip,mask)) @@ -65,90 +65,90 @@ def generate_linux(metric): def generate_mac(metric): results=fetch_ip_data() - + upscript_header=textwrap.dedent("""\ #!/bin/sh export PATH="/bin:/sbin:/usr/sbin:/usr/bin" - + OLDGW=`netstat -nr | grep '^default' | grep -v 'ppp' | sed 's/default *\\([0-9\.]*\\) .*/\\1/'` if [ ! -e /tmp/pptp_oldgw ]; then echo "${OLDGW}" > /tmp/pptp_oldgw fi - + dscacheutil -flushcache route add 10.0.0.0/8 "${OLDGW}" route add 172.16.0.0/12 "${OLDGW}" route add 192.168.0.0/16 "${OLDGW}" """) - + downscript_header=textwrap.dedent("""\ #!/bin/sh export PATH="/bin:/sbin:/usr/sbin:/usr/bin" - + if [ ! -e /tmp/pptp_oldgw ]; then exit 0 fi - + ODLGW=`cat /tmp/pptp_oldgw` route delete 10.0.0.0/8 "${OLDGW}" route delete 172.16.0.0/12 "${OLDGW}" route delete 192.168.0.0/16 "${OLDGW}" """) - + upfile=open('ip-up','w') downfile=open('ip-down','w') - + upfile.write(upscript_header) upfile.write('\n') downfile.write(downscript_header) downfile.write('\n') - + for ip,_,mask in results: upfile.write('route add %s/%s "${OLDGW}"\n'%(ip,mask)) downfile.write('route delete %s/%s ${OLDGW}\n'%(ip,mask)) - + downfile.write('\n\nrm /tmp/pptp_oldgw\n') upfile.close() downfile.close() - + print "For pptp on mac only, please copy ip-up and ip-down to the /etc/ppp folder," \ "don't forget to make them executable with the chmod command." def generate_win(metric): - results = fetch_ip_data() + results = fetch_ip_data() upscript_header=textwrap.dedent("""@echo off for /F "tokens=3" %%* in ('route print ^| findstr "\\<0.0.0.0\\>"') do set "gw=%%*" - + """) - + upfile=open('vpnup.bat','w') downfile=open('vpndown.bat','w') - + upfile.write(upscript_header) upfile.write('\n') upfile.write('ipconfig /flushdns\n\n') - + downfile.write("@echo off") downfile.write('\n') - + for ip,mask,_ in results: upfile.write('route add %s mask %s %s metric %d\n'%(ip,mask,"%gw%",metric)) downfile.write('route delete %s\n'%(ip)) - + upfile.close() downfile.close() - + # up_vbs_wrapper=open('vpnup.vbs','w') # up_vbs_wrapper.write('Set objShell = CreateObject("Wscript.shell")\ncall objShell.Run("vpnup.bat",0,FALSE)') # up_vbs_wrapper.close() # down_vbs_wrapper=open('vpndown.vbs','w') # down_vbs_wrapper.write('Set objShell = CreateObject("Wscript.shell")\ncall objShell.Run("vpndown.bat",0,FALSE)') # down_vbs_wrapper.close() - + print "For pptp on windows only, run vpnup.bat before dialing to vpn," \ "and run vpndown.bat after disconnected from the vpn." @@ -204,41 +204,52 @@ def generate_pac(metric): print "Note: Some browser doesn't support Socks 5 in PAC, although they may support Socks 5 in the settings." print 'Tip: You can use "file://path" as the URL of the local file.' +def generate_pydata(metric): + results = fetch_ip_data() + + pydata = open("./chnroutes_data.py", "wb") + + pydata.write('CHINESE_SUBNETS = [\n') + for ip, mask, _ in results: + pydata.write(' ["%s", "%s"],\n' % (ip, mask)) + pydata.write("]\n") + pydata.close() + def generate_android(metric): results = fetch_ip_data() - + upscript_header=textwrap.dedent("""\ #!/bin/sh alias nestat='/system/xbin/busybox netstat' alias grep='/system/xbin/busybox grep' alias awk='/system/xbin/busybox awk' alias route='/system/xbin/busybox route' - + OLDGW=`netstat -rn | grep ^0\.0\.0\.0 | awk '{print $2}'` - + """) - + downscript_header=textwrap.dedent("""\ #!/bin/sh alias route='/system/xbin/busybox route' - + """) - + upfile=open('vpnup.sh','w') downfile=open('vpndown.sh','w') - + upfile.write(upscript_header) upfile.write('\n') downfile.write(downscript_header) downfile.write('\n') - + for ip,mask,_ in results: upfile.write('route add -net %s netmask %s gw $OLDGW\n'%(ip,mask)) downfile.write('route del -net %s netmask %s\n'%(ip,mask)) - + upfile.close() downfile.close() - + print "Old school way to call up/down script from openvpn client. " \ "use the regular openvpn 2.1 method to add routes if it's possible" @@ -248,17 +259,17 @@ def fetch_ip_data(): print "Fetching data from apnic.net, it might take a few minutes, please wait..." url=r'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' data=urllib2.urlopen(url).read() - + cnregex=re.compile(r'apnic\|cn\|ipv4\|[0-9\.]+\|[0-9]+\|[0-9]+\|a.*',re.IGNORECASE) cndata=cnregex.findall(data) - + results=[] for item in cndata: unit_items=item.split('|') starting_ip=unit_items[3] num_ip=int(unit_items[4]) - + imask=0xffffffff^(num_ip-1) #convert to string imask=hex(imask)[2:] @@ -267,16 +278,16 @@ def fetch_ip_data(): mask[1]=imask[2:4] mask[2]=imask[4:6] mask[3]=imask[6:8] - + #convert str to int mask=[ int(i,16 ) for i in mask] mask="%d.%d.%d.%d"%tuple(mask) - + #mask in *nix format mask2=32-int(math.log(num_ip,2)) - + results.append((starting_ip,mask,mask2)) - + return results @@ -286,7 +297,7 @@ def fetch_ip_data(): dest='platform', default='openvpn', nargs='?', - help="Target platforms, it can be openvpn, mac, linux," + help="Target platforms, it can be openvpn, mac, linux," "win, android and pac. openvpn by default.") parser.add_argument('-m','--metric', dest='metric', @@ -294,9 +305,9 @@ def fetch_ip_data(): nargs='?', type=int, help="Metric setting for the route rules") - + args = parser.parse_args() - + if args.platform.lower() == 'openvpn': generate_ovpn(args.metric) elif args.platform.lower() == 'linux': @@ -309,6 +320,8 @@ def fetch_ip_data(): generate_android(args.metric) elif args.platform.lower() == 'pac': generate_pac(args.metric) + elif args.platform.lower() == "pydata": + generate_pydata(args.metric) else: print>>sys.stderr, "Platform %s is not supported."%args.platform exit(1) From d30cffc9cc5344d7137b0b61000238ac1d7fc52d Mon Sep 17 00:00:00 2001 From: Mingye Wang Date: Sun, 25 Oct 2015 00:28:19 -0400 Subject: [PATCH 4/4] Now you can use your favorite script without bash Also used `printf` to avoid `echo` mess --- chnroutes.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/chnroutes.py b/chnroutes.py index 46e3cce..1e431a6 100755 --- a/chnroutes.py +++ b/chnroutes.py @@ -22,26 +22,26 @@ def generate_ovpn(metric): def generate_linux(metric): results = fetch_ip_data() upscript_header=textwrap.dedent("""\ - #!/bin/bash + #!/bin/sh export PATH="/bin:/sbin:/usr/sbin:/usr/bin" - OLDGW=`ip route show | grep '^default' | sed -e 's/default via \\([^ ]*\\).*/\\1/'` + OLDGW=$(ip route show | grep '^default' | sed -e 's/default via \\([^ ]*\\).*/\\1/') - if [ $OLDGW == '' ]; then + if [ "$OLDGW" == '' ]; then exit 0 fi if [ ! -e /tmp/vpn_oldgw ]; then - echo $OLDGW > /tmp/vpn_oldgw + printf '%s\n' "$OLDGW" > /tmp/vpn_oldgw fi """) downscript_header=textwrap.dedent("""\ - #!/bin/bash + #!/bin/sh export PATH="/bin:/sbin:/usr/sbin:/usr/bin" - OLDGW=`cat /tmp/vpn_oldgw` + OLDGW=$(cat /tmp/vpn_oldgw) """) @@ -73,7 +73,7 @@ def generate_mac(metric): OLDGW=`netstat -nr | grep '^default' | grep -v 'ppp' | sed 's/default *\\([0-9\.]*\\) .*/\\1/'` if [ ! -e /tmp/pptp_oldgw ]; then - echo "${OLDGW}" > /tmp/pptp_oldgw + printf '%s\n' "$OLDGW" > /tmp/pptp_oldgw fi dscacheutil -flushcache