Android VPN编程
来源:互联网 发布:高韶青 离开中国 知乎 编辑:程序博客网 时间:2024/04/30 09:42
最近公司需要开发android vpn相关方面的功能,但是网上的资料并不多,本人经过大量的搜索后,最终找到了两个可行的方法:
(1)用官方提供的VpnService进行开发,具体怎么样开发可以搜索官方toyVpn例子来看一下。
(2)查看Android源码,利用反射调用隐藏的API开启VPN。
由于项目中需要用PPTP协议进行VPN连接,如果用第一种方法进行开发的话,因为官方没有提供PPTP的实现类,需要自己实现协议,而我在stackOverflow和github上都没有找到可用的代码,所以我只好选择利用第二种方法进行开发。
throw new IllegalStateException("KeyStore isn't unlocked");
}
(1)用官方提供的VpnService进行开发,具体怎么样开发可以搜索官方toyVpn例子来看一下。
(2)查看Android源码,利用反射调用隐藏的API开启VPN。
由于项目中需要用PPTP协议进行VPN连接,如果用第一种方法进行开发的话,因为官方没有提供PPTP的实现类,需要自己实现协议,而我在stackOverflow和github上都没有找到可用的代码,所以我只好选择利用第二种方法进行开发。
首先我们先看源码,我的源码版本是4.4.4,在\packages\apps\Settings\src\com\android\settings\vpn2包下找到VPN相关的类,首先我们来看VpnDialog.java。
VpnProfile getProfile() { // First, save common fields. VpnProfile profile = new VpnProfile(mProfile.key); profile.name = mName.getText().toString(); profile.type = mType.getSelectedItemPosition(); profile.server = mServer.getText().toString().trim(); profile.username = mUsername.getText().toString(); profile.password = mPassword.getText().toString(); profile.searchDomains = mSearchDomains.getText().toString().trim(); profile.dnsServers = mDnsServers.getText().toString().trim(); profile.routes = mRoutes.getText().toString().trim(); // Then, save type-specific fields. switch (profile.type) { case VpnProfile.TYPE_PPTP: profile.mppe = mMppe.isChecked(); break; case VpnProfile.TYPE_L2TP_IPSEC_PSK: profile.l2tpSecret = mL2tpSecret.getText().toString(); // fall through case VpnProfile.TYPE_IPSEC_XAUTH_PSK: profile.ipsecIdentifier = mIpsecIdentifier.getText().toString(); profile.ipsecSecret = mIpsecSecret.getText().toString(); break; case VpnProfile.TYPE_L2TP_IPSEC_RSA: profile.l2tpSecret = mL2tpSecret.getText().toString(); // fall through case VpnProfile.TYPE_IPSEC_XAUTH_RSA: if (mIpsecUserCert.getSelectedItemPosition() != 0) { profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem(); } // fall through case VpnProfile.TYPE_IPSEC_HYBRID_RSA: if (mIpsecCaCert.getSelectedItemPosition() != 0) { profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem(); } if (mIpsecServerCert.getSelectedItemPosition() != 0) { profile.ipsecServerCert = (String) mIpsecServerCert.getSelectedItem(); } break; } profile.saveLogin = mSaveLogin.isChecked(); return profile; }
以上是一部分VpnDialog的一部分代码,由此可以知道创建VPN连接的相关信息被封装在VpnProfile中。
我们再来看VpnSettings.java。
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.vpn_create: { // Generate a new key. Here we just use the current time. // 生成VpnProfile的key long millis = System.currentTimeMillis(); while (mPreferences.containsKey(Long.toHexString(millis))) { ++millis; } //弹出VpnDialog mDialog = new VpnDialog( getActivity(), this, new VpnProfile(Long.toHexString(millis)), true); mDialog.setOnDismissListener(this); mDialog.show(); return true; } case R.id.vpn_lockdown: { LockdownConfigFragment.show(this); return true; } } return super.onOptionsItemSelected(item); } @Override public void onResume() { super.onResume(); final boolean pickLockdown = getActivity() .getIntent().getBooleanExtra(EXTRA_PICK_LOCKDOWN, false); if (pickLockdown) { LockdownConfigFragment.show(this); } // Check KeyStore here, so others do not need to deal with it. // j检查是否设置了锁屏密码 if (!mKeyStore.isUnlocked()) { if (!mUnlocking) { // Let us unlock KeyStore. See you later! Credentials.getInstance().unlock(getActivity()); } else { // We already tried, but it is still not working! finishFragment(); } mUnlocking = !mUnlocking; return; } // Now KeyStore is always unlocked. Reset the flag. mUnlocking = false; // Currently we are the only user of profiles in KeyStore. // Assuming KeyStore and KeyGuard do the right thing, we can // safely cache profiles in the memory. if (mPreferences.size() == 0) { PreferenceGroup group = getPreferenceScreen(); final Context context = getActivity(); final Listprofiles = loadVpnProfiles(mKeyStore); for (VpnProfile profile : profiles) { final VpnPreference pref = new VpnPreference(context, profile); pref.setOnPreferenceClickListener(this); mPreferences.put(profile.key, pref); group.addPreference(pref); } } // Show the dialog if there is one. if (mDialog != null) { mDialog.setOnDismissListener(this); mDialog.show(); } // Start monitoring. if (mUpdater == null) { mUpdater = new Handler(this); } mUpdater.sendEmptyMessage(0); // Register for context menu. Hmmm, getListView() is hidden? registerForContextMenu(getListView()); } @Override public void onClick(DialogInterface dialog, int button) { if (button == DialogInterface.BUTTON_POSITIVE) { // Always save the profile. VpnProfile profile = mDialog.getProfile(); //将VpnProfile对象存储起来,有KeyStore.java可知,KeyStore.UID_SELF = -1,KeyStore.FLAG_ENCRYPTED = 1 mKeyStore.put(Credentials.VPN + profile.key, profile.encode(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED); // Update the preference. VpnPreference preference = mPreferences.get(profile.key); if (preference != null) { disconnect(profile.key); preference.update(profile); } else { preference = new VpnPreference(getActivity(), profile); preference.setOnPreferenceClickListener(this); mPreferences.put(profile.key, preference); getPreferenceScreen().addPreference(preference); } // If we are not editing, connect! if (!mDialog.isEditing()) { try { connect(profile); } catch (Exception e) { Log.e(TAG, "connect", e); } } } } private void connect(VpnProfile profile) throws Exception { try { //连接 mService.startLegacyVpn(profile); } catch (IllegalStateException e) { Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show(); } } private void disconnect(String key) { if (mInfo != null && key.equals(mInfo.key)) { try { //断开连接 mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN); } catch (Exception e) { // ignore } } } //获取已经创建的VPN对应的VpnProfile对象 private static List loadVpnProfiles(KeyStore keyStore, int... excludeTypes) { final ArrayList result = Lists.newArrayList(); final String[] keys = keyStore.saw(Credentials.VPN); if (keys != null) { for (String key : keys) {//Credentials.VPN 是“VPN_”,key是VpnProfile.key,是我们随机生成的字符串 final VpnProfile profile = VpnProfile.decode( key, keyStore.get(Credentials.VPN + key)); if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) { result.add(profile); } } } return result; }
以上我们基本理清了思路,重要的代码也就那么几行,接下来我们就可以利用反射进行编程了,以下是我的代码:
package com.smartgiant.util;import android.content.Context;import android.net.ConnectivityManager;import android.util.Log;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.net.Inet4Address;import java.net.InetAddress;import java.net.NetworkInterface;import java.net.SocketException;import java.util.Enumeration;public class VpnUtil { private static Class vpnProfileClz; private static Class credentialsClz; private static Class keyStoreClz; private static Class iConManagerClz; private static Object iConManagerObj; /** * 使用其他方法前先调用该方法 * 初始化vpn相关的类 * @param context */ public static void init(Context context){ try { vpnProfileClz = Class.forName("com.android.internal.net.VpnProfile"); keyStoreClz = Class.forName("android.security.KeyStore"); ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); Field fieldIConManager = null; fieldIConManager = cm.getClass().getDeclaredField("mService"); fieldIConManager.setAccessible(true); iConManagerObj = fieldIConManager.get(cm); iConManagerClz = Class.forName(iConManagerObj.getClass().getName()); } catch (Exception e) { e.printStackTrace(); } } /** * @param name vpn连接名,自定义 * @param server 服务器地址 * @param username 用户名 * @param password 用户密码 * @return 返回一个com.android.internal.net.VpnProfile的实例 */ public static Object createVpnProfile(String name, String server, String username, String password) { Object vpnProfileObj = null; try { //生成vpn的key long millis = System.currentTimeMillis(); String vpnKey = Long.toHexString(millis); //获得构造函数 Constructor constructor = vpnProfileClz.getConstructor(String.class); vpnProfileObj = constructor.newInstance(vpnKey); //设置参数 setParams(vpnProfileObj,name,server,username,password); //插入vpn数据 insertVpn(vpnProfileObj,vpnKey); } catch (Exception e) { e.printStackTrace(); } return vpnProfileObj; } /** * @param name vpn连接名,自定义 * @param server 服务器地址 * @param username 用户名 * @param password 用户密码 * @return 返回一个com.android.internal.net.VpnProfile的实例 */ public static Object setParams(Object vpnProfileObj,String name, String server, String username, String password) { try { Field field_username = vpnProfileClz.getDeclaredField("username"); Field field_password = vpnProfileClz.getDeclaredField("password"); Field field_server = vpnProfileClz.getDeclaredField("server"); Field field_name = vpnProfileClz.getDeclaredField("name"); //设置参数 field_name.set(vpnProfileObj, name); field_server.set(vpnProfileObj, server); field_username.set(vpnProfileObj, username); field_password.set(vpnProfileObj, password); } catch (Exception e) { e.printStackTrace(); } return vpnProfileObj; } /** * 连接vpn * @param context * @param profile com.android.internal.net.VpnProfile的实例 * @return true:连接成功,false:连接失败 */ public static boolean connect(Context context, Object profile) { boolean isConnected = true; try { Method metStartLegacyVpn = iConManagerClz.getDeclaredMethod("startLegacyVpn", vpnProfileClz); metStartLegacyVpn.setAccessible(true); //解锁KeyStore unlock(context); //开启vpn连接 metStartLegacyVpn.invoke(iConManagerObj, profile); } catch (Exception e) { isConnected = false; e.printStackTrace(); } return isConnected; } /** * 断开vpn连接 * @param context * @return true:已断开,false:断开失败 */ public static boolean disconnect(Context context) { boolean disconnected = true; try { Method metPrepare = iConManagerClz.getDeclaredMethod("prepareVpn", String.class, String.class); //断开连接 metPrepare.invoke(iConManagerObj, "[Legacy VPN]", "[Legacy VPN]"); } catch (Exception e) { disconnected = false; e.printStackTrace(); } return disconnected; } /** * @return 返回一个已存在的vpn实例 */ public static Object getVpnProfile() { try { Object keyStoreObj = getKeyStoreInstance(); Method keyStore_saw = keyStoreClz.getMethod("saw",String.class); keyStore_saw.setAccessible(true); //查找数据库 String[] keys = (String[]) keyStore_saw.invoke(keyStoreObj,"VPN_"); //如果之前没有创建过vpn,则返回null if(keys == null || keys.length == 0){ return null; } for(String s : keys){ Log.i("key:",s); } Method vpnProfile_decode = vpnProfileClz.getDeclaredMethod("decode", String.class, byte[].class); vpnProfile_decode.setAccessible(true); Method keyStore_get = keyStoreClz.getDeclaredMethod("get", String.class); keyStore_get.setAccessible(true); //获得第一个vpn Object byteArrayValue = keyStore_get.invoke(keyStoreObj,"VPN_"+keys[0]); //反序列化返回VpnProfile实例 Object vpnProfileObj = vpnProfile_decode.invoke(null, keys[0], byteArrayValue); return vpnProfileObj; } catch (Exception e) { e.printStackTrace(); return null; } } private static void insertVpn(Object profieObj,String key)throws Exception{ Method keyStore_put = keyStoreClz.getDeclaredMethod("put", String.class, byte[].class, int.class, int.class); Object keyStoreObj = getKeyStoreInstance(); Class vpnProfileClz = Class.forName("com.android.internal.net.VpnProfile"); Method vpnProfile_encode = vpnProfileClz.getDeclaredMethod("encode"); byte[] bytes = (byte[]) vpnProfile_encode.invoke(profieObj); keyStore_put.invoke(keyStoreObj,"VPN_"+key,bytes,-1,1); } private static Object getKeyStoreInstance() throws Exception { Method keyStore_getInstance = keyStoreClz.getMethod("getInstance"); keyStore_getInstance.setAccessible(true); Object keyStoreObj = keyStore_getInstance.invoke(null); return keyStoreObj; } private static void unlock(Context mContext) throws Exception { credentialsClz = Class.forName("android.security.Credentials"); Method credentials_getInstance = credentialsClz.getDeclaredMethod("getInstance"); Object credentialsObj = credentials_getInstance.invoke(null); Method credentials_unlock = credentialsClz.getDeclaredMethod("unlock",Context.class); credentials_unlock.invoke(credentialsObj,mContext); }}
public void onClick(View view){ VpnUtil.init(MainActivity.this); if(view.getId() == R.id.bt_connect){ //查询检查是否已经存在VPN Object vpnProfile = VpnUtil.getVpnProfile(); if(vpnProfile == null){ vpnProfile = VpnUtil.createVpnProfile("vpn1", "192.168.191.1", "vpntest", "123456"); }else{ VpnUtil.setParams(vpnProfile,"vpn1", "192.168.191.1", "vpntest", "123456"); } //连接 VpnUtil.connect(MainActivity.this,vpnProfile); }else if(view.getId() == R.id.bt_disconnect){ //断开连接 VpnUtil.disconnect(MainActivity.this); } }
这个APP需要改变为系统应用才能使用,不然会在连接过程中出现java.lang.SecurityException: Unauthorized Caller错误,并且需要在AndroidManifest.xml文件下添加 android:sharedUserId="android.uid.system",另外如果没有设置锁屏密码,连接过程会提示设置锁屏密码,但是可以通过注释掉源码里面Vpn.java文件里的以下三行代码去掉锁屏。
if (!keyStore.isUnlocked()) {throw new IllegalStateException("KeyStore isn't unlocked");
}
以上是我对于android系统下VPN编程的一些理解,希望可以帮到大家,如果有问题,我们可以在评论区交流。
阅读全文
0 0
- Android VPN 编程
- android VPN编程
- Android VPN 编程
- Android VPN 编程
- Android VPN 编程
- Android VPN 编程
- Android VPN编程
- Android VPN 编程
- Android VPN编程
- android VPN
- android vpn pptp 自动切换VPN
- Android VPN设置
- Android VPN code
- Android Vpn 整理
- Android VPN source code
- android vpn 分析
- Android Vpn 的配置
- Android VPN source code
- SDUTOJ-3363 数据结构实验之图论七:驴友计划(Floyd)
- 树莓派DHT22传感器设置
- DB2 插入数据并返回自增长主键
- springboot使用自定义配置文件
- hdu--2222--Keywords Search
- Android VPN编程
- 【HPU1189】Ou à [数学]
- Android--资源混淆工具使用说明
- 前向渲染水效果实现
- BZOJ 1997: [Hnoi2010]Planar 2-sat
- 图像边缘锯齿及处理方法
- Go! Go! Go! 来我的另一个博客
- 8.21上课感悟
- 快速幂