页面

2011年4月2日星期六

如何避免 VPN 连接后会降低本地网络访问速度的问题


前言

上回说到了通过 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 及其派生系统的支持代码,不过没有进行测试罢了,有环境的朋友可以测试后告诉我在什么版本上可以执行,或者是有什么问题,我会修改的。
001 #!/bin/bash
002 LANG=C
003 PROG=$0
005 IPV4_HTML=/tmp/cnnic_ipv4.html
006 IPV4_DATA=~/cnnic_ipv4.data
007 VPN_IF=`ip tuntap | cut -d ':' -f 1`
008
009 if [ -z "$VPN_IF" ] ; then
010 VPN_IF="tun"
011 fi
012
013 GATEWAY=`route -n | grep -v "$VPN_IF" | grep UG | tr -s ' ' | cut -d ' ' -f 2`
014
015 if [ "`echo "$GATEWAY" | wc -l`" -gt "1" ] ; then
016 ROUTE_ADDED=true
017 GATEWAY=`echo "$GATEWAY" | head -n 1`
018 fi
019
020 GATEWAY_IF=`route -n | grep $GATEWAY | head -n 1 | tr -s ' ' | cut -d ' ' -f8`
021
022 IP_PROG=`which ip`
023
024 ROUTE_DIR_DEBIAN=/etc/network/if-up.d
025 ROUTE_FILE_DEBIAN=$ROUTE_DIR_DEBIAN/vpnroute
026
027 ROUTE_DIR_REDHAT=/etc/sysconfig/network-scripts
028 ROUTE_FILE_REDHAT=$ROUTE_DIR_REDHAT/route-$GATEWAY_IF
029
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
035 chmod a+w $IPV4_HTML
036 chmod a+w $IPV4_DATA
037 echo "Done."
038 }
039
040 function check_data() {
041 if [ ! -f $IPV4_DATA ]
042 then
043 echo "CN network data file [$IPV4_DATA] is not exist."
044 get_cn_data
045 fi
046 }
047
048 function add_route_cn() {
049 check_data
050 PERMANENT="$1"
051 # Check whether the routes are already added
052 if [ "$ROUTE_ADDED" == "true" ] ; then
053 echo "Routes already added, should be removed first."
054 remove_route_cn
055 GATEWAY=`echo "$GATEWAY" | head -n 1`
056 fi
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]"
061 # Debian/Ubuntu
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
067 fi
068 # Redhat/Fedora
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"
073 else
074 cp -f "$ROUTE_FILE_REDHAT" "$ROUTE_FILE_REDHAT.bak"
075 fi
076 fi
077 fi
078
079 count=0
080 while read line
081 do
082 net=`echo "$line" | cut -d '/' -f1`
083 cidr=`echo "$line" | cut -d '/' -f2`
084
085 # generate ip route cmd
086 ip_args="$net/$cidr via $GATEWAY"
087 if [ "$PERMANENT" == "yes" ]
088 then
089 # Debian/Ubuntu
090 if [ -d $ROUTE_DIR_DEBIAN ] ; then
091 echo "$IP_PROG route add $ip_args" >> $ROUTE_FILE_DEBIAN
092 fi
093 # Redhat/Fedora
094 if [ -d $ROUTE_DIR_REDHAT ] ; then
095 echo "$ip_args" >> $ROUTE_FILE_REDHAT
096 fi
097 fi
098 $IP_PROG route add $ip_args
099 count=$(( $count + 1 ))
100 done < $IPV4_DATA
101 echo "Added $count networks."
102 echo "Done."
103 }
104
105 function remove_route_cn() {
106 check_data
107 echo "Removing routes of CN ..."
108 # Debian/Ubuntu
109 if [ -x $ROUTE_FILE_DEBIAN ] ; then
110 rm -f $ROUTE_FILE_DEBIAN
111 fi
112 # Redhat/Fedora
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
117 else
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
121 fi
122 fi
123
124 count=0
125 while read line
126 do
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 ))
131 done < $IPV4_DATA
132 echo "Removed $count networks."
133 echo "Done."
134 }
135
136 function usage() {
137 echo "Route networks of CN to the default gateway instead of VPN tunnel"
138 echo ""
139 echo "usage: $PROG {on|off|update}"
140 echo ""
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"
145 echo ""
146 }
147
148 case "$1" in
149 "on")       add_route_cn yes ;;
150 "once")     add_route_cn no     ;;
151 "off")      remove_route_cn     ;;
152 "update")   get_cn_data     ;;
153 *)      usage           ;;
154 esac
下载脚本到本地
2 unzip vpnroute.zip
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] }
007
008 function cidr-to-mask($cidr) {
009 [string]$mask = ""
010 $full_octets = $cidr/8
011 $partial_octet = $cidr%8
012
013 for ($i = 0; $i -lt 4; $i++) {
014 $a = $full_octets - $i
015 if (($a -gt 0) -and ($a -ge 1)) {
016 $mask += "255"
017 } elseif (($a -gt 0) -and ($a -lt 1)) {
018 $mask += (256 - [math]::Pow(2,(8-$partial_octet)))
019 } else {
020 $mask += "0"
021 }
022 if ($i -lt 3) { $mask += "." }
023 }
024 return $mask
025 }
026
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)
031
032 cat $ipv4_html | where { $_ -match "whois.pl?" } | % { $_ -replace "^.*searchtext=([.\d/]+).*$", '$1' } > $ipv4_data
033 Write-Host "Done."
034 }
035
036 function check-data {
037 if (!(test-path $ipv4_data)) {
038 Write-Host "CN network data file [$ipv4_data] is not exist."
039 get-cn-data
040 }
041 }
042
043 function add-route-cn([bool]$permanent) {
044 check-data
045
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."
049 remove-route-cn
050 }
051
052 Write-Host "Adding routes of CN ..."
053 Write-Host "Default gateway is $gateway"
054 $count = 0
055 foreach ($line in Get-Content $ipv4_data)
056 {
057 $item = $line.split("/")
058 $net = $item[0]
059 $cidr = $item[1]
060 if (($cidr -ge 0) -and ($cidr -le 32))
061 {
062 $mask = cidr-to-mask($cidr)
063 $opt = ""
064 if ($permanent) {
065 $opt = "-p"
066 }
067 route.exe add $opt $net mask $mask $gateway > null
068 $count++
069 }
070 }
071 Write-Host "Added $count networks."
072 Write-Host "Done."
073 }
074
075 function remove-route-cn {
076 check-data
077 Write-Host "Removing routes of CN ..."
078 $count = 0
079 foreach ($line in Get-Content $ipv4_data)
080 {
081 $item = $line.split("/")
082 $net = $item[0]
083 $cidr = $item[1]
084 if (($cidr -ge 0) -and ($cidr -le 32))
085 {
086 $mask = cidr-to-mask($cidr)
087 }
088 route delete $net mask $mask > null
089 $count++
090 }
091 Write-Host "Removed $count networks."
092 Write-Host "Done."
093 }
094
095 function usage {
096 Write-Host "Route networks of CN to the default gateway instead of VPN tunnel"
097 Write-Host ""
098 Write-Host "usage: $prog {on|off|update}"
099 Write-Host ""
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"
104 Write-Host ""
105 }
106
107 switch($args[0]) {
108 "on" {   add-route-cn($true) }
109 "once" {   add-route-cn($false)    }
110 "off" {   remove-route-cn }
111 "update" {   get-cn-data }
112 default     {   usage           }
113 }
下载脚本 http://files.cnblogs.com/dancefire/vpnroute.zip 到某个目录,比如 d:\tmp
修改 PowerShell 执行权限,默认只能执行签名脚本。:
开始->附件->Windows PowerShell->右键点击 Windows PowerShell->以管理员身份执行
在命令行里执行:
1 Set-ExecutionPolicy -ExecutionPolicy Unrestricted
可能会提示如下信息:
1 执行策略更改
2 执行策略可以防止您执行不信任的脚本。更改执行策略可能会使您面临
3 about_Execution_Policies
4 帮助主题中所述的安全风险。是否要更改执行策略?
5 [Y] 是(Y) [N] 否(N) [S] 挂起(S) [?] 帮助 (默认值为"Y"):
选择 Y,或者回车即可。
然后,进入下载文件所在目录,执行脚本
1 cd d:\tmp
2 .\vpnroute.ps1
如果脚本可以执行,它会返回如下帮助信息:
1 PS d:\tmp> .\vpnroute.ps1
2 Route networks of CN to the default gateway instead of VPN tunnel
3
4 usage: vpnroute.ps1 {on|off|update}
5
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使用默认的网关访问,就运行:
1 .\vpnroute.ps1 on
如果想要去掉添加的路由,就运行
1 .\vpnroute.ps1 off
想更新中国IP路由数据,就运行
1 .\vpnroute.ps1 update
我注意到 Windows 的 PowerShell 脚本执行速度不是很高,在 Linux 上使用 Bash 添加路由大约只需要10-20秒,在 Windows 上使用 PowerShell 需要 70-80 秒。大家如果看到有段时间没响应,不要着急,给他一些时间就会好的。
连接后,分别访问 http://www.ip.cnhttp://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

没有评论:

发表评论