1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.vpn2; 18 19 import android.content.Context; 20 import android.net.IConnectivityManager; 21 import android.net.LinkAddress; 22 import android.net.RouteInfo; 23 import android.os.Bundle; 24 import android.os.Environment; 25 import android.os.RemoteException; 26 import android.os.ServiceManager; 27 import android.security.Credentials; 28 import android.security.KeyStore; 29 import android.test.InstrumentationTestCase; 30 import android.test.InstrumentationTestRunner; 31 import android.test.suitebuilder.annotation.LargeTest; 32 import android.util.Log; 33 34 import com.android.internal.net.LegacyVpnInfo; 35 import com.android.internal.net.VpnConfig; 36 import com.android.internal.net.VpnProfile; 37 38 import junit.framework.Assert; 39 40 import org.apache.http.HttpResponse; 41 import org.apache.http.client.HttpClient; 42 import org.apache.http.client.methods.HttpGet; 43 import org.apache.http.impl.client.DefaultHttpClient; 44 import org.apache.http.util.EntityUtils; 45 import org.json.JSONException; 46 import org.json.JSONObject; 47 48 import java.io.File; 49 import java.io.FileInputStream; 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.net.UnknownHostException; 53 import java.nio.charset.StandardCharsets; 54 import java.util.List; 55 import java.util.Map; 56 57 /** 58 * Legacy VPN connection tests 59 * 60 * To run the test, use command: 61 * adb shell am instrument -e class com.android.settings.vpn2.VpnTests -e profile foo.xml 62 * -w com.android.settings.tests/android.test.InstrumentationTestRunner 63 * 64 * VPN profiles are saved in an xml file and will be loaded through {@link VpnProfileParser}. 65 * Push the profile (foo.xml) to the external storage, e.g adb push foo.xml /sdcard/ before running 66 * the above command. 67 * 68 * A typical profile looks like the following: 69 * <vpn> 70 * <name></name> 71 * <type></type> 72 * <server></server> 73 * <username></username> 74 * <password></password> 75 * <dnsServers></dnsServers> 76 * <searchDomains></searchDomains> 77 * <routes></routes> 78 * <l2tpSecret></l2tpSecret> 79 * <ipsecIdentifier></ipsecIdentifier> 80 * <ipsecSecret></ipsecSecret> 81 * <ipsecUserCert></ipsecUserCert> 82 * <ipsecCaCert></ipsecCaCert> 83 * <ipsecServerCert></ipsecServerCert> 84 * </vpn> 85 * VPN types include: TYPE_PPTP, TYPE_L2TP_IPSEC_PSK, TYPE_L2TP_IPSEC_RSA, 86 * TYPE_IPSEC_XAUTH_PSK, TYPE_IPSEC_XAUTH_RSA, TYPE_IPSEC_HYBRID_RSA 87 */ 88 public class VpnTests extends InstrumentationTestCase { 89 private static final String TAG = "VpnTests"; 90 /* Maximum time to wait for VPN connection */ 91 private static final long MAX_CONNECTION_TIME = 5 * 60 * 1000; 92 private static final long VPN_STAY_TIME = 60 * 1000; 93 private static final int MAX_DISCONNECTION_TRIES = 3; 94 private static final String EXTERNAL_SERVER = 95 "http://ip2country.sourceforge.net/ip2c.php?format=JSON"; 96 private static final String VPN_INTERFACE = "ppp0"; 97 private final IConnectivityManager mService = IConnectivityManager.Stub 98 .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); 99 private Map<Integer, VpnInfo> mVpnInfoPool = null; 100 private Context mContext; 101 private CertInstallerHelper mCertHelper = null; 102 private KeyStore mKeyStore = KeyStore.getInstance(); 103 private String mPreviousIpAddress = null; 104 private boolean DEBUG = false; 105 106 @Override 107 protected void setUp() throws Exception { 108 super.setUp(); 109 InputStream in = null; 110 InstrumentationTestRunner mRunner = (InstrumentationTestRunner)getInstrumentation(); 111 mContext = mRunner.getContext(); 112 Bundle arguments = mRunner.getArguments(); 113 String PROFILE_NAME = arguments.getString("profile"); 114 Assert.assertNotNull("Push profile to external storage and load with" 115 + "'-e profile <filename>'", PROFILE_NAME); 116 File profileFile = new File(Environment.getExternalStorageDirectory(), PROFILE_NAME); 117 in = new FileInputStream(profileFile); 118 mVpnInfoPool = VpnProfileParser.parse(in); 119 Assert.assertNotNull("no VPN profiles are parsed", mVpnInfoPool); 120 if (DEBUG) { 121 Log.v(TAG, "print out the vpn profiles"); 122 for (Map.Entry<Integer, VpnInfo> profileEntrySet: mVpnInfoPool.entrySet()) { 123 VpnInfo vpnInfo = profileEntrySet.getValue(); 124 printVpnProfile(vpnInfo.getVpnProfile()); 125 if (vpnInfo.getCertificateFile() != null) { 126 Log.d(TAG, "certificate file for this vpn is " + vpnInfo.getCertificateFile()); 127 } 128 if (vpnInfo.getPassword() != null) { 129 Log.d(TAG, "password for the certificate file is: " + vpnInfo.getPassword()); 130 } 131 } 132 } 133 // disconnect existing vpn if there is any 134 LegacyVpnInfo oldVpn = mService.getLegacyVpnInfo(); 135 if (oldVpn != null) { 136 Log.v(TAG, "disconnect legacy VPN"); 137 disconnect(); 138 // wait till the legacy VPN is disconnected. 139 int tries = 0; 140 while (tries < MAX_DISCONNECTION_TRIES && mService.getLegacyVpnInfo() != null) { 141 tries++; 142 Thread.sleep(10 * 1000); 143 Log.v(TAG, "Wait for legacy VPN to be disconnected."); 144 } 145 Assert.assertNull("Failed to disconect VPN", mService.getLegacyVpnInfo()); 146 // wait for 30 seconds after the previous VPN is disconnected. 147 sleep(30 * 1000); 148 } 149 // Create CertInstallerHelper to initialize the keystore 150 mCertHelper = new CertInstallerHelper(); 151 } 152 153 @Override 154 protected void tearDown() throws Exception { 155 sleep(VPN_STAY_TIME); 156 super.tearDown(); 157 } 158 159 private void printVpnProfile(VpnProfile profile) { 160 Log.v(TAG, "profile: "); 161 Log.v(TAG, "key: " + profile.key); 162 Log.v(TAG, "name: " + profile.name); 163 Log.v(TAG, "type: " + profile.type); 164 Log.v(TAG, "server: " + profile.server); 165 Log.v(TAG, "username: " + profile.username); 166 Log.v(TAG, "password: " + profile.password); 167 Log.v(TAG, "dnsServers: " + profile.dnsServers); 168 Log.v(TAG, "searchDomains: " + profile.searchDomains); 169 Log.v(TAG, "routes: " + profile.routes); 170 Log.v(TAG, "mppe: " + profile.mppe); 171 Log.v(TAG, "l2tpSecret: " + profile.l2tpSecret); 172 Log.v(TAG, "ipsecIdentifier: " + profile.ipsecIdentifier); 173 Log.v(TAG, "ipsecSecret: " + profile.ipsecSecret); 174 Log.v(TAG, "ipsecUserCert: " + profile.ipsecUserCert); 175 Log.v(TAG, "ipsecCaCert: " + profile.ipsecCaCert); 176 Log.v(TAG, "ipsecServerCert: " + profile.ipsecServerCert); 177 } 178 179 private void printKeyStore(VpnProfile profile) { 180 // print out the information from keystore 181 String privateKey = ""; 182 String userCert = ""; 183 String caCert = ""; 184 String serverCert = ""; 185 if (!profile.ipsecUserCert.isEmpty()) { 186 privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert; 187 byte[] value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert); 188 userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); 189 } 190 if (!profile.ipsecCaCert.isEmpty()) { 191 byte[] value = mKeyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert); 192 caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); 193 } 194 if (!profile.ipsecServerCert.isEmpty()) { 195 byte[] value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert); 196 serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); 197 } 198 Log.v(TAG, "privateKey: \n" + ((privateKey == null) ? "" : privateKey)); 199 Log.v(TAG, "userCert: \n" + ((userCert == null) ? "" : userCert)); 200 Log.v(TAG, "caCert: \n" + ((caCert == null) ? "" : caCert)); 201 Log.v(TAG, "serverCert: \n" + ((serverCert == null) ? "" : serverCert)); 202 } 203 204 /** 205 * Connect legacy VPN 206 */ 207 private void connect(VpnProfile profile) throws Exception { 208 try { 209 mService.startLegacyVpn(profile); 210 } catch (IllegalStateException e) { 211 fail(String.format("start legacy vpn: %s failed: %s", profile.name, e.toString())); 212 } 213 } 214 215 /** 216 * Disconnect legacy VPN 217 */ 218 private void disconnect() throws Exception { 219 try { 220 mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN); 221 } catch (RemoteException e) { 222 Log.e(TAG, String.format("disconnect VPN exception: %s", e.toString())); 223 } 224 } 225 226 /** 227 * Get external IP address 228 */ 229 private String getIpAddress() { 230 String ip = null; 231 try { 232 HttpClient httpClient = new DefaultHttpClient(); 233 HttpGet httpGet = new HttpGet(EXTERNAL_SERVER); 234 HttpResponse httpResponse = httpClient.execute(httpGet); 235 Log.i(TAG, "Response from httpget: " + httpResponse.getStatusLine().toString()); 236 237 String entityStr = EntityUtils.toString(httpResponse.getEntity()); 238 JSONObject json_data = new JSONObject(entityStr); 239 ip = json_data.getString("ip"); 240 Log.v(TAG, "json_data: " + ip); 241 } catch (IllegalArgumentException e) { 242 Log.e(TAG, "exception while getting external IP: " + e.toString()); 243 } catch (IOException e) { 244 Log.e(TAG, "IOException while getting IP: " + e.toString()); 245 } catch (JSONException e) { 246 Log.e(TAG, "exception while creating JSONObject: " + e.toString()); 247 } 248 return ip; 249 } 250 251 /** 252 * Verify the vpn connection by checking the VPN state and external IP 253 */ 254 private void validateVpnConnection(VpnProfile profile) throws Exception { 255 validateVpnConnection(profile, false); 256 } 257 258 /** 259 * Verify the vpn connection by checking the VPN state, external IP or ping test 260 */ 261 private void validateVpnConnection(VpnProfile profile, boolean pingTestFlag) throws Exception { 262 LegacyVpnInfo legacyVpnInfo = mService.getLegacyVpnInfo(); 263 Assert.assertTrue(legacyVpnInfo != null); 264 265 long start = System.currentTimeMillis(); 266 while (((System.currentTimeMillis() - start) < MAX_CONNECTION_TIME) && 267 (legacyVpnInfo.state != LegacyVpnInfo.STATE_CONNECTED)) { 268 Log.v(TAG, "vpn state: " + legacyVpnInfo.state); 269 sleep(10 * 1000); 270 legacyVpnInfo = mService.getLegacyVpnInfo(); 271 } 272 273 // the vpn state should be CONNECTED 274 Assert.assertTrue(legacyVpnInfo.state == LegacyVpnInfo.STATE_CONNECTED); 275 if (pingTestFlag) { 276 Assert.assertTrue(pingTest(profile.server)); 277 } else { 278 String curIpAddress = getIpAddress(); 279 // the outgoing IP address should be the same as the VPN server address 280 Assert.assertEquals(profile.server, curIpAddress); 281 } 282 } 283 284 private boolean pingTest(String server) { 285 final long PING_TIMER = 3 * 60 * 1000; // 3 minutes 286 if (server == null || server.isEmpty()) { 287 return false; 288 } 289 long startTime = System.currentTimeMillis(); 290 while ((System.currentTimeMillis() - startTime) < PING_TIMER) { 291 try { 292 Log.v(TAG, "Start ping test, ping " + server); 293 Process p = Runtime.getRuntime().exec("ping -c 10 -w 100 " + server); 294 int status = p.waitFor(); 295 if (status == 0) { 296 // if any of the ping test is successful, return true 297 return true; 298 } 299 } catch (UnknownHostException e) { 300 Log.e(TAG, "Ping test Fail: Unknown Host"); 301 } catch (IOException e) { 302 Log.e(TAG, "Ping test Fail: IOException"); 303 } catch (InterruptedException e) { 304 Log.e(TAG, "Ping test Fail: InterruptedException"); 305 } 306 } 307 // ping test timeout 308 return false; 309 } 310 311 /** 312 * Install certificates from a file loaded in external stroage on the device 313 * @param profile vpn profile 314 * @param fileName certificate file name 315 * @param password password to extract certificate file 316 */ 317 private void installCertificatesFromFile(VpnProfile profile, String fileName, String password) 318 throws Exception { 319 if (profile == null || fileName == null || password == null) { 320 throw new Exception ("vpn profile, certificate file name and password can not be null"); 321 } 322 323 int curUid = mContext.getUserId(); 324 mCertHelper.installCertificate(profile, fileName, password); 325 326 if (DEBUG) { 327 printKeyStore(profile); 328 } 329 } 330 331 private void sleep(long time) { 332 try { 333 Thread.sleep(time); 334 } catch (InterruptedException e) { 335 Log.e(TAG, "interrupted: " + e.toString()); 336 } 337 } 338 339 /** 340 * Test PPTP VPN connection 341 */ 342 @LargeTest 343 public void testPPTPConnection() throws Exception { 344 mPreviousIpAddress = getIpAddress(); 345 VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_PPTP); 346 VpnProfile vpnProfile = curVpnInfo.getVpnProfile(); 347 connect(vpnProfile); 348 validateVpnConnection(vpnProfile); 349 } 350 351 /** 352 * Test L2TP/IPSec PSK VPN connection 353 */ 354 @LargeTest 355 public void testL2tpIpsecPskConnection() throws Exception { 356 mPreviousIpAddress = getIpAddress(); 357 VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_L2TP_IPSEC_PSK); 358 VpnProfile vpnProfile = curVpnInfo.getVpnProfile(); 359 connect(vpnProfile); 360 validateVpnConnection(vpnProfile); 361 } 362 363 /** 364 * Test L2TP/IPSec RSA VPN connection 365 */ 366 @LargeTest 367 public void testL2tpIpsecRsaConnection() throws Exception { 368 mPreviousIpAddress = getIpAddress(); 369 VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_L2TP_IPSEC_RSA); 370 VpnProfile vpnProfile = curVpnInfo.getVpnProfile(); 371 if (DEBUG) { 372 printVpnProfile(vpnProfile); 373 } 374 String certFile = curVpnInfo.getCertificateFile(); 375 String password = curVpnInfo.getPassword(); 376 installCertificatesFromFile(vpnProfile, certFile, password); 377 connect(vpnProfile); 378 validateVpnConnection(vpnProfile); 379 } 380 381 /** 382 * Test IPSec Xauth RSA VPN connection 383 */ 384 @LargeTest 385 public void testIpsecXauthRsaConnection() throws Exception { 386 mPreviousIpAddress = getIpAddress(); 387 VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_XAUTH_RSA); 388 VpnProfile vpnProfile = curVpnInfo.getVpnProfile(); 389 if (DEBUG) { 390 printVpnProfile(vpnProfile); 391 } 392 String certFile = curVpnInfo.getCertificateFile(); 393 String password = curVpnInfo.getPassword(); 394 installCertificatesFromFile(vpnProfile, certFile, password); 395 connect(vpnProfile); 396 validateVpnConnection(vpnProfile); 397 } 398 399 /** 400 * Test IPSec Xauth PSK VPN connection 401 */ 402 @LargeTest 403 public void testIpsecXauthPskConnection() throws Exception { 404 VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_XAUTH_PSK); 405 VpnProfile vpnProfile = curVpnInfo.getVpnProfile(); 406 if (DEBUG) { 407 printVpnProfile(vpnProfile); 408 } 409 connect(vpnProfile); 410 validateVpnConnection(vpnProfile, true); 411 } 412 413 /** 414 * Test IPSec Hybrid RSA VPN connection 415 */ 416 @LargeTest 417 public void testIpsecHybridRsaConnection() throws Exception { 418 mPreviousIpAddress = getIpAddress(); 419 VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_HYBRID_RSA); 420 VpnProfile vpnProfile = curVpnInfo.getVpnProfile(); 421 if (DEBUG) { 422 printVpnProfile(vpnProfile); 423 } 424 connect(vpnProfile); 425 validateVpnConnection(vpnProfile); 426 } 427 } 428