编译Android版本的OpenVPN

0x00 前言

目前网上看到的Android端OpenVPN客户端都是基于VPNService开发的,这种方法不需要root权限,因此使用非常广泛。但是,这种方法需要UI操作,还是不够完美,我更喜欢使用纯命令行的openvpn(需要root权限)。

以下是编译2.3.17 x86版本openvpn过程的记录。
编译环境为:Ubuntu 14.04 64位系统

源码下载地址为:http://build.openvpn.net/downloads/releases/

0x01 依赖库编译

openvpn依赖的库主要有:openssl、liblzo

编译openssl

github上找到一个提供编译方法的项目,它提供的是openssl-1.1.0f版本的编译,按照如下命令可以很快编译出x86版本的静态库。

./build-openssl4android.sh android-x86 x86

但是后来发现,openvpn用到的一些函数已经不支持openssl 1.1以上版本,因此改用1.0.2版本的openssl。下载地址为:https://www.openssl.org/source/old/1.0.2/

修改build-openssl4android.sh中openssl的文件名,然后运行脚本编译。完成后在会在libs\x86\lib目录下生成libcrypto.alibssl.a两个文件。

网上还找到另外一种方法,原理是一致的,方法略有差异。

export CC="$STANDALONE_TOOCHAIN_PATH/bin/i686-linux-android-gcc -mtune=atom -march=atom --sysroot=$STANDALONE_TOOCHAIN_PATH/sysroot"
export AR=$STANDALONE_TOOCHAIN_PATH/bin/i686-linux-android-ar
export RANLIB=$STANDALONE_TOOCHAIN_PATH/bin/i686-linux-android-ranlib
./Configure android-x86 -DOPENSSL_IA32_SSE2 -DAES_ASM -DVPAES_ASM
make

$STANDALONE_TOOCHAIN_PATH是生成的工具链目录,使用build-openssl4android.sh会自动生成该目录。

经过测试,也是可以的。

编译liblzo

liblzo源码可以从http://www.oberhumer.com/opensource/lzo/download/下载。

编译可以参考这个项目,在源码根目录下创建jni目录并进入该目录,创建Android.mk文件,输入以下内容:

LOCAL_PATH:= $(call my-dir)

common_SRC_FILES:=  \
    ../src/lzo_crc.c \
    ../src/lzo_init.c \
    ../src/lzo_ptr.c \
    ../src/lzo_str.c \
    ../src/lzo_util.c \
    ../src/lzo1.c \
    ../src/lzo1_99.c \
    ../src/lzo1a.c \
    ../src/lzo1a_99.c \
    ../src/lzo1b_1.c \
    ../src/lzo1b_2.c \
    ../src/lzo1b_3.c \
    ../src/lzo1b_4.c \
    ../src/lzo1b_5.c \
    ../src/lzo1b_6.c \
    ../src/lzo1b_7.c \
    ../src/lzo1b_8.c \
    ../src/lzo1b_9.c \
    ../src/lzo1b_99.c \
    ../src/lzo1b_9x.c \
    ../src/lzo1b_cc.c \
    ../src/lzo1b_d1.c \
    ../src/lzo1b_d2.c \
    ../src/lzo1b_rr.c \
    ../src/lzo1b_xx.c \
    ../src/lzo1c_1.c \
    ../src/lzo1c_2.c \
    ../src/lzo1c_3.c \
    ../src/lzo1c_4.c \
    ../src/lzo1c_5.c \
    ../src/lzo1c_6.c \
    ../src/lzo1c_7.c \
    ../src/lzo1c_8.c \
    ../src/lzo1c_9.c \
    ../src/lzo1c_99.c \
    ../src/lzo1c_d1.c \
    ../src/lzo1c_d2.c \
    ../src/lzo1c_rr.c \
    ../src/lzo1c_xx.c \
    ../src/lzo1f_1.c \
    ../src/lzo1f_9x.c \
    ../src/lzo1f_d1.c \
    ../src/lzo1f_d2.c \
    ../src/lzo1x_1.c \
    ../src/lzo1x_9x.c \
    ../src/lzo1x_d1.c \
    ../src/lzo1x_d2.c \
    ../src/lzo1x_d3.c \
    ../src/lzo1x_o.c \
    ../src/lzo1x_1k.c \
    ../src/lzo1x_1l.c \
    ../src/lzo1x_1o.c \
    ../src/lzo1y_1.c \
    ../src/lzo1y_9x.c \
    ../src/lzo1y_d1.c \
    ../src/lzo1y_d2.c \
    ../src/lzo1y_d3.c \
    ../src/lzo1y_o.c \
    ../src/lzo1z_9x.c \
    ../src/lzo1z_d1.c \
    ../src/lzo1z_d2.c \
    ../src/lzo1z_d3.c \
    ../src/lzo2a_9x.c \
    ../src/lzo2a_d1.c \
    ../src/lzo2a_d2.c


common_C_INCLUDES += $(LOCAL_PATH)/../include

# static library
# =====================================================

include $(CLEAR_VARS)
LOCAL_SRC_FILES:= $(common_SRC_FILES)
LOCAL_C_INCLUDES:= $(common_C_INCLUDES)
LOCAL_MODULE := liblzo
LOCAL_PRELINK_MODULE:= false
include $(BUILD_STATIC_LIBRARY)

然后,再创建Application.mk文件,输入以下内容:

# The ARMv7 is significanly faster due to the use of the hardware FPU
NDK_TOOLCHAIN_VERSION := 4.9
APP_ABI := x86  
#
APP_PLATFORM := android-8
#APP_STL:=stlport_static
APP_STL := gnustl_static
APP_CPPFLAGS += -std=c++11

这样可以只编译x86版本。

最后,调用ndk编译($NDK为NDK根目录)。

$NDK/ndk-build

编译完会生成/path/to/liblzo/source/obj/local/x86/liblzo.a文件。

0x02 编译openvpn

环境准备

进入openvpn源码根目录,执行:

./configure

这步操作主要是为了生成config.h文件,里面包含了编译时用到的各种宏定义。

在源码根目录下创建jni目录,将config.h拷贝到jni目录中,并编辑该文件。

  • 注释掉#define HAVE_GETPASS 1行,因为Android中没有getpass函数
  • #define IFCONFIG_PATH "/sbin/ifconfig"修改为#define IFCONFIG_PATH "/system/bin/ifconfig"
  • #define IPROUTE_PATH "/sbin/ip"修改为#define IPROUTE_PATH "/system/bin/ip"
  • #define ROUTE_PATH "/sbin/route"修改为#define ROUTE_PATH "/system/bin/route"

进入jni目录,使用前面的方法创建Application.mk文件;然后创建Android.mk文件,输入以下内容(修改自:https://github.com/fries/android-external-openvpn/blob/master/Android.mk):

LOCAL_PATH:= $(call my-dir)

#on a 32bit maschine run ./configure --enable-password-save --disable-pkcs11 --with-ifconfig-path=/system/bin/ifconfig --with-route-path=/system/bin/route
#from generated Makefile copy variable contents of openvpn_SOURCES to common_SRC_FILES
# append missing.c to the end of the list
# missing.c defines undefined functions.
# in tun.c replace /dev/net/tun with /dev/tun


include $(CLEAR_VARS)
LOCAL_MODULE := libcrypto
LOCAL_SRC_FILES := $(LOCAL_PATH)/openssl/$(TARGET_ARCH_ABI)/lib/libcrypto.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libssl
LOCAL_SRC_FILES := $(LOCAL_PATH)/openssl/$(TARGET_ARCH_ABI)/lib/libssl.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := liblzo
LOCAL_SRC_FILES := $(LOCAL_PATH)/liblzo/$(TARGET_ARCH_ABI)/lib/liblzo.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

common_SRC_FILES:= \
        ../src/openvpn/openvpn.c \
        ../src/openvpn/base64.c \
        ../src/openvpn/buffer.c \
        ../src/openvpn/clinat.c \
        ../src/openvpn/console.c \
        ../src/openvpn/cryptoapi.c \
        ../src/openvpn/crypto.c \
        ../src/openvpn/crypto_openssl.c \
        ../src/openvpn/dhcp.c \
        ../src/openvpn/error.c \
        ../src/openvpn/event.c \
        ../src/openvpn/fdmisc.c \
        ../src/openvpn/forward.c \
        ../src/openvpn/fragment.c \
        ../src/openvpn/gremlin.c \
        ../src/openvpn/helper.c \
        ../src/openvpn/httpdigest.c \
        ../src/openvpn/init.c \
        ../src/openvpn/interval.c \
        ../src/openvpn/list.c \
        ../src/openvpn/lladdr.c \
        ../src/openvpn/lzo.c \
        ../src/openvpn/manage.c \
        ../src/openvpn/mbuf.c \
        ../src/openvpn/misc.c \
        ../src/openvpn/mroute.c \
        ../src/openvpn/mss.c \
        ../src/openvpn/mstats.c \
        ../src/openvpn/mtcp.c \
        ../src/openvpn/mtu.c \
        ../src/openvpn/mudp.c \
        ../src/openvpn/multi.c \
        ../src/openvpn/ntlm.c \
        ../src/openvpn/occ.c \
        ../src/openvpn/options.c \
        ../src/openvpn/otime.c \
        ../src/openvpn/packet_id.c \
        ../src/openvpn/perf.c \
        ../src/openvpn/pf.c \
        ../src/openvpn/ping.c \
        ../src/openvpn/pkcs11.c \
        ../src/openvpn/pkcs11_openssl.c \
        ../src/openvpn/platform.c \
        ../src/openvpn/plugin.c \
        ../src/openvpn/pool.c \
        ../src/openvpn/proto.c \
        ../src/openvpn/proxy.c \
        ../src/openvpn/ps.c \
        ../src/openvpn/push.c \
        ../src/openvpn/reliable.c \
        ../src/openvpn/route.c \
        ../src/openvpn/schedule.c \
        ../src/openvpn/session_id.c \
        ../src/openvpn/shaper.c \
        ../src/openvpn/sig.c \
        ../src/openvpn/socket.c \
        ../src/openvpn/socks.c \
        ../src/openvpn/ssl.c \
        ../src/openvpn/ssl_openssl.c \
        ../src/openvpn/ssl_verify.c \
        ../src/openvpn/ssl_verify_openssl.c \
        ../src/openvpn/status.c \
        ../src/openvpn/tun.c 

common_CFLAGS += -DHAVE_CONFIG_H

common_C_INCLUDES += \
    $(LOCAL_PATH)/openssl/include \
    $(LOCAL_PATH)/liblzo/include \
    $(LOCAL_PATH)/../include \
    $(LOCAL_PATH)/../src/compat

common_SHARED_LIBRARIES := 



# static linked binary
# =====================================================

include $(CLEAR_VARS)
LOCAL_SRC_FILES:= $(common_SRC_FILES)
LOCAL_CFLAGS:= $(common_CFLAGS)
LOCAL_C_INCLUDES:= $(common_C_INCLUDES)

LOCAL_SHARED_LIBRARIES += $(common_SHARED_LIBRARIES)
LOCAL_STATIC_LIBRARIES:= libssl libcrypto liblzo
LOCAL_LDLIBS += -lz

LOCAL_MODULE:= openvpn
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
include $(BUILD_EXECUTABLE)

libcrypto.alibssl.a拷贝到jni/openssl/x86/lib目录下(自行创建)。
liblzo.a拷贝到jni/liblzo/x86/lib目录下。
将liblzo源码根目录下的include目录拷贝到jni/liblzo目录下。
将openssl源码根目录下的include目录拷贝到jni/openssl目录下。

开始编译

$NDK/ndk-build

完成后,会生成/path/to/openvpn/source/libs/x86/openvpn文件。

0x03 使用Android模拟器测试

openvpn拷贝到模拟器,修改可执行权限;准备好ovpn文件。

  1. 确保ovpn文件中存在dev-node /dev/tun这行,因为Android中和Linux中tun设备路径不一致
  2. 由于默认临时目录为/tmp,需要修改为export TMPDIR=/data/local/tmp;不要通过在命令行增加--tmp-dir /data/local/tmp的方法来实现,否则会导致ovpn文件里的配置项无法被正常加载
  3. ip rule add from 0/0 table main pref 1000修改默认路由表的优先级,避免openvpn修改的路由信息不生效(我不确定这种做法会不会有副作用)
  4. ./openvpn client.ovpn

如果一切顺利的话,此时已经连接成功,可以使用浏览器打开ip138.com进行测试。

分享