Pages

Thursday, 28 February 2013

自动翻墙的OPENVPN

需求:
1. 希望能够尽量透明地免设置地实现自动翻墙
2. 尽量不影响墙内站点的访问速度
3. 尽可能地节省成本(主要是流量成本)
4. 尽可能地便于维护和更新
参考:
1. autoproxy 主要用了他家的 gfwlist
2. chnroutes 参考了他家的配置
3. autoddvpn 参考了他家的生成路由表的脚本
设计:
为了尽量透明和免设置,也为了速度能够好一点,没有考虑用代理的方式而是用了 VPN 的方式。这带来了不少的麻烦,因为 autoproxy-gfwlist 是基于代理的而不是基于 VPN 的,后面会提到一些细节。
为了节省流量成本(也为了能够让墙内网站访问快一点),没有使用 chnroutes 那种将全部国外网站都走 VPN 的简单易维护的方式,而是准备使用类似于 autoddvpn gracemode 的方式。
公司有很多很多个网络出口。经过考虑,挑选了三个链接速度比较快的出口(广州联通,北京联通,杭州 BGP )做了三个 VPN, 并且同时在这三个出口做了 NAT, 其他出口则设置路由表将墙外 IP 路由到这三个出口之一。
为了能够保证 GSLB, 特别是 DNS based GSLB 的有效性,不考虑将 DNS 整个走 VPN 来防止污染,而需要自建 DNS server 来确保没有被污染的域名走本地查询只有被污染的域名才 forward 到墙外去查询。
因此整个分为这么几快:
1. 墙外的 openvpn server, 自动更新墙外路由表并且 push 到墙内的三个出口
2. 墙内的三个出口处的 oenvpn client, 自动接收 openvpn server push 来的路由表并且应用。同时做 NAT
3. 墙内的其他出口,根据地理位置同步墙内的三个出口之一的路由表并且指向该出口
4. 防止 DNS 污染的 DNS recursive server, 广州,北京,杭州各一个
5. 监控
为了便于实现路由表的更新,我没有用 autoddvpn gracemode 里面的那种在 vpnup.sh 脚本里面添加路由表的方式,而是使用了 client-config-dir (需要比较新的 openvpn, 支持 client-config-dir 也就是 ccd 以及 push-continuation, 没有仔细查 changelog 来看最低版本要求,总之我用了 openvpn 2.2),将需要 push 的路由表放入 ccd 之中,配合 management console, 在更新了 ccd 之中的路由表之后在 console 里面踢掉所有 client, 这样 client 就会自动重连并且得到新的路由表, client 和 server 均无需重启。
client 和其他出口同步路由表,用脚本搞定
用 BIND 建了三个 DNS server(用 bind 不用 dnsmasq 的原因稍后再提)将被污染的域 forward 到 openvpn server 中 /etc/resolv.conf 里面的 DNS server 而其他域在本地做 recursive 查询。
整个架构是简单的。最大的难点在于: 1. 如何获得墙外 IP 列表并做出路由表给 openvpn ccd 使用 2. 如何获取被 DNS 污染的域名列表并做出 forward 列表给 BIND 使用。
这里我们唯一的资源只有 autoproxy-gfwlist, 但是这个 list 是为了代理使用的。没有区分 IP 屏蔽和 DNS 污染,也没有给出每个域名的 IP 具体是什么。因此在 autoddvpn 里面,使用了一个脚本来扫 autoproxy-gfwlist 里面的全部域名,一个一个查询其 IP, 并且将相关的 IP 段整个加入路由表中,同时将 autoproxy-gfwlist 里面所有的域名均加入 BIND 的 forward list 中。
这样得到的两个列表,首先存在大量的冗余,因为只有 DNS 污染的也被加到路由表了,其次都是有问题需要修正的。但是又没有更好的方法去生成列表了。
修正:
1. redirect 和 其他资源
autoddvpn 的脚本没有考虑重定向的问题。例如 http://imdb.com/ 会被301重定向到 http://www.imdb.com/ 而 www.imdb.com 和 imdb.com 的 IP 是不同的。因此重定向之后不会走 VPN 而导致被重置,需要将 www.imdb.com 的 IP 加入路由表。
另外如果网页之中引用的其他资源( css, js, frame, etc… )不会被脚本所跟踪到。例如 http://zinio.com/ 中的全部图片是放置在 img.zinio.com 这个域的,而 zinio.com 和 img.zinio.com 的 IP 是不同的,需要将 img.zinio.com 的 IP 加入路由表。
一个更复杂的例子是 facebook. http://facebook.com/ 首先重定向到 http://www.facebook.com/, 然后引用了位于 static.ak.fbcdn.net 和 s-static.ak.fbcdn.net 的资源,这些资源同时还受到 DNS 污染。(还有更复杂的,那就是这些资源均指向了 CDN, CDN 的问题后面再说。)
解决这个问题,有个办法是修改脚本,让脚本能够跟踪重定向和跟踪引用的资源,但是这样大大增加了脚本运行的时间,而且大大增加了生成列表的冗余(脚本默认已经给了超过一千的路由表和域)。我目前采取的办法是根据同事的报告手工修正 -_-|||…
2. 关键词
目前的架构可以解决 IP 封锁和 DNS 污染两个问题,但是对于关键词的封锁没有太好的办法(除非像 chnroutes 那样将全部国外 IP 均路由到 VPN, 但这样影响速度,并且将导致极大的流量,是不可接受的)。对于非常重要和经常出现问题的几个站点,例如 google, wikipedia 等,我将他们的 IP 全部加入了 VPN, 这样虽然增加了流量(目前 google 的流量占了全部 VPN 流量的一半以上),但是方便了很多。
3. google.cn
google 是一个非常麻烦的例子。在将全部 google IP 加入 VPN 之后,出现了 google.cn 中的谷歌音乐拒绝来自国外 IP 访问的情况。必须将 google.cn 和 g.cn 的 IP 指向一个不走 VPN 的 IP. 但是 google 的 IP 都是通用服务(通过 host 来判断,每个 IP 都可以提供几乎所有服务),目前我采取的方法是将 google.cn 和 g.cn 均在 bind 里面强制指向了谷歌在国内的 IP, 这样就不走路由表并且还增加了速度。
4. CDN
CDN 是一个非常复杂的例子。因为 CDN 都是同时给大量网站提供服务,一旦 CDN 的 IP 进入路由表就会带来大量流量并且让不少墙内网站拖慢速度,而且 CDN 是很少被 IP 封锁的(影响太大),因此在 CDN 里面的 IP 我都尽量从路由表中去掉了。
但是 CDN 虽然不被 IP 封锁,被 DNS 污染却是很正常的。这里有个非常糟糕的事情是,为了能够照顾到 CDN 的 DNS based GSLB, 我们是不可能将 CDN 的域去 forward 到墙外查询的。因此出现这种情况只能特别处理。
例如对于 Facebook 的例子。 Facebook 引用了位于 static.ak.fbcdn.net 和 s-static.ak.fbcdn.net 的资源。 fbcdn.net 是被 DNS 和关键词污染的,因此需要将 fbcdn.net 加入 bind forwarder.
然后这两个域均指向了 CDN:
static.ak.fbcdn.net. IN CNAME static.ak.facebook.com.edgesuite.net.
s-static.ak.fbcdn.net. IN CNAME s-static.ak.fbcdn.net.edgekey.net.
这里我们不能将 edgesuite.net 和 edgekey.net 加入 forward list, 而需要加 facebook.com.edgesuite.net 和 fbcdn.net.edgekey.net
这样去解决 DNS 污染的问题
但是指向 CDN 的网站还会出现关键词污染的问题。 facebook 的例子中不存在这样的问题因为 facebook 全部资源均使用了 ssl (感谢上帝), twitter 也一样(感谢上帝)。对于不使用 SSL 同时使用 CDN 的网站,还真没有什么好的办法,因为还是完全不能考虑将 CDN 的 IP 加入路由表。在 CDN 使用 SSL 还是有点麻烦的,感谢这些网站都做了实现。