VPN的英文全称是“Virtual Private Network”,翻译过来就是“虚拟专用网络”。顾名思义,虚拟专用网络我们可以把它理解成是虚拟出来的企业内部专线。它可以通过特殊的加密的通讯协议在连接在Internet上的位于不同地方的两个或多个企业内部网之间建立一条专有的通讯线路,就好比是架设了一条专线一样,但是它并不需要真正的去铺设光缆之类的物理线路。这就好比去电信局申请专线,但是不用给铺设线路的费用,也不用购买路由器等硬件设备。VPN技术原是路由器具有的重要技术之一,目前在交换机,防火墙设备或WINDOWS2000等软件里也都支持VPN功能,一句话,VPN的核心就是在利用公共网络建立虚拟私有网。虚拟专用网(VPN)被定义为通过一个公用网络(通常是因特网)建立一个临时的、安全的连接,是一条穿过混乱的公用网络的安全、稳定的隧道。虚拟专用网是对企业公司分支机构、商业伙伴及供应商同公司的内部网建立可信的安全连接,并保证数据的安全传输。
关于Android VPN编程,网上的资料比较少,本人最近有一个关于在Android平台下开发VPN 客户端的项目,在项目刚开始的时候,进行比较艰难,主要是android(4.0之前)的VPN领域鲜有API可供直接调用,也没有官方的说明文档。经过将近一个星期的研究,终于有些头绪,在此本人愿将一些经验与大家共享。
为了研究android vpn,本人下载一些apk,通过反编译工具apktool,查看了一些代码片段,虽然反编译之后的代码不易于阅读,但是还是提供了一些有用的信息。通过对apk的
debug,本人发现很多apk都引入了xinkvpn开发包。非常感谢xinkvpn的开发者,抽取并封装了Android系统不对外访问的API,并提供了完整的代码,到目前为止可能对于SDK10之后的访问还存在问题,对于SDK14之前可以在此基础上进行一些修改,对于SDK14(Android4.0)后,则无法调用到公开的Android API接口,因此需要另外的研究。
需要注意的是,对于android 4.0之前的版本,如果采用l2tp secret,ipsec 方式,则需要获得root权限,并写入Keystore;对于PPTP和L2tp则不需要,但是因为其采用的是非加密方式,因此安全性要差一些。
关于xinkvpn,有兴趣的读者可以到https://github.com/xinthink/xinkvpn进行下载并运行,在此不再重复。
本人的项目要求是用户不需要输入用户名/密码/服务器IP等和VPN设置有关的参数,就可以直接连接到VPN。因此链接VPN的操作要在后台进行,这样就要对账户进行封装。当然本文的代码也可以供非此类情况的使用。
首先建立一个保存VPN Profile的抽象类,供PPTP,L2tp,L2tpIpsecPsk等扩展,此抽象类如下
[java] view plaincopy
- package vink.vpn.whyonly.editor;
- import xink.vpn.VpnProfileRepository;
- import xink.vpn.wrapper.InvalidProfileException;
- import xink.vpn.wrapper.KeyStore;
- import xink.vpn.wrapper.VpnProfile;
- import xink.vpn.wrapper.VpnState;
- import android.content.Context;
- /**
- * @author Whyonly
- *
- */
- public abstract class OWLVpnProfileEditor {
- private VpnProfile profile;
- private VpnProfileRepository repository;
- private KeyStore keyStore;
- private Runnable resumeAction;
- protected Context mContext;
- public OWLVpnProfileEditor(final Context context) {
- mContext = context;
- repository = VpnProfileRepository.getInstance(context);
- keyStore = new KeyStore(context);
- }
- public void onSave() {
- try {
- profile = createProfile();
- populateProfile();
- saveProfile();
- } catch (InvalidProfileException e) {
- throw e;
- }
- }
- private void populateProfile() {
- profile.setState(VpnState.IDLE);
- doPopulateProfile();
- repository.checkProfile(profile);
- }
- private void saveProfile() {
- repository.addVpnProfile(profile);
- }
- @SuppressWarnings("unchecked")
- protected <T extends VpnProfile> T getProfile() {
- return (T) profile;
- }
- protected abstract VpnProfile createProfile();
- protected abstract void doPopulateProfile();
- }
PPTP的扩展如下
[java] view plaincopy
- package vink.vpn.whyonly.editor;
- import xink.vpn.wrapper.PptpProfile;
- import xink.vpn.wrapper.VpnProfile;
- import android.content.Context;
- /**
- * @author Whyonly
- *
- */
- public class OWLPptpProfileEditor extends OWLVpnProfileEditor {
- public OWLPptpProfileEditor(final Context context) {
- super(context);
- }
- @Override
- protected VpnProfile createProfile() {
- return new PptpProfile(mContext);
- }
- @Override
- protected void doPopulateProfile() {
- PptpProfile profile = getProfile();
- profile.setName("OWLPPTP");
- profile.setServerName("0.0.0.0");
- profile.setDomainSuffices("8.8.8.8");
- profile.setUsername("whyonly");
- profile.setPassword(".....");
- profile.setEncryptionEnabled(true);
- }
- }
请自行填入关于PPTP的账户信息,如果没有的话,可以到网上找一个VPN的免费代理,申请帐号。
L2TP IP Sec的代码如下,
[java] view plaincopy
- package vink.vpn.whyonly.editor;
- import xink.vpn.wrapper.L2tpIpsecPskProfile;
- import xink.vpn.wrapper.VpnProfile;
- import android.content.Context;
- /**
- * @author Whyonly
- *
- */
- public class OWLL2tpIpsecPskProfileEditor extends OWLVpnProfileEditor {
- public OWLL2tpIpsecPskProfileEditor(final Context context) {
- super(context);
- }
- @Override
- protected VpnProfile createProfile() {
- return new L2tpIpsecPskProfile(mContext);
- }
- @Override
- protected void doPopulateProfile() {
- L2tpIpsecPskProfile p = getProfile();
- p.setName("OWLL2tpIpsecPsk");
- p.setServerName("0.0.0.0");
- p.setDomainSuffices("8.8.8.8");
- p.setUsername("xxxxxxx");
- p.setPassword("xxxx");
- p.setPresharedKey("xxxxx");
- boolean secretEnabled = false;
- p.setSecretEnabled(secretEnabled);
- p.setSecretString(secretEnabled ? "xxxx" : "");
- }
- }
请自行填入关于账户的相关信息。
建立好账户的类之后,就是把账户存入Profile,
[java] view plaincopy
- OWLVpnProfileEditor pptp = new OWLPptpProfileEditor(this);
- pptp.onSave();
- OWLVpnProfileEditor lwtpIpsec = new OWLL2tpIpsecPskProfileEditor(this);
- lwtpIpsec.onSave();
接着是连接到账户
[java] view plaincopy
- VpnActor actor = new VpnActor(getApplicationContext());
- actor.connect(profile);
最后需要对连接的状态进行监听,
[java] view plaincopy
- private void registerReceivers() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_VPN_CONNECTIVITY);
- stateBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- String action = intent.getAction();
- if (ACTION_VPN_CONNECTIVITY.equals(action)) {
- onStateChanged(intent);
- } else {
- Log.d(TAG, "VPNSettings receiver ignores intent:" + intent); //$NON-NLS-1$
- }
- }
- };
- registerReceiver(stateBroadcastReceiver, filter);
- }
- private void onStateChanged(final Intent intent) {
- //Log.d(TAG, "onStateChanged: " + intent); //$NON-NLS-1$
- final String profileName = intent.getStringExtra(BROADCAST_PROFILE_NAME);
- final VpnState state = Utils.extractVpnState(intent);
- final int err = intent.getIntExtra(BROADCAST_ERROR_CODE, VPN_ERROR_NO_ERROR);
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- stateChanged(profileName, state, err);
- }
- });
- }
- private void stateChanged(final String profileName, final VpnState state, final int errCode) {
- processing change
- }