利用 Dnsmasq 的 ipset 实现智能路由、科学上网

上一篇文章《在 EdgeRouter Lite 上实现科学上网》是用的比较传统的方式,即简单地添加目的路由。对于比较大规模的 ICP 例如 Google, Facebook,挖出他们的网段然后添加到路由表里是可行的(主要是他们有较好的网络基础设施建设,都申请了自己的 IP 地址段,并且用 BGP 互联广播到全网)。不过对于绝大多数被封的站点,想要一个一个地追踪他们的 IP 地址是多少并保持更新,这无疑是非常吃力的。好在昨天翻 Dnsmasq 的 Manpage 读到了 ipset 的功能选项,放狗一搜发现早有人提到这项功能了,不过都是在 Openwrt 上实现的。这回我决定在 EdgeRouter Lite 上也实现一次。

目前 ER-Lite 上最新的固件是 1.5.0 版本,基于 Debian Squeeze 6.0.9 ,很遗憾其中的 Dnsmasq 版本是 2.62,根据 Change Log,需要到 2.66 版本才能有 ipset 支持,这样只好自己编译了。好在编译起来不麻烦,把几个需要的 feature 在 src/config.h 里加上,然后在 Makefile 里把 CC CFLAGS LDFLAGS PKG_CONFIG_PATH 等几个变量设好,就可以直接编译了。(当然有些 feature 需要额外的依赖,需要事先编译好)我这里编译的 dnsmasq 2.71 版本,feature 尽量和原系统预装的 2.62 保持一致。

ubnt@ERL:~$ /usr/sbin/dnsmasq --version
Dnsmasq version 2.71 Copyright (c) 2000-2014 Simon Kelley
Compile time options: IPv6 GNU-getopt no-RTC DBus no-i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth no-DNSSEC

This software comes with ABSOLUTELY NO WARRANTY.
Dnsmasq is free software, and you are welcome to redistribute it
under the terms of the GNU General Public License, version 2 or 3.

直接替换原系统里的 2.62 版本,就可以无缝工作了。以 Google 为例,在配置中添加如下选项:

ubnt@ERL:~$ configure
[edit]
ubnt@ERL# set service dns forwarding options server=/google.com/8.8.8.8
[edit]
ubnt@ERL# set service dns forwarding options ipset=/google.com/CROSS_WALL

第一行是指定 google.com 下所有的域名都交给 8.8.8.8 这个 DNS Server 来解析,第二行是指定所有 google.com 域名下解析出来的 IP 都扔到名为 CROSS_WALL 的这个 ipset 里(当然 ipset 需要事先创建好)

到 8.8.8.8/32 的路由可以通过之前建立的 OpenVPN tunnel 出去,直接添加到路由表。这样防止解析出来的域名受到污染。随后在 firewall rule 里让所有 destination addr 符合 CROSS_WALL 这个 ipset 的包都走特定的 tunnel 出去,这样就实现了智能路由,科学上网了。trace route 如下:

C:\Users\Xiaoding>tracert -d www.google.com

Tracing route to www.google.com [173.194.127.210]
over a maximum of 30 hops:

  1     6 ms     1 ms     5 ms  192.168.111.1
  2   281 ms   285 ms   286 ms  10.8.0.1
  3   276 ms   282 ms   271 ms  96.44.154.33
  4   267 ms   271 ms   275 ms  72.11.150.105
  5   283 ms   279 ms   277 ms  96.44.180.97
  6   280 ms   280 ms   277 ms  206.72.210.41
  7   290 ms   283 ms   284 ms  64.233.174.41
  8   291 ms   308 ms   290 ms  64.233.174.190
  9   400 ms   408 ms   393 ms  64.233.174.177
 10   403 ms   402 ms   422 ms  209.85.243.249
 11   437 ms   406 ms   410 ms  66.249.94.30
 12   407 ms   411 ms   413 ms  209.85.240.133
 13   426 ms   442 ms   442 ms  173.194.127.210

Trace complete.

 

在 EdgeRouter Lite 上实现科学上网

从淘宝上买了一个 Ubiquiti EdgeRouter Lite,mips 平台的路由器,提供 3 个 1000Mbps 网络接口、基于 Debian 和 Vyatta 的 EdgeOS 操作平台,最重要的是有 4GB 的内置存储以及 512MB RAM,这使得此款机器的性能远远超过那些刷了 OpenWRT 的家用无线路由。

为了让路由正常运转,首先是配置基本的网络服务,这包括:

  • 配置 eth0 上的 PPPoE 拨号上网
  • 配置 eth1 和 eth2,添加到 bridge br0,并在 br0 上配置局域网 IP
  • 配置 br0 上的 DHCP 服务,实现 LAN 机器自动获取 IP
  • 配置 br0 上的 DNS Forward,在局域网网关上转发 DNS 请求
  • 在 PPPoE 接口上配置 NAT 服务,使 LAN 机器能访问外网

另外,为了支持无线网络,需要接入一个无线 AP,一般来说都是用家用无线路由器实现的,具体要注意以下几点:

  • 单独配置无线路由器,关掉局域网上的 DHCP 服务
  • 配置无线路由器的 LAN 端 IP,使之与 EdgeRouter Lite 的 br0 不在一个网段(尽量避免冲突)
  • 用网线连接 EdgeRouter Lite 的 LAN 口和无线路由器的 LAN 口

以上步骤照着说明和经验,很快可以完成,这样就实现了最基本的基于 NAT 的家庭网络服务。

接下来,为了科学上网(*翻*墙*),需要配置 tunnel 和额外的路由。

关于 tunnel,经仔细研究,发现可以用传统的 gre, sit 或者 ipip 隧道,但这些服务有一个缺点就是不能很好地支持 PPPoE 获取的动态 IP,虽然经过搜索,有人已经提供了利用 SSH 远程更新的思路和方案,但是在 EdgeOS 的 vbash shell 里没有直接支持,必须绕过上层的 EdgeOS 直接在 Linux OS 里写脚本,这样未免显得略微麻烦。EdgeOS 里倒是直接提供了 OpenVPN 的选项,这样一来直接用 OpenVPN 建立 site-to-site tunnel 倒是一个很直接的方案。

服务器端是一个米国的 VPS,跑着 FreeBSD 9.2-RELEASE,先用 ports 装上 security/openvpn,然后建立服务端,配置文件如下,非常简单:

# use tun device, for a layer-3 tunnel
dev tun
# tunnel over udp for better performance
proto udp
# config tunnel end-point ip for both local and remote
ifconfig 10.8.0.1 10.8.0.2
# add route to remote network
route 10.10.112.0 255.255.255.0 10.8.0.2
# use pre-shared static key since tls negotiation is interrupted
# by the great firewall
secret static.key
# avoid using default port
port 31194

verb 3
# enable lzo compression
comp-lzo

log-append /var/log/openvpn.log
# persist tun device and key
persist-tun
persist-key
# run as unprivileged user
user nobody
group nogroup

接下来是 EdgeRouter Lite 上的 OpenVPN 配置,如下

set interfaces openvpn vtun0 local-address 10.8.0.2
set interfaces openvpn vtun0 local-port 31194
set interfaces openvpn vtun0 mode site-to-site
set interfaces openvpn vtun0 openvpn-option '--comp-lzo '
set interfaces openvpn vtun0 remote-address 10.8.0.1
set interfaces openvpn vtun0 remote-host XXX.XXX.XXX.XXX
set interfaces openvpn vtun0 remote-port 31194
set interfaces openvpn vtun0 shared-secret-key-file /config/auth/static.key

这样隧道就建好了,通过  show interfaces 命令可以查看到,也可以 ping 通 tunnel 的远端地址,说明隧道连接是成功的。

为了把隧道当做出口,需要在远端配置 NAT 服务,对于 FreeBSD 来说,用 pf 或者 ipfw 都很容易做到。以 pf 为例,只需要一句配置语句:

nat on $external_if from { tun0:peer, $remote_subnet } to any -> $external_if

随后在 EdgeRouter Lite 上配置路由,使得访问 Google 和 Facebook 的包通过隧道出去。这里可以用策略路由,也可以用简单的静态路由来实现。关键是找到 Google 和 Facebook 的服务器 IP 地址段。

Google 的 IP 地址段非常好找,在 Google Help 上提到了可以通过 Google 的 SPF 记录来查找。简单地在 cmd 里运行 nslookup -type=txt _spf.google.com 8.8.4.4  ,可以看到有三个 netblock,再用 nslookup 查询一下,Google 的 IP 地址段就出来了。(目前第一个记录是 ipv4 地址,第二个是 ipv6 地址,第三个为空)

C:\Users\Xiaoding>nslookup -type=txt _netblocks.google.com 8.8.4.4
Server:  google-public-dns-b.google.com
Address:  8.8.4.4

Non-authoritative answer:
_netblocks.google.com   text =

        "v=spf1 ip4:216.239.32.0/19 ip4:64.233.160.0/19 ip4:66.249.80.0/20 ip4:7
2.14.192.0/18 ip4:209.85.128.0/17 ip4:66.102.0.0/20 ip4:74.125.0.0/16 ip4:64.18.
0.0/20 ip4:207.126.144.0/20 ip4:173.194.0.0/16 ~all"

Facebook 的 IP 地址段官方也提供了相应的列表,不过方式与 Google 略有不同,是发布在 Facebook developers 网站上的,在 Facebook content sharing best practice 这篇文章中直接列出了当前的 IP 地址段,也提供了命令行方式通过查询 Facebook 的自治系统(AS)路由广播来查询。

IP whitelisting

Your engineers can allow the following IP addresses access to pages that would otherwise be inaccessible to the public.

31.13.24.0/21
31.13.64.0/18
66.220.144.0/20
69.63.176.0/20
69.171.224.0/19
74.119.76.0/22
103.4.96.0/22
173.252.64.0/18
204.15.20.0/22
2401:db00::/32
2620:0:1c00::/40
2a03:2880::/32
Please note that these IP ranges can and do change regularly, so you should periodically run the following command to receive an updated list

whois -h whois.radb.net — ‘-i origin AS32934’ | grep ^route

在 EdgeRouter Lite 上配置好路由后,就可以成功地通过隧道实现“科学上网”了。

ubnt@EdgeRouterLite:~$ show firewall modify   GOOGLE_ROUTE

IPv4 Modify Firewall "GOOGLE_ROUTE":

 Active on (br0,IN)

rule  action   proto     packets  bytes
----  ------   -----     -------  -----
1     modify   all       30128    1699704
  condition - match-DST-NTWRK-GROUP Google

2     modify   all       1119     175999
  condition - match-DST-NTWRK-GROUP Facebook

10000 accept   all       379763   22430120

去除 Google 搜索的 URL 重定向和跟踪

Chrome 上的 Remove Google Redirects 扩展最近不好用了,Google 搜出来的结果仍然有重定向的跳转。费了点劲,在 StackExchange 上找到了 Don’t Track me Google 这个,试了一试,果然管用。

之前用过的有:Remove Google Redirects,Straight Google,还有 userscripts.org 上的若干脚本…… 只是 Google 的搜索策略在改变,作者都来不及更新了。

12306手机版App逆向研究

抽空研究了一下 12306 放出的 Android 版手机 App,本想挖一挖有没有手机版协议可供调用,不过就结果来看意义已经不大。以下简单记一下过程。

12306 的 apk 安装包可以用 WinRAR 打开,解压出 classes.dex,然后用 dex2jar 转换成 jar 包,就可以放到 jd-gui 里看 java 源代码了,源代码未经混淆,看起来十分方便。

在 jd-gui 里可以看到有好几个 java package,cn.domob 这个包是多盟的广告联盟代码,12306 App 的界面上未见到有广告,估计是用来做统计分析的。有 com.worklight 这个包,可以证明是用了 IBM Worklight 这个框架。真正实打实有内容的就是 com.MobileTicket 这一个包,其它的都是引入的库,并非 12306 相关的程序代码。阅读入口类 MobileTicket.class 的代码,可以发现其调用了本地的浏览器显示一个网页。也就是说:12306 App 的主界面其实是由网页组成的,整个程序的逻辑实现也都放在网页里了,java 源代码的干货其实很少。

  protected void bindBrowser(CordovaWebView paramCordovaWebView, boolean paramBoolean)
  {
    super.bindBrowser(paramCordovaWebView, paramBoolean);
    mDService = new DService(this, "56OJyf1IuNPVnYbzz4", "16TLwHboApIGwNU-ypi0ThBk");
    mDViewManager = new DViewManager(this, mDService);
    mDViewManager.doAppStartReport();
    paramCordovaWebView.pluginManager.addService("WebResourcesDownloader", "com.worklight.androidgap.plugin.SSLWebResourcesDownloaderPlugin");
    paramCordovaWebView.pluginManager.addService("NativeBusyIndicator", "com.worklight.androidgap.plugin.MyBusyIndicator");
  }

  public void init()
  {
    SSLWLWebView localSSLWLWebView = new SSLWLWebView(this);
    if (Build.VERSION.SDK_INT < 11) {}
    for (Object localObject = new CordovaWebViewClient(this, localSSLWLWebView);; localObject = new IceCreamCordovaWebViewClient(this, localSSLWLWebView))
    {
      super.init(localSSLWLWebView, (CordovaWebViewClient)localObject, new CordovaChromeClient(this, localSSLWLWebView));
      return;
    }
  }

知道是怎么回事,就得提取网页资源了。apk 安装包中的 assets/www 目录里有两个 resources.zip 文件,但似乎经过特殊处理,直接用 WinRAR 解压缩无法打开。另辟奚径,把手机程序安装到 Android 手机里(模拟器也行),然后用手机文件管理器打开 /data/data/com.MobileTicket 这个数据目录,就能看到程序使用的关键数据了。网页就存在 files/www/default 这里。

看了看代码,是基于 worklight 框架用 MVC 的方式实现的,view 都是 html 网页,js 来实现 model 和 controller,12306 的程序猿很厚道,js 都没有混淆,连中文注释都在,读起来十分方便,嘿嘿。比较关键的有 MobileTicket.js util.js 还有 controller 目录里的各个 js,看看文件名就知道是用来干什么的很好读。

这里有一个比较关键的 CheckCodePlugin(此 check code 并非我们看到的四位验证码,而是请求里自带的一个参数),通过网络抓包也会发现每次都会提交一个请求的参数是check_code,而这个东东实现上貌似是由 libcheckcode.so 这个 native 库来实现的(CheckCodePlugin.js 中有一份调试版本,但未可知调试版本与实现版本有何不同)。把 libcheckcode.so 放到 IDA Pro 里进行反汇编分析,在导出表中能看到 main, MD5_Init, MD5_Update 等几个模块,看了看汇编代码,也就是简单地做了个 MD5 Hash,看来 libcheckcode.so 与 js 的行为应该是一致的。很好奇为什么做 MD5 这活儿非要用一个 native 的库来完成,为了故弄玄虚?也许是为了保密或者准备以后换成更复杂的 checkcode 算法。

关于抓包,12306 App 需要读取本地网页,单纯给 Android 设置系统级代理然后在 PC 上用 Fiddler 抓包的话,打开程序便会跳出错误,因为无法读取本地的 file:/// 开头的 URL (其实就是本地网页)。如此,需要用 ProxyDroid 这样的代理 App,使之忽略掉 localhost 和 intranet address,然后在 Fiddler 中启用 HTTPS 分析,就可以轻松抓到 12306 手机 App 的协议。

协议中最开始有 IBM Worklight 框架自带的验证过程,由于此验证过程由 Worklight 框架实现,分析起来较为复杂。App 里用到了 wl_antiXSRFRealm,wl_deviceNoProvisioningRealm 和 wl_authenticityRealm,这些实现都可以在 apk java 源代码的 com.worklight.wlclient.challengehandler 包中找到。

以上是主要的研究心得。最开始以为 libcheckcode.so 是用来生成图片验证码的,这样 12306 App 实际上不需要从服务器取得验证码,而是在本地完成,没想到这个猜测是错误的。实际上 App 仍然要从服务器请求验证码,这一流程在 orderManager.js 中可以看到 refreshCaptcha() 即是。总结一下,手机协议的优势是验证码简单容易破解,但是代价是 worklight 框架复杂,初始验证过程不好模拟,且容易被服务器端的协议升级所反制,写一个手机协议的刷票软件仍然是十分费力且不讨好的事。

缺乏 ipv6 支持导致的 curl 库错误

wordpress 的 dashboard 中报错 WP HTTP: getpeername() failed with errno 57: Socket is not connected

上 Google 搜,也有人遇到了不过没有看到解决方案。自己通过 nslookup 查询以及 curl -v 调用,发现问题的来源是 curl 在 DNS 解析的时候,遇到 DNS 的 A 记录中有 ipv6 地址,就会出现这个问题。查询 port 里 curl 的安装选项,发现关掉了 ipv6 支持,因此 A 记录中的 ipv6 地址不能访问,就产生了以上的问题。

感叹一下国内的 ipv6 部署进展太慢了,以至于俺也没有动力在 vps 上折腾 ipv6 。