Hacking Limbo

Reading / Coding / Hacking

在 Android 上使用 gotunnel 翻墙

gotunnel 是 reus 同学用 Go 语言实现的一个 SOCKS5 代理,跟大众化的 OpenVPN 和 GoAgent 相比,gotunnel 的优势在于其私有协议没那么容易被 GFW “定点清除”(像 OpenVPN 最近就被封锁了无法连接,而 GoAgent 依赖的 Google App Engine 本身就很容易被墙)。跟它类似的代理服务器还有 shadowsocks,不过后者是用 Python 写的,占用资源较多,而且还依赖 Python 运行时环境。相反,由于 Go 程序是静态编译的,既省资源,又可以很方便地移植到不同的设备上,优势很明显。

这篇文章要讲的就是怎样在 Android 设备上使用 gotunnel 翻墙。测试的环境是已获得 root 权限的 Android 4.1 系统,涉及的操作包括交叉编译 gotunnel 和 redsocks,设置 iptables 转发规则以及解决 DNS 污染问题。事先声明:懒得折腾的同学,请考虑 GAE Proxy,SSH Tunnel,或者 VPN 这些更简易的方案。

编译 gotunnel

要编译 Android 版本的 gotunnel,有两种方法,一是在 Android 设备上安装 Go 开发环境,但目前官方下载页上面说 "no binary distribution for ARM yet",意味着这个方案不现实,遂放弃。替代的方法(其实是更正常的方法)是在桌面系统(Mac OS X / Linux / Windows)上交叉编译,指定目标系统为 ARM Linux。不过,Go 的二进制安装包并未包含交叉编译所需的工具链,需要自己编译。以下内容主要参考自 go-wiki 上的 Building windows go programs on linux 一文:

# 重新编译 Go 环境
cd $GOROOT/src && ./make.bash

# 这里只编译 ARM 架构的工具链
# 不同数字代表不同的 CPU 架构:5 - ARM, 8 - x86, 6 - amd64
for cmd in a c g l;
do
    go tool dist install -v cmd/5$cmd
done

# 安装 ARM Linux 工具链
export CGO_ENABLED=0
export GOARCH=arm
export GOOS=linux

go tool dist install -v pkg/runtime
go install -v -a std

安装完工具链,编译 gotunnel 就非常简单了(此处省去服务器部署的步骤):

git clone https://github.com/reusee/gotunnel
cd gotunnel/client

# 按自己的情况在 config.go 中设置服务器信息
CGO_ENABLED=0 GOARCH=arm GOOS=linux go build -o gotunnel

redsocks

Android 上的大多数软件都不支持单独设置代理,4.1 系统本身也没有相关的设置界面,唯一靠谱的途径是使用 redsocks + iptables,其数据传输流程大致为:app -> iptables -> redsocks -> gotunnel -> Internet. iptables 的作用是拦截所有对外的 TCP 请求,将这些请求转发到指定端口,这个端口由 redsocks 提供,并由它负责与 SOCKS 代理的客户端通信。

redsocks 同样需要交叉编译,其过程略复杂,想偷懒可以到 SSH Tunnel 主页 下载一个已经编译好的(顺便把 iptables 也下载下来,等会要用)。

想自己编译的话,步骤如下:

  1. 下载 Android NDK,利用 build/tools/make-standalone-toolchain.sh 安装工具链(我用的参数是 --toolchain=arm-linux-androideabi-4.6 --system=darwin-x86 --install-dir=/tmp/android-toolchain

  2. 下载 libevent,在这里下载 config.subconfig.guess,替换掉 libevent 原有的两个同名文件(否则 configure 脚本无法识别 Android 系统的工具链),编译并安装 libevent 到一个临时目录:

    # 请确认步骤 1 中的工具链的位置在 $PATH 中
    ./configure --host arm-linux-androideabi --prefix=/tmp/libevent
    make && make install
  3. redsocks 最新版本使用了 tsearch 函数,很悲剧的是 Android 工具链里没有提供这个库,解决方法:

    • 下载相关文件,放到 redsocks 目录里:search.h tfind.c tsearch.c tdelete.c tdestroy.c

    • redudp.c 中的 #include <search.h> 改为 #include "search.h"

    • 修改 Makefile,在 OBJS 这一行中添加 tsearch.o tdelete.o tdestroy.o tfind.o,修复之后可以正常编译:

      export CC=arm-linux-androideabi-gcc
      CFLAGS="-static -I/tmp/libevent/include" LDFLAGS="-L/tmp/libevent/lib" make

iptables

把前面编译的和下载的东西扔到 Android 上(可以安装 SSH Droid 后用 scp 传输,有 ssh 也方便进行 root 相关的操作),启动 gotunnel 和 redsocks 之后,添加 iptables 规则:

# 在 nat 表中新建一个名为 GOTUNNEL 的规则链
iptables -t nat -N GOTUNNEL
iptables -t nat -F GOTUNNEL

# 排除掉不需要走代理的 IP
iptables -t nat -A GOTUNNEL -d 192.168.0.0/16 -j RETURN

# 将其他 TCP 请求转发到 8118 端口(redsocks 监听的端口)
iptables -t nat -A GOTUNNEL -p tcp -j REDIRECT --to-ports 8118

# 启用 GOTUNNEL 规则链
iptables -t nat -A OUTPUT -p tcp -j GOTUNNEL

DNS

完成上述步骤后,仍然无法正常访问 Twitter 和 Facebook,因为 DNS 污染使得浏览器等应用无法解析到正确的 IP. (为什么 VPN 和 SSH 隧道没有这个问题呢?因为它们支持在远端解析 DNS,而 redsocks 不支持。)

解决方案:在 smarthosts 的基础上手动添加 Twitter 的 ip(我在 VPS 上逐个解析之后写到 hosts 里面)。有个叫 DNSLite 的应用提供了 DNS 代理,但没什么效果,我只用它来管理 hosts 文件。SSH Tunnel 内置的 DNS 代理效果比较好,如果能抽离出来作为单独的应用运行就好了。

THE END

至此大功告成!我的相关配置文件在这里:https://gist.github.com/4089326 (我用 Scripter 来管理启动和停止代理的脚本)