• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
setUp()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
tearDown()154     protected void tearDown() throws Exception {
155         sleep(VPN_STAY_TIME);
156         super.tearDown();
157     }
158 
printVpnProfile(VpnProfile profile)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 
printKeyStore(VpnProfile profile)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      */
connect(VpnProfile profile)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      */
disconnect()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      */
getIpAddress()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      */
validateVpnConnection(VpnProfile profile)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      */
validateVpnConnection(VpnProfile profile, boolean pingTestFlag)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 
pingTest(String server)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      */
installCertificatesFromFile(VpnProfile profile, String fileName, String password)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 
sleep(long time)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
testPPTPConnection()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
testL2tpIpsecPskConnection()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
testL2tpIpsecRsaConnection()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
testIpsecXauthRsaConnection()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
testIpsecXauthPskConnection()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
testIpsecHybridRsaConnection()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