前言
上回说到了通过 VPN 进行加速, 有不少朋友已经体会到了 VPN 的好处,许多网站从缓慢到无法访问,变得可以流畅的访问了。但是,我们经常面临一个问题,VPN 拨通后,所有流量都会流经 VPN,导致本地的网络访问可能会很不顺畅,有的速度非常缓慢,有的甚至不能访问。特别是对于那些在使用 VPN 同时还在进行本地下载的用户,这个问题更加明显。对于那些收费的 VPN 而言,因为它们速度较快,所以感觉问题不大。但是,对于那些免费的、速度比较缓慢、甚至限制流量的 VPN 来说,这就是一个比较严重的问题了。很多人不得不同时只干一件事情,要使用 VPN 就停止本地网络的访问,要访问本地网络就需要断开 VPN。那么,可不可以只有访问镇外的流量走 VPN,而本地网络依旧使用本地连接呢?答案自然是可以的。
解决方案
其实这个问题在使用 VPN 之前就存在过。使用过教育网的朋友肯定有经验,国内流量是免费的,而国外流量是收费的。一般来说大家都会访问国内网络,但是毕竟偶尔也需要访问国外网络。 有的学校做的比较一刀切,干脆禁止国外流量,而有的学校则收取国外流量高昂的费用。所以,在教育网生活的时间,大家都到处寻找国内代理服务器,一时间代理 服务器搜索软件、代理服务器列表比比皆是。当时我们学校面临这个问题的时候,我们采用了另外一种办法,除了教育网光纤接入外,我们还申请了电信的包月 ADSL 作为第二链路。当时对于非服务器的网段使用的是 NAT,分别在教育网和ADSL接入上做NAT。然后,修改路由设置,凡是国内流量,都路由到教育网光纤接口;凡是国外流量,都路由到电信的 ADSL 接口。虽然,国外流量的带宽不大,但是对于当时的学生对国外网站的需求不高的情况下,这个问题还是得到了很好的解决。
再回过来看我们 VPN 的问题,会发现其实是一个问题。我们可以添加路由,将所有本地的网络(如上例的所有国内网络范围),路由到我们使用 VPN 前的默认网关。而剩余的,也就是国外的网络范围,自动流经 VPN。这就很好的解决了 VPN 降低本地网络访问速度的问题。
然后,我们需要寻找到本地的 IP 地址范围,在本例中,我使用的是国内的地址范围。因为这个数据库属于公开数据,我们可以从 CNNIC 或者 APNIC 获得相关数据。剩下的就是对地址库进行分析,然后生成路由添加和删除的脚本就可以了,下列脚本只对 VPN 类的链路性的加速有意义,对于SOCKS类的代理是一点帮助也没有。
由于我的机器分别有 Ubuntu 和 Windows 7,因此,我将针对这两个系统写脚本。脚本语言不同,但是参数是一样的。
- on 表明开启国内路由走默认网关的配置。也就是说添加国内网络的路由指向原默认网关。
- once 表明只开启国内路由走默认网关的配置一次,重新启动系统后配置将丢失。
- off 表明关闭这个功能。也就是说删除之前操作所添加的路由。
- update 是指下载更新 IP 地址库。
代码编写以及使用
Linux
对于 Linux 最通用的莫过于 Shell 了,因此,我使用 Bash 完成了这个脚本。虽然我是在 Ubuntu 10.10 系统上测试的,但是它应该支持大部分 Debian 派生系统。我也写了 Redhat 及其派生系统的支持代码,不过没有进行测试罢了,有环境的朋友可以测试后告诉我在什么版本上可以执行,或者是有什么问题,我会修改的。
005 |
IPV4_HTML=/tmp/cnnic_ipv4.html |
006 |
IPV4_DATA=~/cnnic_ipv4.data |
007 |
VPN_IF=`ip tuntap | cut -d ':' -f 1` |
009 |
if [ -z "$VPN_IF" ] ; then |
013 |
GATEWAY=`route -n | grep - v "$VPN_IF" | grep UG | tr -s ' ' | cut -d ' ' -f 2` |
015 |
if [ "`echo " $GATEWAY " | wc -l`" - gt "1" ] ; then |
017 |
GATEWAY=` echo "$GATEWAY" | head -n 1` |
020 |
GATEWAY_IF=`route -n | grep $GATEWAY | head -n 1 | tr -s ' ' | cut -d ' ' -f8` |
024 |
ROUTE_DIR_DEBIAN=/etc/network/ if -up.d |
025 |
ROUTE_FILE_DEBIAN=$ROUTE_DIR_DEBIAN/vpnroute |
027 |
ROUTE_DIR_REDHAT=/etc/sysconfig/network-scripts |
028 |
ROUTE_FILE_REDHAT=$ROUTE_DIR_REDHAT/route-$GATEWAY_IF |
030 |
function get_cn_data() { |
031 |
echo "Downloading CN network data from CNNIC..." |
032 |
RE_PATTERN= "s/^.*searchtext=\([./0-9]*\).*$/\1/" |
033 |
wget -O $IPV4_HTML $URL_CNNIC |
034 |
grep "whois.pl?" $IPV4_HTML | sed -e "$RE_PATTERN" > $IPV4_DATA |
040 |
function check_data() { |
041 |
if [ ! -f $IPV4_DATA ] |
043 |
echo "CN network data file [$IPV4_DATA] is not exist." |
048 |
function add_route_cn() { |
051 |
# Check whether the routes are already added |
052 |
if [ "$ROUTE_ADDED" == "true" ] ; then |
053 |
echo "Routes already added, should be removed first." |
055 |
GATEWAY=` echo "$GATEWAY" | head -n 1` |
057 |
echo "Adding routes of CN ..." |
058 |
echo "Default gateway is $GATEWAY on dev $GATEWAY_IF" |
059 |
if [ "$PERMANENT" == "yes" ] ; then |
060 |
echo "Generating route config file for next boot. [$ROUTE_FILE_DEBIAN]" |
062 |
if [ -d $ROUTE_DIR_DEBIAN ] ; then |
063 |
# Prepare ROUTE_FILE_DEBIAN header |
064 |
rm -f $ROUTE_FILE_DEBIAN |
065 |
echo "#!/bin/bash" >> $ROUTE_FILE_DEBIAN |
066 |
chmod a+x $ROUTE_FILE_DEBIAN |
069 |
if [ -d $ROUTE_DIR_REDHAT ] ; then |
070 |
# Use backup overwrite current file, otherwise backup the file |
071 |
if [ -x "$ROUTE_FILE_REDHAT.bak" ] ; then |
072 |
cp -f "$ROUTE_FILE_REDHAT.bak" "$ROUTE_FILE_REDHAT" |
074 |
cp -f "$ROUTE_FILE_REDHAT" "$ROUTE_FILE_REDHAT.bak" |
082 |
net=` echo "$line" | cut -d '/' -f1` |
083 |
cidr=` echo "$line" | cut -d '/' -f2` |
085 |
# generate ip route cmd |
086 |
ip_args= "$net/$cidr via $GATEWAY" |
087 |
if [ "$PERMANENT" == "yes" ] |
090 |
if [ -d $ROUTE_DIR_DEBIAN ] ; then |
091 |
echo "$IP_PROG route add $ip_args" >> $ROUTE_FILE_DEBIAN |
094 |
if [ -d $ROUTE_DIR_REDHAT ] ; then |
095 |
echo "$ip_args" >> $ROUTE_FILE_REDHAT |
098 |
$IP_PROG route add $ip_args |
099 |
count=$(( $count + 1 )) |
101 |
echo "Added $count networks." |
105 |
function remove_route_cn() { |
107 |
echo "Removing routes of CN ..." |
109 |
if [ -x $ROUTE_FILE_DEBIAN ] ; then |
110 |
rm -f $ROUTE_FILE_DEBIAN |
113 |
if [ -x $ROUTE_FILE_REDHAT ] ; then |
114 |
# If the backup exist, then use backup overwrite current file, otherwise filter the file |
115 |
if [ -x "$ROUTE_FILE_REDHAT.bak" ] ; then |
116 |
mv -f $ROUTE_FILE_REDHAT.bak $ROUTE_FILE_REDHAT |
118 |
cp -f $ROUTE_FILE_REDHAT $ROUTE_FILE_REDHAT.old |
119 |
sed "/ via $GATEWAY$/d" $ROUTE_FILE_REDHAT > $ROUTE_FILE_REDHAT.tmp |
120 |
mv -f $ROUTE_FILE_REDHAT.tmp $ROUTE_FILE_REDHAT |
127 |
net=` echo "$line" | cut -d '/' -f1` |
128 |
cidr=` echo "$line" | cut -d '/' -f2` |
129 |
$IP_PROG route del $net/$cidr via $GATEWAY 2> /dev/null |
130 |
count=$(( $count + 1 )) |
132 |
echo "Removed $count networks." |
137 |
echo "Route networks of CN to the default gateway instead of VPN tunnel" |
139 |
echo "usage: $PROG {on|off|update}" |
141 |
echo " on Add routes of CN to default gateway." |
142 |
echo " once Add routes of CN to default gateway. Only for this time, reboot the configure will be disappeared." |
143 |
echo " off Remove routes of CN" |
144 |
echo " update Force download/update CN network data" |
149 |
"on" ) add_route_cn yes ;; |
150 |
"once" ) add_route_cn no ;; |
151 |
"off" ) remove_route_cn ;; |
152 |
"update" ) get_cn_data ;; |
下载脚本到本地
3 |
sudo bash vpnroute.sh on |
需要删除添加的路由,可以执行:
1 |
sudo bash vpnroute.sh off |
需要更新网络路由数据,可以执行:
1 |
bash vpnroute.sh update |
Windows
Windows 的批处理文件虽然也能写一些东西,但是总觉得它的功能太受限了。因此,这次我使用 PowerShell 来写这个脚本。PowerShell 是微软 2006 年推出的抗衡 *nix 中的 shell 的产品,通过紧密结合 .Net Framework 大幅提高命令行及脚本的能力。熟悉 linux shell 脚本的朋友会在使用中会发现有很多语法似曾相识。虽然有各种不适应,但是不得不说 PowerShell 还是很强大的。
001 |
$prog = $myinvocation .mycommand.name |
003 |
$curdir = ( get-location ).path |
004 |
$ipv4_html = join-path $curdir "cnnic_ipv4.html" |
005 |
$ipv4_data = join-path $curdir "cnnic_ipv4.data" |
006 |
$gateway = route print -4 | where { $_ -match "^\s+0.0.0.0\s+0.0.0.0" } | %{ $_ -replace '\s+' , ' ' } | %{ $_.Split( " " )[3] } |
008 |
function cidr -to -mask ( $cidr ) { |
010 |
$full_octets = $cidr /8 |
011 |
$partial_octet = $cidr % 8 |
013 |
for ( $i = 0; $i -lt 4; $i ++) { |
014 |
$a = $full_octets - $i |
015 |
if (( $a -gt 0) -and ( $a -ge 1)) { |
017 |
} elseif (( $a -gt 0) -and ( $a -lt 1)) { |
018 |
$mask += (256 - [math]::Pow(2,(8-$partial_octet))) |
022 |
if ( $i -lt 3) { $mask += "." } |
027 |
function get -cn -data { |
028 |
Write-Host "Downloading CN network data from CNNIC..." |
029 |
$webclient = new-object System.Net.WebClient |
030 |
$webclient .DownloadFile($url_cnnic, $ipv4_html) |
032 |
cat $ipv4_html | where { $_ -match "whois.pl?" } | % { $_ -replace "^.*searchtext=([.\d/]+).*$" , '$1' } > $ipv4_data |
036 |
function check -data { |
037 |
if (!( test-path $ipv4_data)) { |
038 |
Write-Host "CN network data file [$ipv4_data] is not exist." |
043 |
function add -route -cn ([bool] $permanent ) { |
046 |
$route_count = route print -4 | where { $_ -match "$gateway" } | measure-object |
047 |
if ($route_count > 1) { |
048 |
Write-Host "Routes already added, should be removed first." |
052 |
Write-Host "Adding routes of CN ..." |
053 |
Write-Host "Default gateway is $gateway" |
055 |
foreach ( $line in Get-Content $ipv4_data) |
057 |
$item = $line .split( "/" ) |
060 |
if (( $cidr -ge 0) -and ( $cidr -le 32)) |
062 |
$mask = cidr -to -mask ( $cidr ) |
067 |
route.exe add $opt $net mask $mask $gateway > null |
071 |
Write-Host "Added $count networks." |
075 |
function remove -route -cn { |
077 |
Write-Host "Removing routes of CN ..." |
079 |
foreach ( $line in Get-Content $ipv4_data) |
081 |
$item = $line .split( "/" ) |
084 |
if (( $cidr -ge 0) -and ( $cidr -le 32)) |
086 |
$mask = cidr -to -mask ( $cidr ) |
088 |
route delete $net mask $mask > null |
091 |
Write-Host "Removed $count networks." |
096 |
Write-Host "Route networks of CN to the default gateway instead of VPN tunnel" |
098 |
Write-Host "usage: $prog {on|off|update}" |
100 |
Write-Host " on Add routes of CN to default gateway" |
101 |
Write-Host " once Add routes of CN to default gateway. Only for this time, reboot the configure will be disappeared." |
102 |
Write-Host " off Remove routes of CN" |
103 |
Write-Host " update Download/update CN network data" |
108 |
"on" { add -route -cn ( $true ) } |
109 |
"once" { add -route -cn ( $false ) } |
110 |
"off" { remove -route -cn } |
111 |
"update" { get -cn -data } |
下载脚本 http://files.cnblogs.com/dancefire/vpnroute.zip 到某个目录,比如 d:\tmp
修改 PowerShell 执行权限,默认只能执行签名脚本。:
开始->附件->Windows PowerShell->右键点击 Windows PowerShell->以管理员身份执行
在命令行里执行:
1 |
Set-ExecutionPolicy -ExecutionPolicy Unrestricted |
可能会提示如下信息:
2 |
执行策略可以防止您执行不信任的脚本。更改执行策略可能会使您面临 |
3 |
about_Execution_Policies |
4 |
帮助主题中所述的安全风险。是否要更改执行策略? |
5 |
[Y] 是(Y) [N] 否(N) [S] 挂起(S) [?] 帮助 (默认值为“Y”): |
选择 Y,或者回车即可。
然后,进入下载文件所在目录,执行脚本
如果脚本可以执行,它会返回如下帮助信息:
1 |
PS d:\tmp> .\vpnroute.ps1 |
2 |
Route networks of CN to the default gateway instead of VPN tunnel |
4 |
usage: vpnroute.ps1 {on|off|update} |
6 |
on Add routes of CN to default gateway |
7 |
once Add routes of CN to default gateway. Only for this time, reboot the configure will be disappeared. |
8 |
off Remove routes of CN |
9 |
update Force download/update CN network data |
如要添加路由,希望所有中国范围的IP使用默认的网关访问,就运行:
如果想要去掉添加的路由,就运行
想更新中国IP路由数据,就运行
我注意到 Windows 的 PowerShell 脚本执行速度不是很高,在 Linux 上使用 Bash 添加路由大约只需要10-20秒,在 Windows 上使用 PowerShell 需要 70-80 秒。大家如果看到有段时间没响应,不要着急,给他一些时间就会好的。
连接后,分别访问 http://www.ip.cn 和 http://whatismyipaddress.com 以测试路由设置是否正常。
如果路由添加正确,访问国内网站 http://www.ip.cn 应该看到的是你国内的 ip。而访问国外网站 http://whatismyipaddress.com ,应该看到的是你 vpn 的 ip。
原文:http://www.cnblogs.com/dancefire/archive/2011/03/28/how-to-setup-vpn-cn-route.html