am b79b5e8d: Merge "Add a CTS test for the VPN API." into lmp-mr1-dev
* commit 'b79b5e8d868cdf28f7edd1b7e37dc380df43a91f': Add a CTS test for the VPN API.
This commit is contained in:
31
tests/cts/hostside/Android.mk
Normal file
31
tests/cts/hostside/Android.mk
Normal file
@@ -0,0 +1,31 @@
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# Only compile source java files in this apk.
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_MODULE := CtsHostsideNetworkTests
|
||||
|
||||
LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
|
||||
|
||||
LOCAL_CTS_TEST_PACKAGE := android.net.hostsidenetwork
|
||||
|
||||
include $(BUILD_CTS_HOST_JAVA_LIBRARY)
|
||||
|
||||
# Build the test APKs using their own makefiles
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||
32
tests/cts/hostside/app/Android.mk
Normal file
32
tests/cts/hostside/app/Android.mk
Normal file
@@ -0,0 +1,32 @@
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_SDK_VERSION := current
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner ub-uiautomator
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_PACKAGE_NAME := CtsHostsideNetworkTestsApp
|
||||
|
||||
LOCAL_PROGUARD_ENABLED := disabled
|
||||
LOCAL_DEX_PREOPT := false
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
38
tests/cts/hostside/app/AndroidManifest.xml
Normal file
38
tests/cts/hostside/app/AndroidManifest.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.cts.net.hostside">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
<activity android:name=".MyActivity" />
|
||||
<service android:name=".MyVpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:name="android.support.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.cts.net.hostside" />
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.cts.net.hostside;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MyActivity extends Activity {
|
||||
private final LinkedBlockingQueue<Integer> mResult = new LinkedBlockingQueue<>(1);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
||||
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (mResult.offer(resultCode) == false) {
|
||||
throw new RuntimeException("Queue is full! This should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getResult(int timeoutMs) throws InterruptedException {
|
||||
return mResult.poll(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.cts.net.hostside;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MyVpnService extends VpnService {
|
||||
|
||||
private static String TAG = "MyVpnService";
|
||||
private static int MTU = 1799;
|
||||
|
||||
private ParcelFileDescriptor mFd = null;
|
||||
private UdpReflector mUdpReflector = null;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
String packageName = getPackageName();
|
||||
String cmd = intent.getStringExtra(packageName + ".cmd");
|
||||
if ("disconnect".equals(cmd)) {
|
||||
stop();
|
||||
} else if ("connect".equals(cmd)) {
|
||||
start(packageName, intent);
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private void start(String packageName, Intent intent) {
|
||||
Builder builder = new Builder();
|
||||
|
||||
String addresses = intent.getStringExtra(packageName + ".addresses");
|
||||
if (addresses != null) {
|
||||
String[] addressArray = addresses.split(",");
|
||||
for (int i = 0; i < addressArray.length; i++) {
|
||||
String[] prefixAndMask = addressArray[i].split("/");
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(prefixAndMask[0]);
|
||||
int prefixLength = Integer.parseInt(prefixAndMask[1]);
|
||||
builder.addAddress(address, prefixLength);
|
||||
} catch (UnknownHostException|NumberFormatException|
|
||||
ArrayIndexOutOfBoundsException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String routes = intent.getStringExtra(packageName + ".routes");
|
||||
if (routes != null) {
|
||||
String[] routeArray = routes.split(",");
|
||||
for (int i = 0; i < routeArray.length; i++) {
|
||||
String[] prefixAndMask = routeArray[i].split("/");
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(prefixAndMask[0]);
|
||||
int prefixLength = Integer.parseInt(prefixAndMask[1]);
|
||||
builder.addRoute(address, prefixLength);
|
||||
} catch (UnknownHostException|NumberFormatException|
|
||||
ArrayIndexOutOfBoundsException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String allowed = intent.getStringExtra(packageName + ".allowedapplications");
|
||||
if (allowed != null) {
|
||||
String[] packageArray = allowed.split(",");
|
||||
for (int i = 0; i < packageArray.length; i++) {
|
||||
String allowedPackage = packageArray[i];
|
||||
if (!TextUtils.isEmpty(allowedPackage)) {
|
||||
try {
|
||||
builder.addAllowedApplication(allowedPackage);
|
||||
} catch(NameNotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String disallowed = intent.getStringExtra(packageName + ".disallowedapplications");
|
||||
if (disallowed != null) {
|
||||
String[] packageArray = disallowed.split(",");
|
||||
for (int i = 0; i < packageArray.length; i++) {
|
||||
String disallowedPackage = packageArray[i];
|
||||
if (!TextUtils.isEmpty(disallowedPackage)) {
|
||||
try {
|
||||
builder.addDisallowedApplication(disallowedPackage);
|
||||
} catch(NameNotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.setMtu(MTU);
|
||||
builder.setBlocking(true);
|
||||
builder.setSession("MyVpnService");
|
||||
|
||||
Log.i(TAG, "Establishing VPN,"
|
||||
+ " addresses=" + addresses
|
||||
+ " routes=" + routes
|
||||
+ " allowedApplications=" + allowed
|
||||
+ " disallowedApplications=" + disallowed);
|
||||
|
||||
mFd = builder.establish();
|
||||
Log.i(TAG, "Established, fd=" + (mFd == null ? "null" : mFd.getFd()));
|
||||
|
||||
mUdpReflector = new UdpReflector(mFd.getFileDescriptor(), MTU);
|
||||
mUdpReflector.start();
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
if (mUdpReflector != null) {
|
||||
mUdpReflector.interrupt();
|
||||
mUdpReflector = null;
|
||||
}
|
||||
try {
|
||||
if (mFd != null) {
|
||||
Log.i(TAG, "Closing filedescriptor");
|
||||
mFd.close();
|
||||
}
|
||||
} catch(IOException e) {
|
||||
} finally {
|
||||
mFd = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stop();
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.cts.net.hostside;
|
||||
|
||||
import android.system.Os;
|
||||
import android.system.ErrnoException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
|
||||
public class UdpReflector extends Thread {
|
||||
|
||||
private static int IPV4_HEADER_LENGTH = 20;
|
||||
private static int IPV6_HEADER_LENGTH = 40;
|
||||
private static int UDP_HEADER_LENGTH = 8;
|
||||
|
||||
private static int IPV4_PROTO_OFFSET = 9;
|
||||
private static int IPV6_PROTO_OFFSET = 6;
|
||||
private static int IPPROTO_UDP = 17;
|
||||
|
||||
private static int IPV4_ADDR_OFFSET = 12;
|
||||
private static int IPV6_ADDR_OFFSET = 8;
|
||||
private static int IPV4_ADDR_LENGTH = 4;
|
||||
private static int IPV6_ADDR_LENGTH = 16;
|
||||
|
||||
private static String TAG = "UdpReflector";
|
||||
|
||||
private FileDescriptor mFd;
|
||||
private byte[] mBuf;
|
||||
|
||||
public UdpReflector(FileDescriptor fd, int mtu) {
|
||||
super("UdpReflector");
|
||||
mFd = fd;
|
||||
mBuf = new byte[mtu];
|
||||
}
|
||||
|
||||
private static void swapBytes(byte[] buf, int pos1, int pos2, int len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
byte b = buf[pos1 + i];
|
||||
buf[pos1 + i] = buf[pos2 + i];
|
||||
buf[pos2 + i] = b;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads one packet from our mFd, and possibly writes the packet back. */
|
||||
private void processPacket() {
|
||||
int len;
|
||||
try {
|
||||
len = Os.read(mFd, mBuf, 0, mBuf.length);
|
||||
} catch (ErrnoException|IOException e) {
|
||||
Log.e(TAG, "Error reading packet: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
int version = mBuf[0] >> 4;
|
||||
int addressOffset, protoOffset, headerLength, addressLength;
|
||||
if (version == 4) {
|
||||
headerLength = IPV4_HEADER_LENGTH;
|
||||
protoOffset = IPV4_PROTO_OFFSET;
|
||||
addressOffset = IPV4_ADDR_OFFSET;
|
||||
addressLength = IPV4_ADDR_LENGTH;
|
||||
} else if (version == 6) {
|
||||
headerLength = IPV6_HEADER_LENGTH;
|
||||
protoOffset = IPV6_PROTO_OFFSET;
|
||||
addressOffset = IPV6_ADDR_OFFSET;
|
||||
addressLength = IPV6_ADDR_LENGTH;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (len < headerLength + UDP_HEADER_LENGTH || mBuf[protoOffset] != IPPROTO_UDP) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap src and dst IP addresses.
|
||||
swapBytes(mBuf, addressOffset, addressOffset + addressLength, addressLength);
|
||||
|
||||
// Swap dst and src ports.
|
||||
int portOffset = headerLength;
|
||||
swapBytes(mBuf, portOffset, portOffset + 2, 2);
|
||||
|
||||
// Send the packet back. We don't need to recalculate the checksum because we didn't change
|
||||
// the packet bytes, we only moved them around.
|
||||
try {
|
||||
len = Os.write(mFd, mBuf, 0, len);
|
||||
} catch (ErrnoException|IOException e) {
|
||||
Log.e(TAG, "Error writing packet: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Log.i(TAG, "UdpReflector starting fd=" + mFd + " valid=" + mFd.valid());
|
||||
while (!interrupted() && mFd.valid()) {
|
||||
processPacket();
|
||||
}
|
||||
Log.i(TAG, "UdpReflector exiting fd=" + mFd + " valid=" + mFd.valid());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.cts.net.hostside;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.VpnService;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.SystemClock;
|
||||
import android.support.test.uiautomator.UiDevice;
|
||||
import android.support.test.uiautomator.UiObject;
|
||||
import android.support.test.uiautomator.UiObjectNotFoundException;
|
||||
import android.support.test.uiautomator.UiScrollable;
|
||||
import android.support.test.uiautomator.UiSelector;
|
||||
import android.test.MoreAsserts;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Inet6Address;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Tests for {@link DocumentsProvider} and interaction with platform intents
|
||||
* like {@link Intent#ACTION_OPEN_DOCUMENT}.
|
||||
*/
|
||||
public class VpnTest extends InstrumentationTestCase {
|
||||
|
||||
public static String TAG = "VpnTest";
|
||||
public static int TIMEOUT_MS = 3 * 1000;
|
||||
|
||||
private UiDevice mDevice;
|
||||
private MyActivity mActivity;
|
||||
private String mPackageName;
|
||||
private ConnectivityManager mCM;
|
||||
Network mNetwork;
|
||||
NetworkCallback mCallback;
|
||||
final Object mLock = new Object();
|
||||
|
||||
private boolean supportedHardware() {
|
||||
final PackageManager pm = getInstrumentation().getContext().getPackageManager();
|
||||
return !pm.hasSystemFeature("android.hardware.type.television") &&
|
||||
!pm.hasSystemFeature("android.hardware.type.watch");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
mNetwork = null;
|
||||
mCallback = null;
|
||||
|
||||
mDevice = UiDevice.getInstance(getInstrumentation());
|
||||
mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
|
||||
MyActivity.class, null);
|
||||
mPackageName = mActivity.getPackageName();
|
||||
mCM = (ConnectivityManager) mActivity.getSystemService(mActivity.CONNECTIVITY_SERVICE);
|
||||
mDevice.waitForIdle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
if (mCallback != null) {
|
||||
mCM.unregisterNetworkCallback(mCallback);
|
||||
}
|
||||
Log.i(TAG, "Stopping VPN");
|
||||
stopVpn();
|
||||
mActivity.finish();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
private void prepareVpn() throws Exception {
|
||||
final int REQUEST_ID = 42;
|
||||
|
||||
// Attempt to prepare.
|
||||
Log.i(TAG, "Preparing VPN");
|
||||
Intent intent = VpnService.prepare(mActivity);
|
||||
|
||||
if (intent != null) {
|
||||
// Start the confirmation dialog and click OK.
|
||||
mActivity.startActivityForResult(intent, REQUEST_ID);
|
||||
mDevice.waitForIdle();
|
||||
|
||||
String packageName = intent.getComponent().getPackageName();
|
||||
String resourceIdRegex = "android:id/button1$|button_start_vpn";
|
||||
final UiObject okButton = new UiObject(new UiSelector()
|
||||
.className("android.widget.Button")
|
||||
.packageName(packageName)
|
||||
.resourceIdMatches(resourceIdRegex));
|
||||
if (okButton.waitForExists(TIMEOUT_MS) == false) {
|
||||
mActivity.finishActivity(REQUEST_ID);
|
||||
fail("VpnService.prepare returned an Intent for '" + intent.getComponent() + "' " +
|
||||
"to display the VPN confirmation dialog, but this test could not find the " +
|
||||
"button to allow the VPN application to connect. Please ensure that the " +
|
||||
"component displays a button with a resource ID matching the regexp: '" +
|
||||
resourceIdRegex + "'.");
|
||||
}
|
||||
|
||||
// Click the button and wait for RESULT_OK.
|
||||
okButton.click();
|
||||
try {
|
||||
int result = mActivity.getResult(TIMEOUT_MS);
|
||||
if (result != MyActivity.RESULT_OK) {
|
||||
fail("The VPN confirmation dialog did not return RESULT_OK when clicking on " +
|
||||
"the button matching the regular expression '" + resourceIdRegex +
|
||||
"' of " + intent.getComponent() + "'. Please ensure that clicking on " +
|
||||
"that button allows the VPN application to connect. " +
|
||||
"Return value: " + result);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
fail("VPN confirmation dialog did not return after " + TIMEOUT_MS + "ms");
|
||||
}
|
||||
|
||||
// Now we should be prepared.
|
||||
intent = VpnService.prepare(mActivity);
|
||||
if (intent != null) {
|
||||
fail("VpnService.prepare returned non-null even after the VPN dialog " +
|
||||
intent.getComponent() + "returned RESULT_OK.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startVpn(
|
||||
String[] addresses, String[] routes,
|
||||
String allowedApplications, String disallowedApplications) throws Exception {
|
||||
|
||||
prepareVpn();
|
||||
|
||||
// Register a callback so we will be notified when our VPN comes up.
|
||||
final NetworkRequest request = new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
|
||||
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.build();
|
||||
mCallback = new NetworkCallback() {
|
||||
public void onAvailable(Network network) {
|
||||
synchronized (mLock) {
|
||||
Log.i(TAG, "Got available callback for network=" + network);
|
||||
mNetwork = network;
|
||||
mLock.notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
mCM.registerNetworkCallback(request, mCallback); // Unregistered in tearDown.
|
||||
|
||||
// Start the service and wait up for TIMEOUT_MS ms for the VPN to come up.
|
||||
Intent intent = new Intent(mActivity, MyVpnService.class)
|
||||
.putExtra(mPackageName + ".cmd", "connect")
|
||||
.putExtra(mPackageName + ".addresses", TextUtils.join(",", addresses))
|
||||
.putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
|
||||
.putExtra(mPackageName + ".allowedapplications", allowedApplications)
|
||||
.putExtra(mPackageName + ".disallowedapplications", disallowedApplications);
|
||||
mActivity.startService(intent);
|
||||
synchronized (mLock) {
|
||||
if (mNetwork == null) {
|
||||
mLock.wait(TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
if (mNetwork == null) {
|
||||
fail("VPN did not become available after " + TIMEOUT_MS + "ms");
|
||||
}
|
||||
|
||||
// Unfortunately, when the available callback fires, the VPN UID ranges are not yet
|
||||
// configured. Give the system some time to do so. http://b/18436087 .
|
||||
try { Thread.sleep(300); } catch(InterruptedException e) {}
|
||||
}
|
||||
|
||||
private void stopVpn() {
|
||||
// Simply calling mActivity.stopService() won't stop the service, because the system binds
|
||||
// to the service for the purpose of sending it a revoke command if another VPN comes up,
|
||||
// and stopping a bound service has no effect. Instead, "start" the service again with an
|
||||
// Intent that tells it to disconnect.
|
||||
Intent intent = new Intent(mActivity, MyVpnService.class)
|
||||
.putExtra(mPackageName + ".cmd", "disconnect");
|
||||
mActivity.startService(intent);
|
||||
}
|
||||
|
||||
private static void checkUdpEcho(
|
||||
String to, String expectedFrom, boolean expectReply) throws IOException {
|
||||
DatagramSocket s;
|
||||
InetAddress address = InetAddress.getByName(to);
|
||||
if (address instanceof Inet6Address) { // http://b/18094870
|
||||
s = new DatagramSocket(0, InetAddress.getByName("::"));
|
||||
} else {
|
||||
s = new DatagramSocket();
|
||||
}
|
||||
s.setSoTimeout(100); // ms.
|
||||
|
||||
String msg = "Hello, world!";
|
||||
DatagramPacket p = new DatagramPacket(msg.getBytes(), msg.length());
|
||||
s.connect(address, 7);
|
||||
|
||||
if (expectedFrom != null) {
|
||||
assertEquals("Unexpected source address: ",
|
||||
expectedFrom, s.getLocalAddress().getHostAddress());
|
||||
}
|
||||
|
||||
try {
|
||||
if (expectReply) {
|
||||
s.send(p);
|
||||
s.receive(p);
|
||||
MoreAsserts.assertEquals(msg.getBytes(), p.getData());
|
||||
} else {
|
||||
try {
|
||||
s.send(p);
|
||||
s.receive(p);
|
||||
fail("Received unexpected reply");
|
||||
} catch(IOException expected) {}
|
||||
}
|
||||
} finally {
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static void expectUdpEcho(String to, String expectedFrom) throws IOException {
|
||||
checkUdpEcho(to, expectedFrom, true);
|
||||
}
|
||||
|
||||
private static void expectNoUdpEcho(String to) throws IOException {
|
||||
checkUdpEcho(to, null, false);
|
||||
}
|
||||
|
||||
public void testDefault() throws Exception {
|
||||
if (!supportedHardware()) return;
|
||||
|
||||
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
|
||||
new String[] {"192.0.2.0/24", "2001:db8::/32"},
|
||||
"", "");
|
||||
|
||||
expectUdpEcho("192.0.2.251", "192.0.2.2");
|
||||
expectUdpEcho("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
|
||||
}
|
||||
|
||||
public void testAppAllowed() throws Exception {
|
||||
if (!supportedHardware()) return;
|
||||
|
||||
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
|
||||
new String[] {"0.0.0.0/0", "::/0"},
|
||||
mPackageName, "");
|
||||
|
||||
expectUdpEcho("192.0.2.251", "192.0.2.2");
|
||||
expectUdpEcho("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
|
||||
}
|
||||
|
||||
public void testAppDisallowed() throws Exception {
|
||||
if (!supportedHardware()) return;
|
||||
|
||||
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
|
||||
new String[] {"192.0.2.0/24", "2001:db8::/32"},
|
||||
"", mPackageName);
|
||||
|
||||
expectNoUdpEcho("192.0.2.251");
|
||||
expectNoUdpEcho("2001:db8:dead:beef::f00");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.cts.net;
|
||||
|
||||
import com.android.cts.tradefed.build.CtsBuildHelper;
|
||||
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
|
||||
import com.android.ddmlib.testrunner.TestIdentifier;
|
||||
import com.android.ddmlib.testrunner.TestResult;
|
||||
import com.android.ddmlib.testrunner.TestResult.TestStatus;
|
||||
import com.android.ddmlib.testrunner.TestRunResult;
|
||||
import com.android.tradefed.build.IBuildInfo;
|
||||
import com.android.tradefed.device.DeviceNotAvailableException;
|
||||
import com.android.tradefed.testtype.DeviceTestCase;
|
||||
import com.android.tradefed.testtype.IAbi;
|
||||
import com.android.tradefed.testtype.IAbiReceiver;
|
||||
import com.android.tradefed.testtype.IBuildReceiver;
|
||||
import com.android.tradefed.device.DeviceNotAvailableException;
|
||||
import com.android.tradefed.device.ITestDevice;
|
||||
import com.android.tradefed.result.CollectingTestListener;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class HostsideNetworkTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
|
||||
private static final String TEST_PKG = "com.android.cts.net.hostside";
|
||||
private static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
|
||||
|
||||
private IAbi mAbi;
|
||||
private CtsBuildHelper mCtsBuild;
|
||||
|
||||
@Override
|
||||
public void setAbi(IAbi abi) {
|
||||
mAbi = abi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBuild(IBuildInfo buildInfo) {
|
||||
mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
assertNotNull(mAbi);
|
||||
assertNotNull(mCtsBuild);
|
||||
|
||||
getDevice().uninstallPackage(TEST_PKG);
|
||||
|
||||
assertNull(getDevice().installPackage(mCtsBuild.getTestApp(TEST_APK), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
getDevice().uninstallPackage(TEST_PKG);
|
||||
}
|
||||
|
||||
public void testVpn() throws Exception {
|
||||
runDeviceTests(TEST_PKG, ".VpnTest");
|
||||
}
|
||||
|
||||
public void runDeviceTests(String packageName, String testClassName)
|
||||
throws DeviceNotAvailableException {
|
||||
RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
|
||||
"android.support.test.runner.AndroidJUnitRunner", getDevice().getIDevice());
|
||||
|
||||
final CollectingTestListener listener = new CollectingTestListener();
|
||||
getDevice().runInstrumentationTests(testRunner, listener);
|
||||
|
||||
final TestRunResult result = listener.getCurrentRunResults();
|
||||
if (result.isRunFailure()) {
|
||||
throw new AssertionError("Failed to successfully run device tests for "
|
||||
+ result.getName() + ": " + result.getRunFailureMessage());
|
||||
}
|
||||
|
||||
if (result.hasFailedTests()) {
|
||||
// build a meaningful error message
|
||||
StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
|
||||
for (Map.Entry<TestIdentifier, TestResult> resultEntry :
|
||||
result.getTestResults().entrySet()) {
|
||||
if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
|
||||
errorBuilder.append(resultEntry.getKey().toString());
|
||||
errorBuilder.append(":\n");
|
||||
errorBuilder.append(resultEntry.getValue().getStackTrace());
|
||||
}
|
||||
}
|
||||
throw new AssertionError(errorBuilder.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user