Merge history of CTS

BUG: 167962976
Test: TH
Merged-In: Ic89c084604788b4d41cf854e5015c8ce7791c64d
Change-Id: I475d5fc915b1525f02fa70cb0694f21c3eb7d15f
This commit is contained in:
Baligh Uddin
2020-11-04 04:08:37 +00:00
139 changed files with 24458 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
// 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.
java_test_host {
name: "CtsHostsideNetworkTests",
defaults: ["cts_defaults"],
// Only compile source java files in this apk.
srcs: ["src/**/*.java"],
libs: [
"cts-tradefed",
"tradefed",
],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
}

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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.
-->
<configuration description="Config for CTS net host test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.cts.net.NetworkPolicyTestsPreparer" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="teardown-command" value="cmd power set-mode 0" />
<option name="teardown-command" value="cmd battery reset" />
<option name="teardown-command" value="cmd netpolicy stop-watching" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsHostsideNetworkTests.jar" />
<option name="runtime-hint" value="3m56s" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/sdcard/CtsHostsideNetworkTests" />
<option name="collect-on-run-ended-only" value="true" />
</metrics_collector>
</configuration>

View File

@@ -0,0 +1,4 @@
# Bug component: 61373
sudheersai@google.com
lorenzo@google.com
jchalard@google.com

View File

@@ -0,0 +1,24 @@
// Copyright (C) 2016 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.
java_test_helper_library {
name: "CtsHostsideNetworkTestsAidl",
sdk_version: "current",
srcs: [
"com/android/cts/net/hostside/IMyService.aidl",
"com/android/cts/net/hostside/INetworkCallback.aidl",
"com/android/cts/net/hostside/INetworkStateObserver.aidl",
"com/android/cts/net/hostside/IRemoteSocketFactory.aidl",
],
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2016 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 com.android.cts.net.hostside.INetworkCallback;
interface IMyService {
void registerBroadcastReceiver();
int getCounters(String receiverName, String action);
String checkNetworkStatus();
String getRestrictBackgroundStatus();
void sendNotification(int notificationId, String notificationType);
void registerNetworkCallback(in INetworkCallback cb);
void unregisterNetworkCallback();
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2019 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.net.Network;
import android.net.NetworkCapabilities;
interface INetworkCallback {
void onBlockedStatusChanged(in Network network, boolean blocked);
void onAvailable(in Network network);
void onLost(in Network network);
void onCapabilitiesChanged(in Network network, in NetworkCapabilities cap);
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016 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;
interface INetworkStateObserver {
boolean isForeground();
void onNetworkStateChecked(String resultData);
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2016 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.os.ParcelFileDescriptor;
interface IRemoteSocketFactory {
ParcelFileDescriptor openSocketFd(String host, int port, int timeoutMs);
String getPackageName();
int getUid();
}

View File

@@ -0,0 +1,40 @@
//
// 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.
//
android_test_helper_app {
name: "CtsHostsideNetworkTestsApp",
defaults: ["cts_support_defaults"],
//sdk_version: "current",
platform_apis: true,
static_libs: [
"androidx.test.rules",
"androidx.test.ext.junit",
"compatibility-device-util-axt",
"ctstestrunner-axt",
"ub-uiautomator",
"CtsHostsideNetworkTestsAidl",
],
libs: [
"android.test.runner",
"android.test.base",
],
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
}

View File

@@ -0,0 +1,56 @@
<?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.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application android:requestLegacyExternalStorage="true" >
<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>
<service
android:name=".MyNotificationListenerService"
android:label="MyNotificationListenerService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.net.hostside" />
</manifest>

View File

@@ -0,0 +1,201 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
import static org.junit.Assert.assertEquals;
import android.os.SystemClock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Base class for metered and non-metered tests on idle apps.
*/
@RequiredProperties({APP_STANDBY_MODE})
abstract class AbstractAppIdleTestCase extends AbstractRestrictBackgroundNetworkTestCase {
@Before
public final void setUp() throws Exception {
super.setUp();
// Set initial state.
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
setAppIdle(false);
turnBatteryOn();
registerBroadcastReceiver();
}
@After
public final void tearDown() throws Exception {
super.tearDown();
executeSilentShellCommand("cmd battery reset");
setAppIdle(false);
}
@Test
public void testBackgroundNetworkAccess_enabled() throws Exception {
setAppIdle(true);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
setAppIdle(true);
assertBackgroundNetworkAccess(false);
// Make sure foreground app doesn't lose access upon enabling it.
setAppIdle(true);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
finishActivity();
assertAppIdle(false); // verify - not idle anymore, since activity was launched...
assertBackgroundNetworkAccess(true);
setAppIdle(true);
assertBackgroundNetworkAccess(false);
// Same for foreground service.
setAppIdle(true);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
stopForegroundService();
assertAppIdle(true);
assertBackgroundNetworkAccess(false);
// Set Idle after foreground service start.
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
setAppIdle(true);
addPowerSaveModeWhitelist(TEST_PKG);
removePowerSaveModeWhitelist(TEST_PKG);
assertForegroundServiceNetworkAccess();
stopForegroundService();
assertAppIdle(true);
assertBackgroundNetworkAccess(false);
}
@Test
public void testBackgroundNetworkAccess_whitelisted() throws Exception {
setAppIdle(true);
assertBackgroundNetworkAccess(false);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
assertAppIdle(false); // verify - not idle anymore, since whitelisted
assertBackgroundNetworkAccess(true);
setAppIdleNoAssert(true);
assertAppIdle(false); // app is still whitelisted
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertAppIdle(true); // verify - idle again, once whitelisted was removed
assertBackgroundNetworkAccess(false);
setAppIdle(true);
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertAppIdle(false); // verify - not idle anymore, since whitelisted
assertBackgroundNetworkAccess(true);
setAppIdleNoAssert(true);
assertAppIdle(false); // app is still whitelisted
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertAppIdle(true); // verify - idle again, once whitelisted was removed
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
// verify - no whitelist, no access!
setAppIdle(true);
assertBackgroundNetworkAccess(false);
}
@Test
public void testBackgroundNetworkAccess_tempWhitelisted() throws Exception {
setAppIdle(true);
assertBackgroundNetworkAccess(false);
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(true);
// Wait until the whitelist duration is expired.
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(false);
}
@Test
public void testBackgroundNetworkAccess_disabled() throws Exception {
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(true);
}
@RequiredProperties({BATTERY_SAVER_MODE})
@Test
public void testAppIdleNetworkAccess_whenCharging() throws Exception {
// Check that app is paroled when charging
setAppIdle(true);
assertBackgroundNetworkAccess(false);
turnBatteryOff();
assertBackgroundNetworkAccess(true);
turnBatteryOn();
assertBackgroundNetworkAccess(false);
// Check that app is restricted when not idle but power-save is on
setAppIdle(false);
assertBackgroundNetworkAccess(true);
setBatterySaverMode(true);
assertBackgroundNetworkAccess(false);
// Use setBatterySaverMode API to leave power-save mode instead of plugging in charger
setBatterySaverMode(false);
turnBatteryOff();
assertBackgroundNetworkAccess(true);
// And when no longer charging, it still has network access, since it's not idle
turnBatteryOn();
assertBackgroundNetworkAccess(true);
}
@Test
public void testAppIdleNetworkAccess_idleWhitelisted() throws Exception {
setAppIdle(true);
assertAppIdle(true);
assertBackgroundNetworkAccess(false);
addAppIdleWhitelist(mUid);
assertBackgroundNetworkAccess(true);
removeAppIdleWhitelist(mUid);
assertBackgroundNetworkAccess(false);
// Make sure whitelisting a random app doesn't affect the tested app.
addAppIdleWhitelist(mUid + 1);
assertBackgroundNetworkAccess(false);
removeAppIdleWhitelist(mUid + 1);
}
@Test
public void testAppIdle_toast() throws Exception {
setAppIdle(true);
assertAppIdle(true);
assertEquals("Shown", showToast());
assertAppIdle(true);
// Wait for a couple of seconds for the toast to actually be shown
SystemClock.sleep(2000);
assertAppIdle(true);
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Base class for metered and non-metered Battery Saver Mode tests.
*/
@RequiredProperties({BATTERY_SAVER_MODE})
abstract class AbstractBatterySaverModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
@Before
public final void setUp() throws Exception {
super.setUp();
// Set initial state.
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
setBatterySaverMode(false);
registerBroadcastReceiver();
}
@After
public final void tearDown() throws Exception {
super.tearDown();
setBatterySaverMode(false);
}
@Test
public void testBackgroundNetworkAccess_enabled() throws Exception {
setBatterySaverMode(true);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
// Make sure foreground app doesn't lose access upon Battery Saver.
setBatterySaverMode(false);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
setBatterySaverMode(true);
assertForegroundNetworkAccess();
// Although it should not have access while the screen is off.
turnScreenOff();
assertBackgroundNetworkAccess(false);
turnScreenOn();
assertForegroundNetworkAccess();
// Goes back to background state.
finishActivity();
assertBackgroundNetworkAccess(false);
// Make sure foreground service doesn't lose access upon enabling Battery Saver.
setBatterySaverMode(false);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
setBatterySaverMode(true);
assertForegroundNetworkAccess();
stopForegroundService();
assertBackgroundNetworkAccess(false);
}
@Test
public void testBackgroundNetworkAccess_whitelisted() throws Exception {
setBatterySaverMode(true);
assertBackgroundNetworkAccess(false);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(true);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(true);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
}
@Test
public void testBackgroundNetworkAccess_disabled() throws Exception {
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(true);
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.Property.DOZE_MODE;
import static com.android.cts.net.hostside.Property.NOT_LOW_RAM_DEVICE;
import android.os.SystemClock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Base class for metered and non-metered Doze Mode tests.
*/
@RequiredProperties({DOZE_MODE})
abstract class AbstractDozeModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
@Before
public final void setUp() throws Exception {
super.setUp();
// Set initial state.
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
setDozeMode(false);
registerBroadcastReceiver();
}
@After
public final void tearDown() throws Exception {
super.tearDown();
setDozeMode(false);
}
@Test
public void testBackgroundNetworkAccess_enabled() throws Exception {
setDozeMode(true);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
// Make sure foreground service doesn't lose network access upon enabling doze.
setDozeMode(false);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
setDozeMode(true);
assertForegroundNetworkAccess();
stopForegroundService();
assertBackgroundState();
assertBackgroundNetworkAccess(false);
}
@Test
public void testBackgroundNetworkAccess_whitelisted() throws Exception {
setDozeMode(true);
assertBackgroundNetworkAccess(false);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(true);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
}
@Test
public void testBackgroundNetworkAccess_disabled() throws Exception {
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(true);
}
@RequiredProperties({NOT_LOW_RAM_DEVICE})
@Test
public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
throws Exception {
setPendingIntentWhitelistDuration(NETWORK_TIMEOUT_MS);
try {
registerNotificationListenerService();
setDozeMode(true);
assertBackgroundNetworkAccess(false);
testNotification(4, NOTIFICATION_TYPE_CONTENT);
testNotification(8, NOTIFICATION_TYPE_DELETE);
testNotification(15, NOTIFICATION_TYPE_FULL_SCREEN);
testNotification(16, NOTIFICATION_TYPE_BUNDLE);
testNotification(23, NOTIFICATION_TYPE_ACTION);
testNotification(42, NOTIFICATION_TYPE_ACTION_BUNDLE);
testNotification(108, NOTIFICATION_TYPE_ACTION_REMOTE_INPUT);
} finally {
resetDeviceIdleSettings();
}
}
private void testNotification(int id, String type) throws Exception {
sendNotification(id, type);
assertBackgroundNetworkAccess(true);
if (type.equals(NOTIFICATION_TYPE_ACTION)) {
// Make sure access is disabled after it expires. Since this check considerably slows
// downs the CTS tests, do it just once.
SystemClock.sleep(NETWORK_TIMEOUT_MS);
assertBackgroundNetworkAccess(false);
}
}
// Must override so it only tests foreground service - once an app goes to foreground, device
// leaves Doze Mode.
@Override
protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception {
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
stopForegroundService();
assertBackgroundState();
}
}

View File

@@ -0,0 +1,880 @@
/*
* Copyright (C) 2016 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 static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getWifiManager;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.util.Log;
import org.junit.Rule;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Superclass for tests related to background network restrictions.
*/
@RunWith(NetworkPolicyTestRunner.class)
public abstract class AbstractRestrictBackgroundNetworkTestCase {
public static final String TAG = "RestrictBackgroundNetworkTests";
protected static final String TEST_PKG = "com.android.cts.net.hostside";
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
private static final int SLEEP_TIME_SEC = 1;
// Constants below must match values defined on app2's Common.java
private static final String MANIFEST_RECEIVER = "ManifestReceiver";
private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
private static final String ACTION_RECEIVER_READY =
"com.android.cts.net.hostside.app2.action.RECEIVER_READY";
static final String ACTION_SHOW_TOAST =
"com.android.cts.net.hostside.app2.action.SHOW_TOAST";
protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
protected static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
protected static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
protected static final String NOTIFICATION_TYPE_ACTION = "ACTION";
protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
// TODO: Update BatteryManager.BATTERY_PLUGGED_ANY as @TestApi
public static final int BATTERY_PLUGGED_ANY =
BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
private static final String NETWORK_STATUS_SEPARATOR = "\\|";
private static final int SECOND_IN_MS = 1000;
static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
private static int PROCESS_STATE_FOREGROUND_SERVICE;
private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
protected static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
private static final int BATTERY_STATE_TIMEOUT_MS = 5000;
private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 500;
private static final int FOREGROUND_PROC_NETWORK_TIMEOUT_MS = 6000;
// Must be higher than NETWORK_TIMEOUT_MS
private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
private static final IntentFilter BATTERY_CHANGED_FILTER =
new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 5_000; // 5 sec
protected Context mContext;
protected Instrumentation mInstrumentation;
protected ConnectivityManager mCm;
protected int mUid;
private int mMyUid;
private MyServiceClient mServiceClient;
private String mDeviceIdleConstantsSetting;
@Rule
public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
.around(new MeterednessConfigurationRule());
protected void setUp() throws Exception {
PROCESS_STATE_FOREGROUND_SERVICE = (Integer) ActivityManager.class
.getDeclaredField("PROCESS_STATE_FOREGROUND_SERVICE").get(null);
mInstrumentation = getInstrumentation();
mContext = getContext();
mCm = getConnectivityManager();
mUid = getUid(TEST_APP2_PKG);
mMyUid = getUid(mContext.getPackageName());
mServiceClient = new MyServiceClient(mContext);
mServiceClient.bind();
mDeviceIdleConstantsSetting = "device_idle_constants";
executeShellCommand("cmd netpolicy start-watching " + mUid);
setAppIdle(false);
Log.i(TAG, "Apps status:\n"
+ "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
+ "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
}
protected void tearDown() throws Exception {
executeShellCommand("cmd netpolicy stop-watching");
mServiceClient.unbind();
}
protected int getUid(String packageName) throws Exception {
return mContext.getPackageManager().getPackageUid(packageName, 0);
}
protected void assertRestrictBackgroundChangedReceived(int expectedCount) throws Exception {
assertRestrictBackgroundChangedReceived(DYNAMIC_RECEIVER, expectedCount);
assertRestrictBackgroundChangedReceived(MANIFEST_RECEIVER, 0);
}
protected void assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)
throws Exception {
int attempts = 0;
int count = 0;
final int maxAttempts = 5;
do {
attempts++;
count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
assertFalse("Expected count " + expectedCount + " but actual is " + count,
count > expectedCount);
if (count == expectedCount) {
break;
}
Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
+ attempts + " attempts; sleeping "
+ SLEEP_TIME_SEC + " seconds before trying again");
SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
} while (attempts <= maxAttempts);
assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
+ maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
}
protected String sendOrderedBroadcast(Intent intent) throws Exception {
return sendOrderedBroadcast(intent, ORDERED_BROADCAST_TIMEOUT_MS);
}
protected String sendOrderedBroadcast(Intent intent, int timeoutMs) throws Exception {
final LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1);
Log.d(TAG, "Sending ordered broadcast: " + intent);
mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String resultData = getResultData();
if (resultData == null) {
Log.e(TAG, "Received null data from ordered intent");
return;
}
result.offer(resultData);
}
}, null, 0, null, null);
final String resultData = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
Log.d(TAG, "Ordered broadcast response after " + timeoutMs + "ms: " + resultData );
return resultData;
}
protected int getNumberBroadcastsReceived(String receiverName, String action) throws Exception {
return mServiceClient.getCounters(receiverName, action);
}
protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
final String status = mServiceClient.getRestrictBackgroundStatus();
assertNotNull("didn't get API status from app2", status);
assertEquals(restrictBackgroundValueToString(expectedStatus),
restrictBackgroundValueToString(Integer.parseInt(status)));
}
protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
assertBackgroundState();
assertNetworkAccess(expectAllowed /* expectAvailable */, false /* needScreenOn */);
}
protected void assertForegroundNetworkAccess() throws Exception {
assertForegroundState();
// We verified that app is in foreground state but if the screen turns-off while
// verifying for network access, the app will go into background state (in case app's
// foreground status was due to top activity). So, turn the screen on when verifying
// network connectivity.
assertNetworkAccess(true /* expectAvailable */, true /* needScreenOn */);
}
protected void assertForegroundServiceNetworkAccess() throws Exception {
assertForegroundServiceState();
assertNetworkAccess(true /* expectAvailable */, false /* needScreenOn */);
}
/**
* Asserts that an app always have access while on foreground or running a foreground service.
*
* <p>This method will launch an activity and a foreground service to make the assertion, but
* will finish the activity / stop the service afterwards.
*/
protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{
// Checks foreground first.
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
finishActivity();
// Then foreground service
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
stopForegroundService();
}
protected final void assertBackgroundState() throws Exception {
final int maxTries = 30;
ProcessState state = null;
for (int i = 1; i <= maxTries; i++) {
state = getProcessStateByUid(mUid);
Log.v(TAG, "assertBackgroundState(): status for app2 (" + mUid + ") on attempt #" + i
+ ": " + state);
if (isBackground(state.state)) {
return;
}
Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
+ "; sleeping 1s before trying again");
SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on background state after " + maxTries + " attempts: " + state );
}
protected final void assertForegroundState() throws Exception {
final int maxTries = 30;
ProcessState state = null;
for (int i = 1; i <= maxTries; i++) {
state = getProcessStateByUid(mUid);
Log.v(TAG, "assertForegroundState(): status for app2 (" + mUid + ") on attempt #" + i
+ ": " + state);
if (!isBackground(state.state)) {
return;
}
Log.d(TAG, "App not on foreground state on attempt #" + i
+ "; sleeping 1s before trying again");
turnScreenOn();
SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on foreground state after " + maxTries + " attempts: " + state );
}
protected final void assertForegroundServiceState() throws Exception {
final int maxTries = 30;
ProcessState state = null;
for (int i = 1; i <= maxTries; i++) {
state = getProcessStateByUid(mUid);
Log.v(TAG, "assertForegroundServiceState(): status for app2 (" + mUid + ") on attempt #"
+ i + ": " + state);
if (state.state == PROCESS_STATE_FOREGROUND_SERVICE) {
return;
}
Log.d(TAG, "App not on foreground service state on attempt #" + i
+ "; sleeping 1s before trying again");
SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on foreground service state after " + maxTries + " attempts: " + state );
}
/**
* Returns whether an app state should be considered "background" for restriction purposes.
*/
protected boolean isBackground(int state) {
return state > PROCESS_STATE_FOREGROUND_SERVICE;
}
/**
* Asserts whether the active network is available or not.
*/
private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn)
throws Exception {
final int maxTries = 5;
String error = null;
int timeoutMs = 500;
for (int i = 1; i <= maxTries; i++) {
error = checkNetworkAccess(expectAvailable);
if (error.isEmpty()) return;
// TODO: ideally, it should retry only when it cannot connect to an external site,
// or no retry at all! But, currently, the initial change fails almost always on
// battery saver tests because the netd changes are made asynchronously.
// Once b/27803922 is fixed, this retry mechanism should be revisited.
Log.w(TAG, "Network status didn't match for expectAvailable=" + expectAvailable
+ " on attempt #" + i + ": " + error + "\n"
+ "Sleeping " + timeoutMs + "ms before trying again");
if (needScreenOn) {
turnScreenOn();
}
// No sleep after the last turn
if (i < maxTries) {
SystemClock.sleep(timeoutMs);
}
// Exponential back-off.
timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
}
fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
+ " attempts.\nLast error: " + error);
}
/**
* Checks whether the network is available as expected.
*
* @return error message with the mismatch (or empty if assertion passed).
*/
private String checkNetworkAccess(boolean expectAvailable) throws Exception {
final String resultData = mServiceClient.checkNetworkStatus();
return checkForAvailabilityInResultData(resultData, expectAvailable);
}
private String checkForAvailabilityInResultData(String resultData, boolean expectAvailable) {
if (resultData == null) {
assertNotNull("Network status from app2 is null", resultData);
}
// Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
assertEquals("Wrong network status: " + resultData, 5, parts.length);
final State state = parts[0].equals("null") ? null : State.valueOf(parts[0]);
final DetailedState detailedState = parts[1].equals("null")
? null : DetailedState.valueOf(parts[1]);
final boolean connected = Boolean.valueOf(parts[2]);
final String connectionCheckDetails = parts[3];
final String networkInfo = parts[4];
final StringBuilder errors = new StringBuilder();
final State expectedState;
final DetailedState expectedDetailedState;
if (expectAvailable) {
expectedState = State.CONNECTED;
expectedDetailedState = DetailedState.CONNECTED;
} else {
expectedState = State.DISCONNECTED;
expectedDetailedState = DetailedState.BLOCKED;
}
if (expectAvailable != connected) {
errors.append(String.format("External site connection failed: expected %s, got %s\n",
expectAvailable, connected));
}
if (expectedState != state || expectedDetailedState != detailedState) {
errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
expectedState, expectedDetailedState, state, detailedState));
}
if (errors.length() > 0) {
errors.append("\tnetworkInfo: " + networkInfo + "\n");
errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
}
return errors.toString();
}
/**
* Runs a Shell command which is not expected to generate output.
*/
protected void executeSilentShellCommand(String command) {
final String result = executeShellCommand(command);
assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
}
/**
* Asserts the result of a command, wait and re-running it a couple times if necessary.
*/
protected void assertDelayedShellCommand(String command, final String expectedResult)
throws Exception {
assertDelayedShellCommand(command, 5, 1, expectedResult);
}
protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
final String expectedResult) throws Exception {
assertDelayedShellCommand(command, maxTries, napTimeSeconds, new ExpectResultChecker() {
@Override
public boolean isExpected(String result) {
return expectedResult.equals(result);
}
@Override
public String getExpected() {
return expectedResult;
}
});
}
protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
ExpectResultChecker checker) throws Exception {
String result = "";
for (int i = 1; i <= maxTries; i++) {
result = executeShellCommand(command).trim();
if (checker.isExpected(result)) return;
Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
+ checker.getExpected() + "' on attempt #" + i
+ "; sleeping " + napTimeSeconds + "s before trying again");
SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
}
fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
+ maxTries
+ " attempts. Last result: '" + result + "'");
}
protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
assertRestrictBackgroundWhitelist(uid, true);
// UID policies live by the Highlander rule: "There can be only one".
// Hence, if app is whitelisted, it should not be blacklisted.
assertRestrictBackgroundBlacklist(uid, false);
}
protected void removeRestrictBackgroundWhitelist(int uid) throws Exception {
executeShellCommand("cmd netpolicy remove restrict-background-whitelist " + uid);
assertRestrictBackgroundWhitelist(uid, false);
}
protected void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
assertRestrictBackground("restrict-background-whitelist", uid, expected);
}
protected void addRestrictBackgroundBlacklist(int uid) throws Exception {
executeShellCommand("cmd netpolicy add restrict-background-blacklist " + uid);
assertRestrictBackgroundBlacklist(uid, true);
// UID policies live by the Highlander rule: "There can be only one".
// Hence, if app is blacklisted, it should not be whitelisted.
assertRestrictBackgroundWhitelist(uid, false);
}
protected void removeRestrictBackgroundBlacklist(int uid) throws Exception {
executeShellCommand("cmd netpolicy remove restrict-background-blacklist " + uid);
assertRestrictBackgroundBlacklist(uid, false);
}
protected void assertRestrictBackgroundBlacklist(int uid, boolean expected) throws Exception {
assertRestrictBackground("restrict-background-blacklist", uid, expected);
}
protected void addAppIdleWhitelist(int uid) throws Exception {
executeShellCommand("cmd netpolicy add app-idle-whitelist " + uid);
assertAppIdleWhitelist(uid, true);
}
protected void removeAppIdleWhitelist(int uid) throws Exception {
executeShellCommand("cmd netpolicy remove app-idle-whitelist " + uid);
assertAppIdleWhitelist(uid, false);
}
protected void assertAppIdleWhitelist(int uid, boolean expected) throws Exception {
assertRestrictBackground("app-idle-whitelist", uid, expected);
}
private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
final int maxTries = 5;
boolean actual = false;
final String expectedUid = Integer.toString(uid);
String uids = "";
for (int i = 1; i <= maxTries; i++) {
final String output =
executeShellCommand("cmd netpolicy list " + list);
uids = output.split(":")[1];
for (String candidate : uids.split(" ")) {
actual = candidate.trim().equals(expectedUid);
if (expected == actual) {
return;
}
}
Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
+ expected + ", got " + actual + "); sleeping 1s before polling again");
SystemClock.sleep(SECOND_IN_MS);
}
fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
+ ". Full list: " + uids);
}
protected void addTempPowerSaveModeWhitelist(String packageName, long duration)
throws Exception {
Log.i(TAG, "Adding pkg " + packageName + " to temp-power-save-mode whitelist");
executeShellCommand("dumpsys deviceidle tempwhitelist -d " + duration + " " + packageName);
}
protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
throws Exception {
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
assertDelayedShellCommand("dumpsys deviceidle whitelist =" + packageName,
Boolean.toString(expected));
}
protected void addPowerSaveModeWhitelist(String packageName) throws Exception {
Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
executeShellCommand("dumpsys deviceidle whitelist +" + packageName);
assertPowerSaveModeWhitelist(packageName, true);
}
protected void removePowerSaveModeWhitelist(String packageName) throws Exception {
Log.i(TAG, "Removing package " + packageName + " from power-save-mode whitelist");
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
executeShellCommand("dumpsys deviceidle whitelist -" + packageName);
assertPowerSaveModeWhitelist(packageName, false);
}
protected void assertPowerSaveModeExceptIdleWhitelist(String packageName, boolean expected)
throws Exception {
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
assertDelayedShellCommand("dumpsys deviceidle except-idle-whitelist =" + packageName,
Boolean.toString(expected));
}
protected void addPowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
Log.i(TAG, "Adding package " + packageName + " to power-save-mode-except-idle whitelist");
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
executeShellCommand("dumpsys deviceidle except-idle-whitelist +" + packageName);
assertPowerSaveModeExceptIdleWhitelist(packageName, true);
}
protected void removePowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
Log.i(TAG, "Removing package " + packageName
+ " from power-save-mode-except-idle whitelist");
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
executeShellCommand("dumpsys deviceidle except-idle-whitelist reset");
assertPowerSaveModeExceptIdleWhitelist(packageName, false);
}
protected void turnBatteryOn() throws Exception {
executeSilentShellCommand("cmd battery unplug");
executeSilentShellCommand("cmd battery set status "
+ BatteryManager.BATTERY_STATUS_DISCHARGING);
assertBatteryState(false);
}
protected void turnBatteryOff() throws Exception {
executeSilentShellCommand("cmd battery set ac " + BATTERY_PLUGGED_ANY);
executeSilentShellCommand("cmd battery set level 100");
executeSilentShellCommand("cmd battery set status "
+ BatteryManager.BATTERY_STATUS_CHARGING);
assertBatteryState(true);
}
private void assertBatteryState(boolean pluggedIn) throws Exception {
final long endTime = SystemClock.elapsedRealtime() + BATTERY_STATE_TIMEOUT_MS;
while (isDevicePluggedIn() != pluggedIn && SystemClock.elapsedRealtime() <= endTime) {
Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
}
if (isDevicePluggedIn() != pluggedIn) {
fail("Timed out waiting for the plugged-in state to change,"
+ " expected pluggedIn: " + pluggedIn);
}
}
private boolean isDevicePluggedIn() {
final Intent batteryIntent = mContext.registerReceiver(null, BATTERY_CHANGED_FILTER);
return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
}
protected void turnScreenOff() throws Exception {
executeSilentShellCommand("input keyevent KEYCODE_SLEEP");
}
protected void turnScreenOn() throws Exception {
executeSilentShellCommand("input keyevent KEYCODE_WAKEUP");
executeSilentShellCommand("wm dismiss-keyguard");
}
protected void setBatterySaverMode(boolean enabled) throws Exception {
Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
if (enabled) {
turnBatteryOn();
executeSilentShellCommand("cmd power set-mode 1");
} else {
executeSilentShellCommand("cmd power set-mode 0");
turnBatteryOff();
}
}
protected void setDozeMode(boolean enabled) throws Exception {
// Check doze mode is supported.
assertTrue("Device does not support Doze Mode", isDozeModeSupported());
Log.i(TAG, "Setting Doze Mode to " + enabled);
if (enabled) {
turnBatteryOn();
turnScreenOff();
executeShellCommand("dumpsys deviceidle force-idle deep");
} else {
turnScreenOn();
turnBatteryOff();
executeShellCommand("dumpsys deviceidle unforce");
}
assertDozeMode(enabled);
}
protected void assertDozeMode(boolean enabled) throws Exception {
assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
}
protected void setAppIdle(boolean enabled) throws Exception {
Log.i(TAG, "Setting app idle to " + enabled);
executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
assertAppIdle(enabled);
}
protected void setAppIdleNoAssert(boolean enabled) throws Exception {
Log.i(TAG, "Setting app idle to " + enabled);
executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
}
protected void assertAppIdle(boolean enabled) throws Exception {
try {
assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
} catch (Throwable e) {
throw e;
}
}
/**
* Starts a service that will register a broadcast receiver to receive
* {@code RESTRICT_BACKGROUND_CHANGE} intents.
* <p>
* The service must run in a separate app because otherwise it would be killed every time
* {@link #runDeviceTests(String, String)} is executed.
*/
protected void registerBroadcastReceiver() throws Exception {
mServiceClient.registerBroadcastReceiver();
final Intent intent = new Intent(ACTION_RECEIVER_READY)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// Wait until receiver is ready.
final int maxTries = 10;
for (int i = 1; i <= maxTries; i++) {
final String message = sendOrderedBroadcast(intent, SECOND_IN_MS * 4);
Log.d(TAG, "app2 receiver acked: " + message);
if (message != null) {
return;
}
Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
SystemClock.sleep(SECOND_IN_MS);
}
fail("app2 receiver is not ready");
}
protected void registerNetworkCallback(INetworkCallback cb) throws Exception {
mServiceClient.registerNetworkCallback(cb);
}
protected void unregisterNetworkCallback() throws Exception {
mServiceClient.unregisterNetworkCallback();
}
/**
* Registers a {@link NotificationListenerService} implementation that will execute the
* notification actions right after the notification is sent.
*/
protected void registerNotificationListenerService() throws Exception {
executeShellCommand("cmd notification allow_listener "
+ MyNotificationListenerService.getId());
final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
final ComponentName listenerComponent = MyNotificationListenerService.getComponentName();
assertTrue(listenerComponent + " has not been granted access",
nm.isNotificationListenerAccessGranted(listenerComponent));
}
protected void setPendingIntentWhitelistDuration(int durationMs) throws Exception {
executeSilentShellCommand(String.format(
"settings put global %s %s=%d", mDeviceIdleConstantsSetting,
"notification_whitelist_duration", durationMs));
}
protected void resetDeviceIdleSettings() throws Exception {
executeShellCommand(String.format("settings delete global %s",
mDeviceIdleConstantsSetting));
}
protected void launchComponentAndAssertNetworkAccess(int type) throws Exception {
if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
startForegroundService();
assertForegroundServiceNetworkAccess();
return;
} else if (type == TYPE_COMPONENT_ACTIVTIY) {
turnScreenOn();
// Wait for screen-on state to propagate through the system.
SystemClock.sleep(2000);
final CountDownLatch latch = new CountDownLatch(1);
final Intent launchIntent = getIntentForComponent(type);
final Bundle extras = new Bundle();
final String[] errors = new String[]{null};
extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, errors));
launchIntent.putExtras(extras);
mContext.startActivity(launchIntent);
if (latch.await(FOREGROUND_PROC_NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
if (!errors[0].isEmpty()) {
if (errors[0] == APP_NOT_FOREGROUND_ERROR) {
// App didn't come to foreground when the activity is started, so try again.
assertForegroundNetworkAccess();
} else {
fail("Network is not available for app2 (" + mUid + "): " + errors[0]);
}
}
} else {
fail("Timed out waiting for network availability status from app2 (" + mUid + ")");
}
} else {
throw new IllegalArgumentException("Unknown type: " + type);
}
}
private void startForegroundService() throws Exception {
final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_FOREGROUND_SERVICE);
mContext.startForegroundService(launchIntent);
assertForegroundServiceState();
}
private Intent getIntentForComponent(int type) {
final Intent intent = new Intent();
if (type == TYPE_COMPONENT_ACTIVTIY) {
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
.setFlags(1);
} else {
fail("Unknown type: " + type);
}
return intent;
}
protected void stopForegroundService() throws Exception {
executeShellCommand(String.format("am startservice -f 2 %s/%s",
TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS));
// NOTE: cannot assert state because it depends on whether activity was on top before.
}
private Binder getNewNetworkStateObserver(final CountDownLatch latch,
final String[] errors) {
return new INetworkStateObserver.Stub() {
@Override
public boolean isForeground() {
try {
final ProcessState state = getProcessStateByUid(mUid);
return !isBackground(state.state);
} catch (Exception e) {
Log.d(TAG, "Error while reading the proc state for " + mUid + ": " + e);
return false;
}
}
@Override
public void onNetworkStateChecked(String resultData) {
errors[0] = resultData == null
? APP_NOT_FOREGROUND_ERROR
: checkForAvailabilityInResultData(resultData, true);
latch.countDown();
}
};
}
/**
* Finishes an activity on app2 so its process is demoted fromforeground status.
*/
protected void finishActivity() throws Exception {
executeShellCommand("am broadcast -a "
+ " com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY "
+ "--receiver-foreground --receiver-registered-only");
}
protected void sendNotification(int notificationId, String notificationType) throws Exception {
Log.d(TAG, "Sending notification broadcast (id=" + notificationId
+ ", type=" + notificationType);
mServiceClient.sendNotification(notificationId, notificationType);
}
protected String showToast() {
final Intent intent = new Intent(ACTION_SHOW_TOAST);
intent.setPackage(TEST_APP2_PKG);
Log.d(TAG, "Sending request to show toast");
try {
return sendOrderedBroadcast(intent, 3 * SECOND_IN_MS);
} catch (Exception e) {
return "";
}
}
private ProcessState getProcessStateByUid(int uid) throws Exception {
return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
}
private static class ProcessState {
private final String fullState;
final int state;
ProcessState(String fullState) {
this.fullState = fullState;
try {
this.state = Integer.parseInt(fullState.split(" ")[0]);
} catch (Exception e) {
throw new IllegalArgumentException("Could not parse " + fullState);
}
}
@Override
public String toString() {
return fullState;
}
}
/**
* Helper class used to assert the result of a Shell command.
*/
protected static interface ExpectResultChecker {
/**
* Checkes whether the result of the command matched the expectation.
*/
boolean isExpected(String result);
/**
* Gets the expected result so it's displayed on log and failure messages.
*/
String getExpected();
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.Property.METERED_NETWORK;
@RequiredProperties({METERED_NETWORK})
public class AppIdleMeteredTest extends AbstractAppIdleTestCase {
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
@RequiredProperties({NON_METERED_NETWORK})
public class AppIdleNonMeteredTest extends AbstractAppIdleTestCase {
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.Property.METERED_NETWORK;
@RequiredProperties({METERED_NETWORK})
public class BatterySaverModeMeteredTest extends AbstractBatterySaverModeTestCase {
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
@RequiredProperties({NON_METERED_NETWORK})
public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright (C) 2016 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 static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
import static com.android.cts.net.hostside.Property.NO_DATA_SAVER_MODE;
import static org.junit.Assert.fail;
import com.android.compatibility.common.util.CddTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import androidx.test.filters.LargeTest;
@RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
@LargeTest
public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
"com.android.providers.downloads"
};
@Before
public void setUp() throws Exception {
super.setUp();
// Set initial state.
setRestrictBackground(false);
removeRestrictBackgroundWhitelist(mUid);
removeRestrictBackgroundBlacklist(mUid);
registerBroadcastReceiver();
assertRestrictBackgroundChangedReceived(0);
}
@After
public void tearDown() throws Exception {
super.tearDown();
setRestrictBackground(false);
}
@Test
public void testGetRestrictBackgroundStatus_disabled() throws Exception {
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
// Verify status is always disabled, never whitelisted
addRestrictBackgroundWhitelist(mUid);
assertRestrictBackgroundChangedReceived(0);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
assertsForegroundAlwaysHasNetworkAccess();
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
}
@Test
public void testGetRestrictBackgroundStatus_whitelisted() throws Exception {
setRestrictBackground(true);
assertRestrictBackgroundChangedReceived(1);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
addRestrictBackgroundWhitelist(mUid);
assertRestrictBackgroundChangedReceived(2);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_WHITELISTED);
removeRestrictBackgroundWhitelist(mUid);
assertRestrictBackgroundChangedReceived(3);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
assertsForegroundAlwaysHasNetworkAccess();
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
}
@Test
public void testGetRestrictBackgroundStatus_enabled() throws Exception {
setRestrictBackground(true);
assertRestrictBackgroundChangedReceived(1);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
assertsForegroundAlwaysHasNetworkAccess();
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
// Make sure foreground app doesn't lose access upon enabling Data Saver.
setRestrictBackground(false);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
setRestrictBackground(true);
assertForegroundNetworkAccess();
// Although it should not have access while the screen is off.
turnScreenOff();
assertBackgroundNetworkAccess(false);
turnScreenOn();
assertForegroundNetworkAccess();
// Goes back to background state.
finishActivity();
assertBackgroundNetworkAccess(false);
// Make sure foreground service doesn't lose access upon enabling Data Saver.
setRestrictBackground(false);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
setRestrictBackground(true);
assertForegroundNetworkAccess();
stopForegroundService();
assertBackgroundNetworkAccess(false);
}
@Test
public void testGetRestrictBackgroundStatus_blacklisted() throws Exception {
addRestrictBackgroundBlacklist(mUid);
assertRestrictBackgroundChangedReceived(1);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
assertsForegroundAlwaysHasNetworkAccess();
assertRestrictBackgroundChangedReceived(1);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
// UID policies live by the Highlander rule: "There can be only one".
// Hence, if app is whitelisted, it should not be blacklisted anymore.
setRestrictBackground(true);
assertRestrictBackgroundChangedReceived(2);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
addRestrictBackgroundWhitelist(mUid);
assertRestrictBackgroundChangedReceived(3);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_WHITELISTED);
// Check status after removing blacklist.
// ...re-enables first
addRestrictBackgroundBlacklist(mUid);
assertRestrictBackgroundChangedReceived(4);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
assertsForegroundAlwaysHasNetworkAccess();
// ... remove blacklist - access's still rejected because Data Saver is on
removeRestrictBackgroundBlacklist(mUid);
assertRestrictBackgroundChangedReceived(4);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
assertsForegroundAlwaysHasNetworkAccess();
// ... finally, disable Data Saver
setRestrictBackground(false);
assertRestrictBackgroundChangedReceived(5);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
assertsForegroundAlwaysHasNetworkAccess();
}
@Test
public void testGetRestrictBackgroundStatus_requiredWhitelistedPackages() throws Exception {
final StringBuilder error = new StringBuilder();
for (String packageName : REQUIRED_WHITELISTED_PACKAGES) {
int uid = -1;
try {
uid = getUid(packageName);
assertRestrictBackgroundWhitelist(uid, true);
} catch (Throwable t) {
error.append("\nFailed for '").append(packageName).append("'");
if (uid > 0) {
error.append(" (uid ").append(uid).append(")");
}
error.append(": ").append(t).append("\n");
}
}
if (error.length() > 0) {
fail(error.toString());
}
}
@RequiredProperties({NO_DATA_SAVER_MODE})
@CddTest(requirement="7.4.7/C-2-2")
@Test
public void testBroadcastNotSentOnUnsupportedDevices() throws Exception {
setRestrictBackground(true);
assertRestrictBackgroundChangedReceived(0);
setRestrictBackground(false);
assertRestrictBackgroundChangedReceived(0);
setRestrictBackground(true);
assertRestrictBackgroundChangedReceived(0);
}
private void assertDataSaverStatusOnBackground(int expectedStatus) throws Exception {
assertRestrictBackgroundStatus(expectedStatus);
assertBackgroundNetworkAccess(expectedStatus != RESTRICT_BACKGROUND_STATUS_ENABLED);
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.Property.METERED_NETWORK;
@RequiredProperties({METERED_NETWORK})
public class DozeModeMeteredTest extends AbstractDozeModeTestCase {
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
@RequiredProperties({NON_METERED_NETWORK})
public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2019 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 static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_APP2_PKG;
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
import android.os.Environment;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.android.compatibility.common.util.OnFailureRule;
import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import androidx.test.platform.app.InstrumentationRegistry;
public class DumpOnFailureRule extends OnFailureRule {
private File mDumpDir = new File(Environment.getExternalStorageDirectory(),
"CtsHostsideNetworkTests");
@Override
public void onTestFailure(Statement base, Description description, Throwable throwable) {
final String testName = description.getClassName() + "_" + description.getMethodName();
if (throwable instanceof AssumptionViolatedException) {
Log.d(TAG, "Skipping test " + testName + ": " + throwable);
return;
}
prepareDumpRootDir();
final File dumpFile = new File(mDumpDir, "dump-" + testName);
Log.i(TAG, "Dumping debug info for " + description + ": " + dumpFile.getPath());
try (FileOutputStream out = new FileOutputStream(dumpFile)) {
for (String cmd : new String[] {
"dumpsys netpolicy",
"dumpsys network_management",
"dumpsys usagestats " + TEST_PKG + " " + TEST_APP2_PKG,
"dumpsys usagestats appstandby",
}) {
dumpCommandOutput(out, cmd);
}
} catch (FileNotFoundException e) {
Log.e(TAG, "Error opening file: " + dumpFile, e);
} catch (IOException e) {
Log.e(TAG, "Error closing file: " + dumpFile, e);
}
}
void dumpCommandOutput(FileOutputStream out, String cmd) {
final ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
.getUiAutomation().executeShellCommand(cmd);
try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
out.write(("Output of '" + cmd + "':\n").getBytes(StandardCharsets.UTF_8));
FileUtils.copy(in, out);
out.write("\n\n=================================================================\n\n"
.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
Log.e(TAG, "Error dumping '" + cmd + "'", e);
}
}
void prepareDumpRootDir() {
if (!mDumpDir.exists() && !mDumpDir.mkdir()) {
Log.e(TAG, "Error creating " + mDumpDir);
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2019 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 static com.android.cts.net.hostside.NetworkPolicyTestUtils.resetMeteredNetwork;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupMeteredNetwork;
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
import android.util.ArraySet;
import android.util.Pair;
import com.android.compatibility.common.util.BeforeAfterRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class MeterednessConfigurationRule extends BeforeAfterRule {
private Pair<String, Boolean> mSsidAndInitialMeteredness;
@Override
public void onBefore(Statement base, Description description) throws Throwable {
final ArraySet<Property> requiredProperties
= RequiredPropertiesRule.getRequiredProperties();
if (requiredProperties.contains(METERED_NETWORK)) {
configureNetworkMeteredness(true);
} else if (requiredProperties.contains(NON_METERED_NETWORK)) {
configureNetworkMeteredness(false);
}
}
@Override
public void onAfter(Statement base, Description description) throws Throwable {
resetNetworkMeteredness();
}
public void configureNetworkMeteredness(boolean metered) throws Exception {
mSsidAndInitialMeteredness = setupMeteredNetwork(metered);
}
public void resetNetworkMeteredness() throws Exception {
if (mSsidAndInitialMeteredness != null) {
resetMeteredNetwork(mSsidAndInitialMeteredness.first,
mSsidAndInitialMeteredness.second);
}
}
}

View File

@@ -0,0 +1,370 @@
/*
* Copyright (C) 2016 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 static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
import static com.android.cts.net.hostside.Property.DOZE_MODE;
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
import android.os.SystemClock;
import android.util.Log;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Test cases for the more complex scenarios where multiple restrictions (like Battery Saver Mode
* and Data Saver Mode) are applied simultaneously.
* <p>
* <strong>NOTE: </strong>it might sound like the test methods on this class are testing too much,
* which would make it harder to diagnose individual failures, but the assumption is that such
* failure most likely will happen when the restriction is tested individually as well.
*/
public class MixedModesTest extends AbstractRestrictBackgroundNetworkTestCase {
private static final String TAG = "MixedModesTest";
@Before
public void setUp() throws Exception {
super.setUp();
// Set initial state.
removeRestrictBackgroundWhitelist(mUid);
removeRestrictBackgroundBlacklist(mUid);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
registerBroadcastReceiver();
}
@After
public void tearDown() throws Exception {
super.tearDown();
try {
setRestrictBackground(false);
} finally {
setBatterySaverMode(false);
}
}
/**
* Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on metered networks.
*/
@RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK})
@Test
public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
final MeterednessConfigurationRule meterednessConfiguration
= new MeterednessConfigurationRule();
meterednessConfiguration.configureNetworkMeteredness(true);
try {
setRestrictBackground(true);
setBatterySaverMode(true);
Log.v(TAG, "Not whitelisted for any.");
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
addRestrictBackgroundWhitelist(mUid);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
removeRestrictBackgroundWhitelist(mUid);
Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
addPowerSaveModeWhitelist(TEST_APP2_PKG);
removeRestrictBackgroundWhitelist(mUid);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
Log.v(TAG, "Whitelisted for both.");
addRestrictBackgroundWhitelist(mUid);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(true);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
removeRestrictBackgroundWhitelist(mUid);
Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
addRestrictBackgroundBlacklist(mUid);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
removeRestrictBackgroundBlacklist(mUid);
Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
addRestrictBackgroundBlacklist(mUid);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
removeRestrictBackgroundBlacklist(mUid);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
} finally {
meterednessConfiguration.resetNetworkMeteredness();
}
}
/**
* Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on non-metered
* networks.
*/
@RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, NON_METERED_NETWORK})
@Test
public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
final MeterednessConfigurationRule meterednessConfiguration
= new MeterednessConfigurationRule();
meterednessConfiguration.configureNetworkMeteredness(false);
try {
setRestrictBackground(true);
setBatterySaverMode(true);
Log.v(TAG, "Not whitelisted for any.");
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
addRestrictBackgroundWhitelist(mUid);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
removeRestrictBackgroundWhitelist(mUid);
Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
addPowerSaveModeWhitelist(TEST_APP2_PKG);
removeRestrictBackgroundWhitelist(mUid);
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(true);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
Log.v(TAG, "Whitelisted for both.");
addRestrictBackgroundWhitelist(mUid);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(true);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
removeRestrictBackgroundWhitelist(mUid);
Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
addRestrictBackgroundBlacklist(mUid);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(false);
removeRestrictBackgroundBlacklist(mUid);
Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
addRestrictBackgroundBlacklist(mUid);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(true);
assertsForegroundAlwaysHasNetworkAccess();
assertBackgroundNetworkAccess(true);
removeRestrictBackgroundBlacklist(mUid);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
} finally {
meterednessConfiguration.resetNetworkMeteredness();
}
}
/**
* Tests that powersave whitelists works as expected when doze and battery saver modes
* are enabled.
*/
@RequiredProperties({DOZE_MODE, BATTERY_SAVER_MODE})
@Test
public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
setBatterySaverMode(true);
setDozeMode(true);
try {
addPowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(true);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
} finally {
setBatterySaverMode(false);
setDozeMode(false);
}
}
/**
* Tests that powersave whitelists works as expected when doze and appIdle modes
* are enabled.
*/
@RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
@Test
public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
setDozeMode(true);
setAppIdle(true);
try {
addPowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(true);
removePowerSaveModeWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
assertBackgroundNetworkAccess(false);
} finally {
setAppIdle(false);
setDozeMode(false);
}
}
@RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
@Test
public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
setDozeMode(true);
setAppIdle(true);
try {
assertBackgroundNetworkAccess(false);
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(true);
// Wait until the whitelist duration is expired.
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(false);
} finally {
setAppIdle(false);
setDozeMode(false);
}
}
@RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
@Test
public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
setBatterySaverMode(true);
setAppIdle(true);
try {
assertBackgroundNetworkAccess(false);
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(true);
// Wait until the whitelist duration is expired.
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(false);
} finally {
setAppIdle(false);
setBatterySaverMode(false);
}
}
/**
* Tests that the app idle whitelist works as expected when doze and appIdle mode are enabled.
*/
@RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
@Test
public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
setDozeMode(true);
setAppIdle(true);
try {
assertBackgroundNetworkAccess(false);
// UID still shouldn't have access because of Doze.
addAppIdleWhitelist(mUid);
assertBackgroundNetworkAccess(false);
removeAppIdleWhitelist(mUid);
assertBackgroundNetworkAccess(false);
} finally {
setAppIdle(false);
setDozeMode(false);
}
}
@RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
@Test
public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
setDozeMode(true);
setAppIdle(true);
try {
assertBackgroundNetworkAccess(false);
addAppIdleWhitelist(mUid);
assertBackgroundNetworkAccess(false);
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(true);
// Wait until the whitelist duration is expired.
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(false);
} finally {
setAppIdle(false);
setDozeMode(false);
removeAppIdleWhitelist(mUid);
}
}
@RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
@Test
public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
setBatterySaverMode(true);
setAppIdle(true);
try {
assertBackgroundNetworkAccess(false);
addAppIdleWhitelist(mUid);
assertBackgroundNetworkAccess(false);
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(true);
// Wait until the whitelist duration is expired.
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
assertBackgroundNetworkAccess(false);
} finally {
setAppIdle(false);
setBatterySaverMode(false);
removeAppIdleWhitelist(mUid);
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.os.Bundle;
import android.view.WindowManager;
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);
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2016 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.Notification;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteInput;
import android.content.ComponentName;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
/**
* NotificationListenerService implementation that executes the notification actions once they're
* created.
*/
public class MyNotificationListenerService extends NotificationListenerService {
private static final String TAG = "MyNotificationListenerService";
@Override
public void onListenerConnected() {
Log.d(TAG, "onListenerConnected()");
}
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
Log.d(TAG, "onNotificationPosted(): " + sbn);
if (!sbn.getPackageName().startsWith(getPackageName())) {
Log.v(TAG, "ignoring notification from a different package");
return;
}
final PendingIntentSender sender = new PendingIntentSender();
final Notification notification = sbn.getNotification();
if (notification.contentIntent != null) {
sender.send("content", notification.contentIntent);
}
if (notification.deleteIntent != null) {
sender.send("delete", notification.deleteIntent);
}
if (notification.fullScreenIntent != null) {
sender.send("full screen", notification.fullScreenIntent);
}
if (notification.actions != null) {
for (Notification.Action action : notification.actions) {
sender.send("action", action.actionIntent);
sender.send("action extras", action.getExtras());
final RemoteInput[] remoteInputs = action.getRemoteInputs();
if (remoteInputs != null && remoteInputs.length > 0) {
for (RemoteInput remoteInput : remoteInputs) {
sender.send("remote input extras", remoteInput.getExtras());
}
}
}
}
sender.send("notification extras", notification.extras);
}
static String getId() {
return String.format("%s/%s", MyNotificationListenerService.class.getPackage().getName(),
MyNotificationListenerService.class.getName());
}
static ComponentName getComponentName() {
return new ComponentName(MyNotificationListenerService.class.getPackage().getName(),
MyNotificationListenerService.class.getName());
}
private static final class PendingIntentSender {
private PendingIntent mSentIntent = null;
private String mReason = null;
private void send(String reason, PendingIntent pendingIntent) {
if (pendingIntent == null) {
// Could happen on action that only has extras
Log.v(TAG, "Not sending null pending intent for " + reason);
return;
}
if (mSentIntent != null || mReason != null) {
// Sanity check: make sure test case set up just one pending intent in the
// notification, otherwise it could pass because another pending intent caused the
// whitelisting.
throw new IllegalStateException("Already sent a PendingIntent (" + mSentIntent
+ ") for reason '" + mReason + "' when requested another for '" + reason
+ "' (" + pendingIntent + ")");
}
Log.i(TAG, "Sending pending intent for " + reason + ":" + pendingIntent);
try {
pendingIntent.send();
mSentIntent = pendingIntent;
mReason = reason;
} catch (CanceledException e) {
Log.w(TAG, "Pending intent " + pendingIntent + " canceled");
}
}
private void send(String reason, Bundle extras) {
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
if (value instanceof PendingIntent) {
send(reason + " with key '" + key + "'", (PendingIntent) value);
}
}
}
}
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2016 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.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.ConditionVariable;
import android.os.IBinder;
import android.os.RemoteException;
import com.android.cts.net.hostside.IMyService;
public class MyServiceClient {
private static final int TIMEOUT_MS = 5000;
private static final String PACKAGE = MyServiceClient.class.getPackage().getName();
private static final String APP2_PACKAGE = PACKAGE + ".app2";
private static final String SERVICE_NAME = APP2_PACKAGE + ".MyService";
private Context mContext;
private ServiceConnection mServiceConnection;
private IMyService mService;
public MyServiceClient(Context context) {
mContext = context;
}
public void bind() {
if (mService != null) {
throw new IllegalStateException("Already bound");
}
final ConditionVariable cv = new ConditionVariable();
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IMyService.Stub.asInterface(service);
cv.open();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
final Intent intent = new Intent();
intent.setComponent(new ComponentName(APP2_PACKAGE, SERVICE_NAME));
// Needs to use BIND_NOT_FOREGROUND so app2 does not run in
// the same process state as app
mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE
| Context.BIND_NOT_FOREGROUND);
cv.block(TIMEOUT_MS);
if (mService == null) {
throw new IllegalStateException(
"Could not bind to MyService service after " + TIMEOUT_MS + "ms");
}
}
public void unbind() {
if (mService != null) {
mContext.unbindService(mServiceConnection);
}
}
public void registerBroadcastReceiver() throws RemoteException {
mService.registerBroadcastReceiver();
}
public int getCounters(String receiverName, String action) throws RemoteException {
return mService.getCounters(receiverName, action);
}
public String checkNetworkStatus() throws RemoteException {
return mService.checkNetworkStatus();
}
public String getRestrictBackgroundStatus() throws RemoteException {
return mService.getRestrictBackgroundStatus();
}
public void sendNotification(int notificationId, String notificationType) throws RemoteException {
mService.sendNotification(notificationId, notificationType);
}
public void registerNetworkCallback(INetworkCallback cb) throws RemoteException {
mService.registerNetworkCallback(cb);
}
public void unregisterNetworkCallback() throws RemoteException {
mService.unregisterNetworkCallback();
}
}

View File

@@ -0,0 +1,184 @@
/*
* 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.Network;
import android.net.ProxyInfo;
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;
public static final String ACTION_ESTABLISHED = "com.android.cts.net.hostside.ESTABNLISHED";
public static final String EXTRA_ALWAYS_ON = "is-always-on";
public static final String EXTRA_LOCKDOWN_ENABLED = "is-lockdown-enabled";
private ParcelFileDescriptor mFd = null;
private PacketReflector mPacketReflector = 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;
}
}
}
}
ArrayList<Network> underlyingNetworks =
intent.getParcelableArrayListExtra(packageName + ".underlyingNetworks");
if (underlyingNetworks == null) {
// VPN tracks default network
builder.setUnderlyingNetworks(null);
} else {
builder.setUnderlyingNetworks(underlyingNetworks.toArray(new Network[0]));
}
boolean isAlwaysMetered = intent.getBooleanExtra(packageName + ".isAlwaysMetered", false);
builder.setMetered(isAlwaysMetered);
ProxyInfo vpnProxy = intent.getParcelableExtra(packageName + ".httpProxy");
builder.setHttpProxy(vpnProxy);
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()));
broadcastEstablished();
mPacketReflector = new PacketReflector(mFd.getFileDescriptor(), MTU);
mPacketReflector.start();
}
private void broadcastEstablished() {
final Intent bcIntent = new Intent(ACTION_ESTABLISHED);
bcIntent.putExtra(EXTRA_ALWAYS_ON, isAlwaysOn());
bcIntent.putExtra(EXTRA_LOCKDOWN_ENABLED, isLockdownEnabled());
sendBroadcast(bcIntent);
}
private void stop() {
if (mPacketReflector != null) {
mPacketReflector.interrupt();
mPacketReflector = 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();
}
}

View File

@@ -0,0 +1,300 @@
/*
* Copyright (C) 2019 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 static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.util.Log;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
private Network mNetwork;
private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
@Rule
public final MeterednessConfigurationRule mMeterednessConfiguration
= new MeterednessConfigurationRule();
enum CallbackState {
NONE,
AVAILABLE,
LOST,
BLOCKED_STATUS,
CAPABILITIES
}
private static class CallbackInfo {
public final CallbackState state;
public final Network network;
public final Object arg;
CallbackInfo(CallbackState s, Network n, Object o) {
state = s; network = n; arg = o;
}
public String toString() {
return String.format("%s (%s) (%s)", state, network, arg);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof CallbackInfo)) return false;
// Ignore timeMs, since it's unpredictable.
final CallbackInfo other = (CallbackInfo) o;
return (state == other.state) && Objects.equals(network, other.network)
&& Objects.equals(arg, other.arg);
}
@Override
public int hashCode() {
return Objects.hash(state, network, arg);
}
}
private class TestNetworkCallback extends INetworkCallback.Stub {
private static final int TEST_CONNECT_TIMEOUT_MS = 30_000;
private static final int TEST_CALLBACK_TIMEOUT_MS = 5_000;
private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
protected void setLastCallback(CallbackState state, Network network, Object o) {
mCallbacks.offer(new CallbackInfo(state, network, o));
}
CallbackInfo nextCallback(int timeoutMs) {
CallbackInfo cb = null;
try {
cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
if (cb == null) {
fail("Did not receive callback after " + timeoutMs + "ms");
}
return cb;
}
CallbackInfo expectCallback(CallbackState state, Network expectedNetwork, Object o) {
final CallbackInfo expected = new CallbackInfo(state, expectedNetwork, o);
final CallbackInfo actual = nextCallback(TEST_CALLBACK_TIMEOUT_MS);
assertEquals("Unexpected callback:", expected, actual);
return actual;
}
@Override
public void onAvailable(Network network) {
setLastCallback(CallbackState.AVAILABLE, network, null);
}
@Override
public void onLost(Network network) {
setLastCallback(CallbackState.LOST, network, null);
}
@Override
public void onBlockedStatusChanged(Network network, boolean blocked) {
setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked);
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities cap) {
setLastCallback(CallbackState.CAPABILITIES, network, cap);
}
public Network expectAvailableCallbackAndGetNetwork() {
final CallbackInfo cb = nextCallback(TEST_CONNECT_TIMEOUT_MS);
if (cb.state != CallbackState.AVAILABLE) {
fail("Network is not available. Instead obtained the following callback :"
+ cb);
}
return cb.network;
}
public void expectBlockedStatusCallback(Network expectedNetwork, boolean expectBlocked) {
expectCallback(CallbackState.BLOCKED_STATUS, expectedNetwork, expectBlocked);
}
public void expectBlockedStatusCallbackEventually(Network expectedNetwork,
boolean expectBlocked) {
final long deadline = System.currentTimeMillis() + TEST_CALLBACK_TIMEOUT_MS;
do {
final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
if (cb.state == CallbackState.BLOCKED_STATUS
&& cb.network.equals(expectedNetwork)) {
assertEquals(expectBlocked, cb.arg);
return;
}
} while (System.currentTimeMillis() <= deadline);
fail("Didn't receive onBlockedStatusChanged()");
}
public void expectCapabilitiesCallbackEventually(Network expectedNetwork, boolean hasCap,
int cap) {
final long deadline = System.currentTimeMillis() + TEST_CALLBACK_TIMEOUT_MS;
do {
final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
if (cb.state != CallbackState.CAPABILITIES
|| !expectedNetwork.equals(cb.network)
|| (hasCap != ((NetworkCapabilities) cb.arg).hasCapability(cap))) {
Log.i("NetworkCallbackTest#expectCapabilitiesCallback",
"Ignoring non-matching callback : " + cb);
continue;
}
// Found a match, return
return;
} while (System.currentTimeMillis() <= deadline);
fail("Didn't receive the expected callback to onCapabilitiesChanged(). Check the "
+ "log for a list of received callbacks, if any.");
}
}
@Before
public void setUp() throws Exception {
super.setUp();
assumeTrue(isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness());
registerBroadcastReceiver();
removeRestrictBackgroundWhitelist(mUid);
removeRestrictBackgroundBlacklist(mUid);
assertRestrictBackgroundChangedReceived(0);
// Initial state
setBatterySaverMode(false);
setRestrictBackground(false);
// Make wifi a metered network.
mMeterednessConfiguration.configureNetworkMeteredness(true);
// Register callback
registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
// Once the wifi is marked as metered, the wifi will reconnect. Wait for onAvailable()
// callback to ensure wifi is connected before the test and store the default network.
mNetwork = mTestNetworkCallback.expectAvailableCallbackAndGetNetwork();
// Check that the network is metered.
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
false /* hasCapability */, NET_CAPABILITY_NOT_METERED);
mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
}
@After
public void tearDown() throws Exception {
super.tearDown();
setRestrictBackground(false);
setBatterySaverMode(false);
unregisterNetworkCallback();
}
@RequiredProperties({DATA_SAVER_MODE})
@Test
public void testOnBlockedStatusChanged_dataSaver() throws Exception {
try {
// Enable restrict background
setRestrictBackground(true);
assertBackgroundNetworkAccess(false);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
// Add to whitelist
addRestrictBackgroundWhitelist(mUid);
assertBackgroundNetworkAccess(true);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
// Remove from whitelist
removeRestrictBackgroundWhitelist(mUid);
assertBackgroundNetworkAccess(false);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
} finally {
mMeterednessConfiguration.resetNetworkMeteredness();
}
// Set to non-metered network
mMeterednessConfiguration.configureNetworkMeteredness(false);
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
try {
assertBackgroundNetworkAccess(true);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
// Disable restrict background, should not trigger callback
setRestrictBackground(false);
assertBackgroundNetworkAccess(true);
} finally {
mMeterednessConfiguration.resetNetworkMeteredness();
}
}
@RequiredProperties({BATTERY_SAVER_MODE})
@Test
public void testOnBlockedStatusChanged_powerSaver() throws Exception {
try {
// Enable Power Saver
setBatterySaverMode(true);
assertBackgroundNetworkAccess(false);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
// Disable Power Saver
setBatterySaverMode(false);
assertBackgroundNetworkAccess(true);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
} finally {
mMeterednessConfiguration.resetNetworkMeteredness();
}
// Set to non-metered network
mMeterednessConfiguration.configureNetworkMeteredness(false);
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
try {
// Enable Power Saver
setBatterySaverMode(true);
assertBackgroundNetworkAccess(false);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
// Disable Power Saver
setBatterySaverMode(false);
assertBackgroundNetworkAccess(true);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
} finally {
mMeterednessConfiguration.resetNetworkMeteredness();
}
}
// TODO: 1. test against VPN lockdown.
// 2. test against multiple networks.
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 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 androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
import org.junit.rules.RunRules;
import org.junit.rules.TestRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import java.util.List;
/**
* Custom runner to allow dumping logs after a test failure before the @After methods get to run.
*/
public class NetworkPolicyTestRunner extends AndroidJUnit4ClassRunner {
private TestRule mDumpOnFailureRule = new DumpOnFailureRule();
public NetworkPolicyTestRunner(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
public Statement methodInvoker(FrameworkMethod method, Object test) {
return new RunRules(super.methodInvoker(method, test), List.of(mDumpOnFailureRule),
describeChild(method));
}
}

View File

@@ -0,0 +1,284 @@
/*
* Copyright (C) 2019 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 static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiManager;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import com.android.compatibility.common.util.AppStandbyUtils;
import com.android.compatibility.common.util.BatteryUtils;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import androidx.test.platform.app.InstrumentationRegistry;
public class NetworkPolicyTestUtils {
private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 5000;
private static ConnectivityManager mCm;
private static WifiManager mWm;
private static Boolean mBatterySaverSupported;
private static Boolean mDataSaverSupported;
private static Boolean mDozeModeSupported;
private static Boolean mAppStandbySupported;
private NetworkPolicyTestUtils() {}
public static boolean isBatterySaverSupported() {
if (mBatterySaverSupported == null) {
mBatterySaverSupported = BatteryUtils.isBatterySaverSupported();
}
return mBatterySaverSupported;
}
/**
* As per CDD requirements, if the device doesn't support data saver mode then
* ConnectivityManager.getRestrictBackgroundStatus() will always return
* RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
* ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
* RESTRICT_BACKGROUND_STATUS_DISABLED or not.
*/
public static boolean isDataSaverSupported() {
if (mDataSaverSupported == null) {
assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
try {
setRestrictBackground(true);
mDataSaverSupported = !isMyRestrictBackgroundStatus(
RESTRICT_BACKGROUND_STATUS_DISABLED);
} finally {
setRestrictBackground(false);
}
}
return mDataSaverSupported;
}
public static boolean isDozeModeSupported() {
if (mDozeModeSupported == null) {
final String result = executeShellCommand("cmd deviceidle enabled deep");
mDozeModeSupported = result.equals("1");
}
return mDozeModeSupported;
}
public static boolean isAppStandbySupported() {
if (mAppStandbySupported == null) {
mAppStandbySupported = AppStandbyUtils.isAppStandbyEnabled();
}
return mAppStandbySupported;
}
public static boolean isLowRamDevice() {
final ActivityManager am = (ActivityManager) getContext().getSystemService(
Context.ACTIVITY_SERVICE);
return am.isLowRamDevice();
}
public static boolean isLocationEnabled() {
final LocationManager lm = (LocationManager) getContext().getSystemService(
Context.LOCATION_SERVICE);
return lm.isLocationEnabled();
}
public static void setLocationEnabled(boolean enabled) {
final LocationManager lm = (LocationManager) getContext().getSystemService(
Context.LOCATION_SERVICE);
lm.setLocationEnabledForUser(enabled, Process.myUserHandle());
assertEquals("Couldn't change location enabled state", lm.isLocationEnabled(), enabled);
Log.d(TAG, "Changed location enabled state to " + enabled);
}
public static boolean isActiveNetworkMetered(boolean metered) {
return getConnectivityManager().isActiveNetworkMetered() == metered;
}
public static boolean canChangeActiveNetworkMeteredness() {
final Network activeNetwork = getConnectivityManager().getActiveNetwork();
final NetworkCapabilities networkCapabilities
= getConnectivityManager().getNetworkCapabilities(activeNetwork);
return networkCapabilities.hasTransport(TRANSPORT_WIFI);
}
public static Pair<String, Boolean> setupMeteredNetwork(boolean metered) throws Exception {
if (isActiveNetworkMetered(metered)) {
return null;
}
final boolean isLocationEnabled = isLocationEnabled();
try {
if (!isLocationEnabled) {
setLocationEnabled(true);
}
final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
setWifiMeteredStatus(ssid, metered);
return Pair.create(ssid, !metered);
} finally {
// Reset the location enabled state
if (!isLocationEnabled) {
setLocationEnabled(false);
}
}
}
public static void resetMeteredNetwork(String ssid, boolean metered) throws Exception {
setWifiMeteredStatus(ssid, metered);
}
public static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
assertFalse("SSID should not be empty", TextUtils.isEmpty(ssid));
final String cmd = "cmd netpolicy set metered-network " + ssid + " " + metered;
executeShellCommand(cmd);
assertWifiMeteredStatus(ssid, metered);
assertActiveNetworkMetered(metered);
}
public static void assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus) {
final String result = executeShellCommand("cmd netpolicy list wifi-networks");
final String expectedLine = ssid + ";" + expectedMeteredStatus;
assertTrue("Expected line: " + expectedLine + "; Actual result: " + result,
result.contains(expectedLine));
}
// Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
public static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final NetworkCallback networkCallback = new NetworkCallback() {
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
if (metered == expectedMeteredStatus) {
latch.countDown();
}
}
};
// Registering a callback here guarantees onCapabilitiesChanged is called immediately
// with the current setting. Therefore, if the setting has already been changed,
// this method will return right away, and if not it will wait for the setting to change.
getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
fail("Timed out waiting for active network metered status to change to "
+ expectedMeteredStatus + " ; network = "
+ getConnectivityManager().getActiveNetwork());
}
getConnectivityManager().unregisterNetworkCallback(networkCallback);
}
public static void setRestrictBackground(boolean enabled) {
executeShellCommand("cmd netpolicy set restrict-background " + enabled);
final String output = executeShellCommand("cmd netpolicy get restrict-background");
final String expectedSuffix = enabled ? "enabled" : "disabled";
assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
output.endsWith(expectedSuffix));
}
public static boolean isMyRestrictBackgroundStatus(int expectedStatus) {
final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
if (expectedStatus != actualStatus) {
Log.d(TAG, "MyRestrictBackgroundStatus: "
+ "Expected: " + restrictBackgroundValueToString(expectedStatus)
+ "; Actual: " + restrictBackgroundValueToString(actualStatus));
return false;
}
return true;
}
// Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
private static String unquoteSSID(String ssid) {
// SSID is returned surrounded by quotes if it can be decoded as UTF-8.
// Otherwise it's guaranteed not to start with a quote.
if (ssid.charAt(0) == '"') {
return ssid.substring(1, ssid.length() - 1);
} else {
return ssid;
}
}
public static String restrictBackgroundValueToString(int status) {
switch (status) {
case RESTRICT_BACKGROUND_STATUS_DISABLED:
return "DISABLED";
case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
return "WHITELISTED";
case RESTRICT_BACKGROUND_STATUS_ENABLED:
return "ENABLED";
default:
return "UNKNOWN_STATUS_" + status;
}
}
public static String executeShellCommand(String command) {
final String result = runShellCommand(command).trim();
Log.d(TAG, "Output of '" + command + "': '" + result + "'");
return result;
}
public static void assertMyRestrictBackgroundStatus(int expectedStatus) {
final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
assertEquals(restrictBackgroundValueToString(expectedStatus),
restrictBackgroundValueToString(actualStatus));
}
public static ConnectivityManager getConnectivityManager() {
if (mCm == null) {
mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
}
return mCm;
}
public static WifiManager getWifiManager() {
if (mWm == null) {
mWm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
}
return mWm;
}
public static Context getContext() {
return getInstrumentation().getContext();
}
public static Instrumentation getInstrumentation() {
return InstrumentationRegistry.getInstrumentation();
}
}

View File

@@ -0,0 +1,254 @@
/*
* 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 static android.system.OsConstants.ICMP6_ECHO_REPLY;
import static android.system.OsConstants.ICMP6_ECHO_REQUEST;
import static android.system.OsConstants.ICMP_ECHO;
import static android.system.OsConstants.ICMP_ECHOREPLY;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.IOException;
public class PacketReflector extends Thread {
private static int IPV4_HEADER_LENGTH = 20;
private static int IPV6_HEADER_LENGTH = 40;
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 int IPV4_PROTO_OFFSET = 9;
private static int IPV6_PROTO_OFFSET = 6;
private static final byte IPPROTO_ICMP = 1;
private static final byte IPPROTO_TCP = 6;
private static final byte IPPROTO_UDP = 17;
private static final byte IPPROTO_ICMPV6 = 58;
private static int ICMP_HEADER_LENGTH = 8;
private static int TCP_HEADER_LENGTH = 20;
private static int UDP_HEADER_LENGTH = 8;
private static final byte ICMP_ECHO = 8;
private static final byte ICMP_ECHOREPLY = 0;
private static String TAG = "PacketReflector";
private FileDescriptor mFd;
private byte[] mBuf;
public PacketReflector(FileDescriptor fd, int mtu) {
super("PacketReflector");
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;
}
}
private static void swapAddresses(byte[] buf, int version) {
int addrPos, addrLen;
switch(version) {
case 4:
addrPos = IPV4_ADDR_OFFSET;
addrLen = IPV4_ADDR_LENGTH;
break;
case 6:
addrPos = IPV6_ADDR_OFFSET;
addrLen = IPV6_ADDR_LENGTH;
break;
default:
throw new IllegalArgumentException();
}
swapBytes(buf, addrPos, addrPos + addrLen, addrLen);
}
// Reflect TCP packets: swap the source and destination addresses, but don't change the ports.
// This is used by the test to "connect to itself" through the VPN.
private void processTcpPacket(byte[] buf, int version, int len, int hdrLen) {
if (len < hdrLen + TCP_HEADER_LENGTH) {
return;
}
// Swap src and dst IP addresses.
swapAddresses(buf, version);
// Send the packet back.
writePacket(buf, len);
}
// Echo UDP packets: swap source and destination addresses, and source and destination ports.
// This is used by the test to check that the bytes it sends are echoed back.
private void processUdpPacket(byte[] buf, int version, int len, int hdrLen) {
if (len < hdrLen + UDP_HEADER_LENGTH) {
return;
}
// Swap src and dst IP addresses.
swapAddresses(buf, version);
// Swap dst and src ports.
int portOffset = hdrLen;
swapBytes(buf, portOffset, portOffset + 2, 2);
// Send the packet back.
writePacket(buf, len);
}
private void processIcmpPacket(byte[] buf, int version, int len, int hdrLen) {
if (len < hdrLen + ICMP_HEADER_LENGTH) {
return;
}
byte type = buf[hdrLen];
if (!(version == 4 && type == ICMP_ECHO) &&
!(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) {
return;
}
// Save the ping packet we received.
byte[] request = buf.clone();
// Swap src and dst IP addresses, and send the packet back.
// This effectively pings the device to see if it replies.
swapAddresses(buf, version);
writePacket(buf, len);
// The device should have replied, and buf should now contain a ping response.
int received = readPacket(buf);
if (received != len) {
Log.i(TAG, "Reflecting ping did not result in ping response: " +
"read=" + received + " expected=" + len);
return;
}
byte replyType = buf[hdrLen];
if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY)
|| (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) {
Log.i(TAG, "Received unexpected ICMP reply: original " + type
+ ", reply " + replyType);
return;
}
// Compare the response we got with the original packet.
// The only thing that should have changed are addresses, type and checksum.
// Overwrite them with the received bytes and see if the packet is otherwise identical.
request[hdrLen] = buf[hdrLen]; // Type
request[hdrLen + 2] = buf[hdrLen + 2]; // Checksum byte 1.
request[hdrLen + 3] = buf[hdrLen + 3]; // Checksum byte 2.
// Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore
// the request and reply may have different IPv6 flow label: ignore that as well.
if (version == 6) {
request[1] = (byte)(request[1] & 0xf0 | buf[1] & 0x0f);
request[2] = buf[2];
request[3] = buf[3];
}
for (int i = 0; i < len; i++) {
if (buf[i] != request[i]) {
Log.i(TAG, "Received non-matching packet when expecting ping response.");
return;
}
}
// Now swap the addresses again and reflect the packet. This sends a ping reply.
swapAddresses(buf, version);
writePacket(buf, len);
}
private void writePacket(byte[] buf, int len) {
try {
Os.write(mFd, buf, 0, len);
} catch (ErrnoException|IOException e) {
Log.e(TAG, "Error writing packet: " + e.getMessage());
}
}
private int readPacket(byte[] buf) {
int len;
try {
len = Os.read(mFd, buf, 0, buf.length);
} catch (ErrnoException|IOException e) {
Log.e(TAG, "Error reading packet: " + e.getMessage());
len = -1;
}
return len;
}
// Reads one packet from our mFd, and possibly writes the packet back.
private void processPacket() {
int len = readPacket(mBuf);
if (len < 1) {
return;
}
int version = mBuf[0] >> 4;
int addrPos, protoPos, hdrLen, addrLen;
if (version == 4) {
hdrLen = IPV4_HEADER_LENGTH;
protoPos = IPV4_PROTO_OFFSET;
addrPos = IPV4_ADDR_OFFSET;
addrLen = IPV4_ADDR_LENGTH;
} else if (version == 6) {
hdrLen = IPV6_HEADER_LENGTH;
protoPos = IPV6_PROTO_OFFSET;
addrPos = IPV6_ADDR_OFFSET;
addrLen = IPV6_ADDR_LENGTH;
} else {
return;
}
if (len < hdrLen) {
return;
}
byte proto = mBuf[protoPos];
switch (proto) {
case IPPROTO_ICMP:
case IPPROTO_ICMPV6:
processIcmpPacket(mBuf, version, len, hdrLen);
break;
case IPPROTO_TCP:
processTcpPacket(mBuf, version, len, hdrLen);
break;
case IPPROTO_UDP:
processUdpPacket(mBuf, version, len, hdrLen);
break;
}
}
public void run() {
Log.i(TAG, "PacketReflector starting fd=" + mFd + " valid=" + mFd.valid());
while (!interrupted() && mFd.valid()) {
processPacket();
}
Log.i(TAG, "PacketReflector exiting fd=" + mFd + " valid=" + mFd.valid());
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2019 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 static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isLowRamDevice;
public enum Property {
BATTERY_SAVER_MODE(1 << 0) {
public boolean isSupported() { return isBatterySaverSupported(); }
},
DATA_SAVER_MODE(1 << 1) {
public boolean isSupported() { return isDataSaverSupported(); }
},
NO_DATA_SAVER_MODE(~DATA_SAVER_MODE.getValue()) {
public boolean isSupported() { return !isDataSaverSupported(); }
},
DOZE_MODE(1 << 2) {
public boolean isSupported() { return isDozeModeSupported(); }
},
APP_STANDBY_MODE(1 << 3) {
public boolean isSupported() { return isAppStandbySupported(); }
},
NOT_LOW_RAM_DEVICE(1 << 4) {
public boolean isSupported() { return !isLowRamDevice(); }
},
METERED_NETWORK(1 << 5) {
public boolean isSupported() {
return isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness();
}
},
NON_METERED_NETWORK(~METERED_NETWORK.getValue()) {
public boolean isSupported() {
return isActiveNetworkMetered(false) || canChangeActiveNetworkMeteredness();
}
};
private int mValue;
Property(int value) { mValue = value; }
public int getValue() { return mValue; }
abstract boolean isSupported();
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2016 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.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.ConditionVariable;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.system.ErrnoException;
import android.system.Os;
import com.android.cts.net.hostside.IRemoteSocketFactory;
import java.io.FileDescriptor;
import java.io.IOException;
public class RemoteSocketFactoryClient {
private static final int TIMEOUT_MS = 5000;
private static final String PACKAGE = RemoteSocketFactoryClient.class.getPackage().getName();
private static final String APP2_PACKAGE = PACKAGE + ".app2";
private static final String SERVICE_NAME = APP2_PACKAGE + ".RemoteSocketFactoryService";
private Context mContext;
private ServiceConnection mServiceConnection;
private IRemoteSocketFactory mService;
public RemoteSocketFactoryClient(Context context) {
mContext = context;
}
public void bind() {
if (mService != null) {
throw new IllegalStateException("Already bound");
}
final ConditionVariable cv = new ConditionVariable();
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IRemoteSocketFactory.Stub.asInterface(service);
cv.open();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
final Intent intent = new Intent();
intent.setComponent(new ComponentName(APP2_PACKAGE, SERVICE_NAME));
mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
cv.block(TIMEOUT_MS);
if (mService == null) {
throw new IllegalStateException(
"Could not bind to RemoteSocketFactory service after " + TIMEOUT_MS + "ms");
}
}
public void unbind() {
if (mService != null) {
mContext.unbindService(mServiceConnection);
}
}
public FileDescriptor openSocketFd(String host, int port, int timeoutMs)
throws RemoteException, ErrnoException, IOException {
// Dup the filedescriptor so ParcelFileDescriptor's finalizer doesn't garbage collect it
// and cause our fd to become invalid. http://b/35927643 .
ParcelFileDescriptor pfd = mService.openSocketFd(host, port, timeoutMs);
FileDescriptor fd = Os.dup(pfd.getFileDescriptor());
pfd.close();
return fd;
}
public String getPackageName() throws RemoteException {
return mService.getPackageName();
}
public int getUid() throws RemoteException {
return mService.getUid();
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2019 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 static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target({METHOD, TYPE})
@Inherited
public @interface RequiredProperties {
Property[] value();
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2019 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 static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import com.android.compatibility.common.util.BeforeAfterRule;
import org.junit.Assume;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.util.ArrayList;
import java.util.Collections;
public class RequiredPropertiesRule extends BeforeAfterRule {
private static ArraySet<Property> mRequiredProperties;
@Override
public void onBefore(Statement base, Description description) {
mRequiredProperties = getAllRequiredProperties(description);
final String testName = description.getClassName() + "#" + description.getMethodName();
assertTestIsValid(testName, mRequiredProperties);
Log.i(TAG, "Running test " + testName + " with required properties: "
+ propertiesToString(mRequiredProperties));
}
private ArraySet<Property> getAllRequiredProperties(Description description) {
final ArraySet<Property> allRequiredProperties = new ArraySet<>();
RequiredProperties requiredProperties = description.getAnnotation(RequiredProperties.class);
if (requiredProperties != null) {
Collections.addAll(allRequiredProperties, requiredProperties.value());
}
for (Class<?> clazz = description.getTestClass();
clazz != null; clazz = clazz.getSuperclass()) {
requiredProperties = clazz.getDeclaredAnnotation(RequiredProperties.class);
if (requiredProperties == null) {
continue;
}
for (Property requiredProperty : requiredProperties.value()) {
for (Property p : Property.values()) {
if (p.getValue() == ~requiredProperty.getValue()
&& allRequiredProperties.contains(p)) {
continue;
}
}
allRequiredProperties.add(requiredProperty);
}
}
return allRequiredProperties;
}
private void assertTestIsValid(String testName, ArraySet<Property> requiredProperies) {
if (requiredProperies == null) {
return;
}
final ArrayList<Property> unsupportedProperties = new ArrayList<>();
for (Property property : requiredProperies) {
if (!property.isSupported()) {
unsupportedProperties.add(property);
}
}
Assume.assumeTrue("Unsupported properties: "
+ propertiesToString(unsupportedProperties), unsupportedProperties.isEmpty());
}
public static ArraySet<Property> getRequiredProperties() {
return mRequiredProperties;
}
private static String propertiesToString(Iterable<Property> properties) {
return "[" + TextUtils.join(",", properties) + "]";
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
//
// Copyright (C) 2016 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.
//
android_test_helper_app {
name: "CtsHostsideNetworkTestsApp2",
defaults: ["cts_support_defaults"],
sdk_version: "current",
static_libs: ["CtsHostsideNetworkTestsAidl"],
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
certificate: ":cts-net-app",
}

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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.app2" >
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
<!--
This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
them in a shared preferences which is then read by the test app. These broadcasts are
handled by 2 listeners, one defined the manifest and another dynamically registered by
a service.
The manifest-defined listener also handles ordered broadcasts used to share data with the
test app.
This application also provides a service, RemoteSocketFactoryService, that the test app can
use to open sockets to remote hosts as a different user ID.
-->
<application android:usesCleartextTraffic="true">
<activity android:name=".MyActivity" android:exported="true"/>
<service android:name=".MyService" android:exported="true"/>
<service android:name=".MyForegroundService" android:exported="true"/>
<service android:name=".RemoteSocketFactoryService" android:exported="true"/>
<receiver android:name=".MyBroadcastReceiver" >
<intent-filter>
<action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED" />
<action android:name="com.android.cts.net.hostside.app2.action.GET_COUNTERS" />
<action android:name="com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS" />
<action android:name="com.android.cts.net.hostside.app2.action.CHECK_NETWORK" />
<action android:name="com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION" />
<action android:name="com.android.cts.net.hostside.app2.action.SHOW_TOAST" />
</intent-filter>
</receiver>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2016 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.app2;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import com.android.cts.net.hostside.INetworkStateObserver;
public final class Common {
static final String TAG = "CtsNetApp2";
// Constants below must match values defined on app's
// AbstractRestrictBackgroundNetworkTestCase.java
static final String MANIFEST_RECEIVER = "ManifestReceiver";
static final String DYNAMIC_RECEIVER = "DynamicReceiver";
static final String ACTION_RECEIVER_READY =
"com.android.cts.net.hostside.app2.action.RECEIVER_READY";
static final String ACTION_FINISH_ACTIVITY =
"com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
static final String ACTION_SHOW_TOAST =
"com.android.cts.net.hostside.app2.action.SHOW_TOAST";
static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
static final String NOTIFICATION_TYPE_DELETE = "DELETE";
static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
static final String NOTIFICATION_TYPE_ACTION = "ACTION";
static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
static final String TEST_PKG = "com.android.cts.net.hostside";
static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
static int getUid(Context context) {
final String packageName = context.getPackageName();
try {
return context.getPackageManager().getPackageUid(packageName, 0);
} catch (NameNotFoundException e) {
throw new IllegalStateException("Could not get UID for " + packageName, e);
}
}
static void notifyNetworkStateObserver(Context context, Intent intent) {
if (intent == null) {
return;
}
final Bundle extras = intent.getExtras();
if (extras == null) {
return;
}
final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface(
extras.getBinder(KEY_NETWORK_STATE_OBSERVER));
if (observer != null) {
try {
if (!observer.isForeground()) {
Log.e(TAG, "App didn't come to foreground");
observer.onNetworkStateChecked(null);
return;
}
} catch (RemoteException e) {
Log.e(TAG, "Error occurred while reading the proc state: " + e);
}
AsyncTask.execute(() -> {
try {
observer.onNetworkStateChecked(
MyBroadcastReceiver.checkNetworkStatus(context));
} catch (RemoteException e) {
Log.e(TAG, "Error occurred while notifying the observer: " + e);
}
});
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2016 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.app2;
import static com.android.cts.net.hostside.app2.Common.ACTION_FINISH_ACTIVITY;
import static com.android.cts.net.hostside.app2.Common.TAG;
import static com.android.cts.net.hostside.app2.Common.TEST_PKG;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import com.android.cts.net.hostside.INetworkStateObserver;
/**
* Activity used to bring process to foreground.
*/
public class MyActivity extends Activity {
private BroadcastReceiver finishCommandReceiver = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "MyActivity.onCreate()");
Common.notifyNetworkStateObserver(this, getIntent());
finishCommandReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Finishing MyActivity");
MyActivity.this.finish();
}
};
registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY));
}
@Override
public void finish() {
if (finishCommandReceiver != null) {
unregisterReceiver(finishCommandReceiver);
}
super.finish();
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "MyActivity.onStart()");
}
@Override
protected void onDestroy() {
Log.d(TAG, "MyActivity.onDestroy()");
super.onDestroy();
}
}

View File

@@ -0,0 +1,267 @@
/*
* Copyright (C) 2016 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.app2;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
import static com.android.cts.net.hostside.app2.Common.ACTION_SHOW_TOAST;
import static com.android.cts.net.hostside.app2.Common.MANIFEST_RECEIVER;
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
import static com.android.cts.net.hostside.app2.Common.TAG;
import static com.android.cts.net.hostside.app2.Common.getUid;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Receiver used to:
* <ol>
* <li>Count number of {@code RESTRICT_BACKGROUND_CHANGED} broadcasts received.
* <li>Show a toast.
* </ol>
*/
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final int NETWORK_TIMEOUT_MS = 5 * 1000;
private final String mName;
public MyBroadcastReceiver() {
this(MANIFEST_RECEIVER);
}
MyBroadcastReceiver(String name) {
Log.d(TAG, "Constructing MyBroadcastReceiver named " + name);
mName = name;
}
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive() for " + mName + ": " + intent);
final String action = intent.getAction();
switch (action) {
case ACTION_RESTRICT_BACKGROUND_CHANGED:
increaseCounter(context, action);
break;
case ACTION_RECEIVER_READY:
final String message = mName + " is ready to rumble";
Log.d(TAG, message);
setResultData(message);
break;
case ACTION_SHOW_TOAST:
showToast(context);
break;
default:
Log.e(TAG, "received unexpected action: " + action);
}
}
@Override
public String toString() {
return "[MyBroadcastReceiver: mName=" + mName + "]";
}
private void increaseCounter(Context context, String action) {
final SharedPreferences prefs = context.getApplicationContext()
.getSharedPreferences(mName, Context.MODE_PRIVATE);
final int value = prefs.getInt(action, 0) + 1;
Log.d(TAG, "increaseCounter('" + action + "'): setting '" + mName + "' to " + value);
prefs.edit().putInt(action, value).apply();
}
static int getCounter(Context context, String action, String receiverName) {
final SharedPreferences prefs = context.getSharedPreferences(receiverName,
Context.MODE_PRIVATE);
final int value = prefs.getInt(action, 0);
Log.d(TAG, "getCounter('" + action + "', '" + receiverName + "'): " + value);
return value;
}
static String getRestrictBackgroundStatus(Context context) {
final ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
final int apiStatus = cm.getRestrictBackgroundStatus();
Log.d(TAG, "getRestrictBackgroundStatus: returning " + apiStatus);
return String.valueOf(apiStatus);
}
private static final String NETWORK_STATUS_TEMPLATE = "%s|%s|%s|%s|%s";
/**
* Checks whether the network is available and return a string which can then be send as a
* result data for the ordered broadcast.
*
* <p>
* The string has the following format:
*
* <p><pre><code>
* NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo
* </code></pre>
*
* <p>Where:
*
* <ul>
* <li>{@code NetinfoState}: enum value of {@link NetworkInfo.State}.
* <li>{@code NetinfoDetailedState}: enum value of {@link NetworkInfo.DetailedState}.
* <li>{@code RealConnectionCheck}: boolean value of a real connection check (i.e., an attempt
* to access an external website.
* <li>{@code RealConnectionCheckDetails}: if HTTP output core or exception string of the real
* connection attempt
* <li>{@code Netinfo}: string representation of the {@link NetworkInfo}.
* </ul>
*
* For example, if the connection was established fine, the result would be something like:
* <p><pre><code>
* CONNECTED|CONNECTED|true|200|[type: WIFI[], state: CONNECTED/CONNECTED, reason: ...]
* </code></pre>
*
*/
// TODO: now that it uses Binder, it counl return a Bundle with the data parts instead...
static String checkNetworkStatus(Context context) {
final ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
// TODO: connect to a hostside server instead
final String address = "http://example.com";
final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
Log.d(TAG, "Running checkNetworkStatus() on thread "
+ Thread.currentThread().getName() + " for UID " + getUid(context)
+ "\n\tactiveNetworkInfo: " + networkInfo + "\n\tURL: " + address);
boolean checkStatus = false;
String checkDetails = "N/A";
try {
final URL url = new URL(address);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(NETWORK_TIMEOUT_MS);
conn.setConnectTimeout(NETWORK_TIMEOUT_MS / 2);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.connect();
final int response = conn.getResponseCode();
checkStatus = true;
checkDetails = "HTTP response for " + address + ": " + response;
} catch (Exception e) {
checkStatus = false;
checkDetails = "Exception getting " + address + ": " + e;
}
Log.d(TAG, checkDetails);
final String state, detailedState;
if (networkInfo != null) {
state = networkInfo.getState().name();
detailedState = networkInfo.getDetailedState().name();
} else {
state = detailedState = "null";
}
final String status = String.format(NETWORK_STATUS_TEMPLATE, state, detailedState,
Boolean.valueOf(checkStatus), checkDetails, networkInfo);
Log.d(TAG, "Offering " + status);
return status;
}
/**
* Sends a system notification containing actions with pending intents to launch the app's
* main activitiy or service.
*/
static void sendNotification(Context context, String channelId, int notificationId,
String notificationType ) {
Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType);
final Intent serviceIntent = new Intent(context, MyService.class);
final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
notificationId);
final Bundle bundle = new Bundle();
bundle.putCharSequence("parcelable", "I am not");
final Notification.Builder builder = new Notification.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_notification);
Action action = null;
switch (notificationType) {
case NOTIFICATION_TYPE_CONTENT:
builder
.setContentTitle("Light, Cameras...")
.setContentIntent(pendingIntent);
break;
case NOTIFICATION_TYPE_DELETE:
builder.setDeleteIntent(pendingIntent);
break;
case NOTIFICATION_TYPE_FULL_SCREEN:
builder.setFullScreenIntent(pendingIntent, true);
break;
case NOTIFICATION_TYPE_BUNDLE:
bundle.putParcelable("Magnum P.I. (Pending Intent)", pendingIntent);
builder.setExtras(bundle);
break;
case NOTIFICATION_TYPE_ACTION:
action = new Action.Builder(
R.drawable.ic_notification, "ACTION", pendingIntent)
.build();
builder.addAction(action);
break;
case NOTIFICATION_TYPE_ACTION_BUNDLE:
bundle.putParcelable("Magnum A.P.I. (Action Pending Intent)", pendingIntent);
action = new Action.Builder(
R.drawable.ic_notification, "ACTION WITH BUNDLE", null)
.addExtras(bundle)
.build();
builder.addAction(action);
break;
case NOTIFICATION_TYPE_ACTION_REMOTE_INPUT:
bundle.putParcelable("Magnum R.I. (Remote Input)", null);
final RemoteInput remoteInput = new RemoteInput.Builder("RI")
.addExtras(bundle)
.build();
action = new Action.Builder(
R.drawable.ic_notification, "ACTION WITH REMOTE INPUT", pendingIntent)
.addRemoteInput(remoteInput)
.build();
builder.addAction(action);
break;
default:
Log.e(TAG, "Unknown notification type: " + notificationType);
return;
}
final Notification notification = builder.build();
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
.notify(notificationId, notification);
}
private void showToast(Context context) {
Toast.makeText(context, "Toast from CTS test", Toast.LENGTH_SHORT).show();
setResultData("Shown");
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2016 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.app2;
import static com.android.cts.net.hostside.app2.Common.TAG;
import static com.android.cts.net.hostside.app2.Common.TEST_PKG;
import android.R;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.cts.net.hostside.INetworkStateObserver;
/**
* Service used to change app state to FOREGROUND_SERVICE.
*/
public class MyForegroundService extends Service {
private static final String NOTIFICATION_CHANNEL_ID = "cts/MyForegroundService";
private static final int FLAG_START_FOREGROUND = 1;
private static final int FLAG_STOP_FOREGROUND = 2;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "MyForegroundService.onStartCommand(): " + intent);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(new NotificationChannel(
NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
NotificationManager.IMPORTANCE_DEFAULT));
switch (intent.getFlags()) {
case FLAG_START_FOREGROUND:
Log.d(TAG, "Starting foreground");
startForeground(42, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_dialog_alert) // any icon is fine
.build());
Common.notifyNetworkStateObserver(this, intent);
break;
case FLAG_STOP_FOREGROUND:
Log.d(TAG, "Stopping foreground");
stopForeground(true);
break;
default:
Log.wtf(TAG, "Invalid flag on intent " + intent);
}
return START_STICKY;
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright (C) 2016 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.app2;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
import static com.android.cts.net.hostside.app2.Common.TAG;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.cts.net.hostside.IMyService;
import com.android.cts.net.hostside.INetworkCallback;
/**
* Service used to dynamically register a broadcast receiver.
*/
public class MyService extends Service {
private static final String NOTIFICATION_CHANNEL_ID = "MyService";
ConnectivityManager mCm;
private MyBroadcastReceiver mReceiver;
private ConnectivityManager.NetworkCallback mNetworkCallback;
// TODO: move MyBroadcast static functions here - they were kept there to make git diff easier.
private IMyService.Stub mBinder =
new IMyService.Stub() {
@Override
public void registerBroadcastReceiver() {
if (mReceiver != null) {
Log.d(TAG, "receiver already registered: " + mReceiver);
return;
}
final Context context = getApplicationContext();
mReceiver = new MyBroadcastReceiver(DYNAMIC_RECEIVER);
context.registerReceiver(mReceiver, new IntentFilter(ACTION_RECEIVER_READY));
context.registerReceiver(mReceiver,
new IntentFilter(ACTION_RESTRICT_BACKGROUND_CHANGED));
Log.d(TAG, "receiver registered");
}
@Override
public int getCounters(String receiverName, String action) {
return MyBroadcastReceiver.getCounter(getApplicationContext(), action, receiverName);
}
@Override
public String checkNetworkStatus() {
return MyBroadcastReceiver.checkNetworkStatus(getApplicationContext());
}
@Override
public String getRestrictBackgroundStatus() {
return MyBroadcastReceiver.getRestrictBackgroundStatus(getApplicationContext());
}
@Override
public void sendNotification(int notificationId, String notificationType) {
MyBroadcastReceiver .sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
notificationId, notificationType);
}
@Override
public void registerNetworkCallback(INetworkCallback cb) {
if (mNetworkCallback != null) {
Log.d(TAG, "unregister previous network callback: " + mNetworkCallback);
unregisterNetworkCallback();
}
Log.d(TAG, "registering network callback");
mNetworkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onBlockedStatusChanged(Network network, boolean blocked) {
try {
cb.onBlockedStatusChanged(network, blocked);
} catch (RemoteException e) {
Log.d(TAG, "Cannot send onBlockedStatusChanged: " + e);
unregisterNetworkCallback();
}
}
@Override
public void onAvailable(Network network) {
try {
cb.onAvailable(network);
} catch (RemoteException e) {
Log.d(TAG, "Cannot send onAvailable: " + e);
unregisterNetworkCallback();
}
}
@Override
public void onLost(Network network) {
try {
cb.onLost(network);
} catch (RemoteException e) {
Log.d(TAG, "Cannot send onLost: " + e);
unregisterNetworkCallback();
}
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities cap) {
try {
cb.onCapabilitiesChanged(network, cap);
} catch (RemoteException e) {
Log.d(TAG, "Cannot send onCapabilitiesChanged: " + e);
unregisterNetworkCallback();
}
}
};
mCm.registerNetworkCallback(makeWifiNetworkRequest(), mNetworkCallback);
try {
cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0);
} catch (RemoteException e) {
unregisterNetworkCallback();
}
}
@Override
public void unregisterNetworkCallback() {
Log.d(TAG, "unregistering network callback");
if (mNetworkCallback != null) {
mCm.unregisterNetworkCallback(mNetworkCallback);
mNetworkCallback = null;
}
}
};
private NetworkRequest makeWifiNetworkRequest() {
return new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onCreate() {
final Context context = getApplicationContext();
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
mCm = (ConnectivityManager) getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
}
@Override
public void onDestroy() {
final Context context = getApplicationContext();
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
if (mReceiver != null) {
Log.d(TAG, "onDestroy(): unregistering " + mReceiver);
getApplicationContext().unregisterReceiver(mReceiver);
}
super.onDestroy();
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2016 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.app2;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.util.Log;
import com.android.cts.net.hostside.IRemoteSocketFactory;
import java.net.Socket;
public class RemoteSocketFactoryService extends Service {
private static final String TAG = RemoteSocketFactoryService.class.getSimpleName();
private IRemoteSocketFactory.Stub mBinder = new IRemoteSocketFactory.Stub() {
@Override
public ParcelFileDescriptor openSocketFd(String host, int port, int timeoutMs) {
try {
Socket s = new Socket(host, port);
s.setSoTimeout(timeoutMs);
return ParcelFileDescriptor.fromSocket(s);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
@Override
public String getPackageName() {
return RemoteSocketFactoryService.this.getPackageName();
}
@Override
public int getUid() {
return Process.myUid();
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}

View File

@@ -0,0 +1,4 @@
android_app_certificate {
name: "cts-net-app",
certificate: "cts-net-app",
}

View File

@@ -0,0 +1,2 @@
# Generated with:
development/tools/make_key cts-net-app '/CN=cts-net-app'

Binary file not shown.

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDAjCCAeqgAwIBAgIJAMhWwIIqr1r6MA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
BAMMC2N0cy1uZXQtYXBwMB4XDTE4MDYyMDAyMjAwN1oXDTQ1MTEwNTAyMjAwN1ow
FjEUMBIGA1UEAwwLY3RzLW5ldC1hcHAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDefOayWQss1E+FQIONK6IhlXhe0BEyHshIrnPOOmuCPa/Svfbnmziy
hr1KTjaQ3ET/mGShwlt6AUti7nKx9aB71IJp5mSBuwW62A8jvN3yNOo45YV8+n1o
TrEoMWMf7hQmoOSqaSJ+VFuVms/kPSEh99okDgHCej6rsEkEcDoh6pJajQyUYDwR
SNAF8SrqCDhqFbZW/LWedvuikCUlNtzuv7/GrcLcsiWEfHv7UOBKpMjLo9BhD1XF
IefnxImcBQrQGMnE9TLixBiEeX5yauLgbZuxBqD/zsI2TH1FjxTeuJan83kLbqqH
FgyvPaUjwckAdQPyom7ZUYFnBc0LQ9xzAgMBAAGjUzBRMB0GA1UdDgQWBBRZrBEw
tAB2WNXj8dQ7ZOuJ34kY5DAfBgNVHSMEGDAWgBRZrBEwtAB2WNXj8dQ7ZOuJ34kY
5DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDeI9AnLW6l/39y
z96w/ldxZVFPzBRiFIsJsPHVyXlD5vUHZv/ju2jFn8TZSZR5TK0bzCEoVLp34Sho
bbS0magP82yIvCRibyoyD+TDNnZkNJwjYnikE+/oyshTSQtpkn/rDA+0Y09BUC1E
N2I6bV9pTXLFg7oah2FmqPRPzhgeYUKENgOQkrrjUCn6y0i/k374n7aftzdniSIz
2kCRVEeN9gws6CnoMPx0vr32v/JVuPV6zfdJYadgj/eFRyTNE4msd9kE82Wc46eU
YiI+LuXZ3ZMUNWGY7MK2pOUUS52JsBQ3K235dA5WaU4x8OBlY/WkNYX/eLbNs5jj
FzLmhZZ1
-----END CERTIFICATE-----

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2019 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;
public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
uninstallPackage(TEST_APP2_PKG, false);
installPackage(TEST_APP2_APK);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
uninstallPackage(TEST_APP2_PKG, true);
}
public void testOnBlockedStatusChanged_dataSaver() throws Exception {
runDeviceTests(TEST_PKG,
TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_dataSaver");
}
public void testOnBlockedStatusChanged_powerSaver() throws Exception {
runDeviceTests(TEST_PKG,
TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2016 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.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.ddmlib.Log;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.result.CollectingTestListener;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
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 java.io.FileNotFoundException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
abstract class HostsideNetworkTestCase extends DeviceTestCase implements IAbiReceiver,
IBuildReceiver {
protected static final boolean DEBUG = false;
protected static final String TAG = "HostsideNetworkTests";
protected static final String TEST_PKG = "com.android.cts.net.hostside";
protected static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
protected static final String TEST_APP2_APK = "CtsHostsideNetworkTestsApp2.apk";
private IAbi mAbi;
private IBuildInfo mCtsBuild;
@Override
public void setAbi(IAbi abi) {
mAbi = abi;
}
@Override
public void setBuild(IBuildInfo buildInfo) {
mCtsBuild = buildInfo;
}
@Override
protected void setUp() throws Exception {
super.setUp();
assertNotNull(mAbi);
assertNotNull(mCtsBuild);
uninstallPackage(TEST_PKG, false);
installPackage(TEST_APK);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
uninstallPackage(TEST_PKG, true);
}
protected void installPackage(String apk) throws FileNotFoundException,
DeviceNotAvailableException {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
assertNull(getDevice().installPackage(buildHelper.getTestFile(apk),
false /* reinstall */, true /* grantPermissions */));
}
protected void uninstallPackage(String packageName, boolean shouldSucceed)
throws DeviceNotAvailableException {
final String result = getDevice().uninstallPackage(packageName);
if (shouldSucceed) {
assertNull("uninstallPackage(" + packageName + ") failed: " + result, result);
}
}
protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException,
InterruptedException {
final String command = "cmd package list packages " + packageName;
final int max_tries = 5;
for (int i = 1; i <= max_tries; i++) {
final String result = runCommand(command);
if (result.trim().isEmpty()) {
return;
}
// 'list packages' filters by substring, so we need to iterate with the results
// and check one by one, otherwise 'com.android.cts.net.hostside' could return
// 'com.android.cts.net.hostside.app2'
boolean found = false;
for (String line : result.split("[\\r\\n]+")) {
if (line.endsWith(packageName)) {
found = true;
break;
}
}
if (!found) {
return;
}
i++;
Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
+ "); sleeping 1s before polling again");
Thread.sleep(1000);
}
fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
}
protected void runDeviceTests(String packageName, String testClassName)
throws DeviceNotAvailableException {
runDeviceTests(packageName, testClassName, null);
}
protected void runDeviceTests(String packageName, String testClassName, String methodName)
throws DeviceNotAvailableException {
RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
"androidx.test.runner.AndroidJUnitRunner", getDevice().getIDevice());
if (testClassName != null) {
if (methodName != null) {
testRunner.setMethodName(testClassName, methodName);
} else {
testRunner.setClassName(testClassName);
}
}
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<TestDescription, 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());
}
}
private static final Pattern UID_PATTERN =
Pattern.compile(".*userId=([0-9]+)$", Pattern.MULTILINE);
protected int getUid(String packageName) throws DeviceNotAvailableException {
final String output = runCommand("dumpsys package " + packageName);
final Matcher matcher = UID_PATTERN.matcher(output);
while (matcher.find()) {
final String match = matcher.group(1);
return Integer.parseInt(match);
}
throw new RuntimeException("Did not find regexp '" + UID_PATTERN + "' on adb output\n"
+ output);
}
protected String runCommand(String command) throws DeviceNotAvailableException {
Log.d(TAG, "Command: '" + command + "'");
final String output = getDevice().executeShellCommand(command);
if (DEBUG) Log.v(TAG, "Output: " + output.trim());
return output;
}
}

View File

@@ -0,0 +1,377 @@
/*
* Copyright (C) 2016 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.ddmlib.Log;
import com.android.tradefed.device.DeviceNotAvailableException;
public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
uninstallPackage(TEST_APP2_PKG, false);
installPackage(TEST_APP2_APK);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
uninstallPackage(TEST_APP2_PKG, true);
}
/**************************
* Data Saver Mode tests. *
**************************/
public void testDataSaverMode_disabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_disabled");
}
public void testDataSaverMode_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_whitelisted");
}
public void testDataSaverMode_enabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_enabled");
}
public void testDataSaverMode_blacklisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_blacklisted");
}
public void testDataSaverMode_reinstall() throws Exception {
final int oldUid = getUid(TEST_APP2_PKG);
// Make sure whitelist is revoked when package is removed
addRestrictBackgroundWhitelist(oldUid);
uninstallPackage(TEST_APP2_PKG, true);
assertPackageUninstalled(TEST_APP2_PKG);
assertRestrictBackgroundWhitelist(oldUid, false);
installPackage(TEST_APP2_APK);
final int newUid = getUid(TEST_APP2_PKG);
assertRestrictBackgroundWhitelist(oldUid, false);
assertRestrictBackgroundWhitelist(newUid, false);
}
public void testDataSaverMode_requiredWhitelistedPackages() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_requiredWhitelistedPackages");
}
public void testDataSaverMode_broadcastNotSentOnUnsupportedDevices() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testBroadcastNotSentOnUnsupportedDevices");
}
/*****************************
* Battery Saver Mode tests. *
*****************************/
public void testBatterySaverModeMetered_disabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
public void testBatterySaverModeMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
public void testBatterySaverModeMetered_enabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
public void testBatterySaverMode_reinstall() throws Exception {
if (!isDozeModeEnabled()) {
Log.w(TAG, "testBatterySaverMode_reinstall() skipped because device does not support "
+ "Doze Mode");
return;
}
addPowerSaveModeWhitelist(TEST_APP2_PKG);
uninstallPackage(TEST_APP2_PKG, true);
assertPackageUninstalled(TEST_APP2_PKG);
assertPowerSaveModeWhitelist(TEST_APP2_PKG, false);
installPackage(TEST_APP2_APK);
assertPowerSaveModeWhitelist(TEST_APP2_PKG, false);
}
public void testBatterySaverModeNonMetered_disabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
public void testBatterySaverModeNonMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
public void testBatterySaverModeNonMetered_enabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
/*******************
* App idle tests. *
*******************/
public void testAppIdleMetered_disabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
public void testAppIdleMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
public void testAppIdleMetered_tempWhitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_tempWhitelisted");
}
public void testAppIdleMetered_enabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
public void testAppIdleMetered_idleWhitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testAppIdleNetworkAccess_idleWhitelisted");
}
// TODO: currently power-save mode and idle uses the same whitelist, so this test would be
// redundant (as it would be testing the same as testBatterySaverMode_reinstall())
// public void testAppIdle_reinstall() throws Exception {
// }
public void testAppIdleNonMetered_disabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
public void testAppIdleNonMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
public void testAppIdleNonMetered_tempWhitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_tempWhitelisted");
}
public void testAppIdleNonMetered_enabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
public void testAppIdleNonMetered_idleWhitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testAppIdleNetworkAccess_idleWhitelisted");
}
public void testAppIdleNonMetered_whenCharging() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testAppIdleNetworkAccess_whenCharging");
}
public void testAppIdleMetered_whenCharging() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testAppIdleNetworkAccess_whenCharging");
}
public void testAppIdle_toast() throws Exception {
// Check that showing a toast doesn't bring an app out of standby
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testAppIdle_toast");
}
/********************
* Doze Mode tests. *
********************/
public void testDozeModeMetered_disabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
public void testDozeModeMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
public void testDozeModeMetered_enabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
public void testDozeModeMetered_enabledButWhitelistedOnNotificationAction() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
"testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
}
// TODO: currently power-save mode and idle uses the same whitelist, so this test would be
// redundant (as it would be testing the same as testBatterySaverMode_reinstall())
// public void testDozeMode_reinstall() throws Exception {
// }
public void testDozeModeNonMetered_disabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
public void testDozeModeNonMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
public void testDozeModeNonMetered_enabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
public void testDozeModeNonMetered_enabledButWhitelistedOnNotificationAction()
throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
"testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
}
/**********************
* Mixed modes tests. *
**********************/
public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDataAndBatterySaverModes_meteredNetwork");
}
public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDataAndBatterySaverModes_nonMeteredNetwork");
}
public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDozeAndBatterySaverMode_powerSaveWhitelists");
}
public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDozeAndAppIdle_powerSaveWhitelists");
}
public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testAppIdleAndDoze_tempPowerSaveWhitelists");
}
public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
}
public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDozeAndAppIdle_appIdleWhitelist");
}
public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists");
}
public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists");
}
/*******************
* Helper methods. *
*******************/
private void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
final int max_tries = 5;
boolean actual = false;
for (int i = 1; i <= max_tries; i++) {
final String output = runCommand("cmd netpolicy list restrict-background-whitelist ");
actual = output.contains(Integer.toString(uid));
if (expected == actual) {
return;
}
Log.v(TAG, "whitelist check for uid " + uid + " doesn't match yet (expected "
+ expected + ", got " + actual + "); sleeping 1s before polling again");
Thread.sleep(1000);
}
fail("whitelist check for uid " + uid + " failed: expected "
+ expected + ", got " + actual);
}
private void assertPowerSaveModeWhitelist(String packageName, boolean expected)
throws Exception {
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
assertDelayedCommand("dumpsys deviceidle whitelist =" + packageName,
Boolean.toString(expected));
}
/**
* Asserts the result of a command, wait and re-running it a couple times if necessary.
*/
private void assertDelayedCommand(String command, String expectedResult)
throws InterruptedException, DeviceNotAvailableException {
final int maxTries = 5;
for (int i = 1; i <= maxTries; i++) {
final String result = runCommand(command).trim();
if (result.equals(expectedResult)) return;
Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
+ expectedResult + "' on attempt #; sleeping 1s before polling again");
Thread.sleep(1000);
}
fail("Command '" + command + "' did not return '" + expectedResult + "' after " + maxTries
+ " attempts");
}
protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
runCommand("cmd netpolicy add restrict-background-whitelist " + uid);
assertRestrictBackgroundWhitelist(uid, true);
}
private void addPowerSaveModeWhitelist(String packageName) throws Exception {
Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
runCommand("dumpsys deviceidle whitelist +" + packageName);
assertPowerSaveModeWhitelist(packageName, true);
}
protected boolean isDozeModeEnabled() throws Exception {
final String result = runCommand("cmd deviceidle enabled deep").trim();
return result.equals("1");
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2016 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;
public class HostsideVpnTests extends HostsideNetworkTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
uninstallPackage(TEST_APP2_PKG, false);
installPackage(TEST_APP2_APK);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
uninstallPackage(TEST_APP2_PKG, true);
}
public void testDefault() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
}
public void testAppAllowed() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppAllowed");
}
public void testAppDisallowed() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppDisallowed");
}
public void testGetConnectionOwnerUidSecurity() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testGetConnectionOwnerUidSecurity");
}
public void testSetProxy() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxy");
}
public void testSetProxyDisallowedApps() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxyDisallowedApps");
}
public void testNoProxy() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testNoProxy");
}
public void testBindToNetworkWithProxy() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBindToNetworkWithProxy");
}
public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception {
runDeviceTests(
TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNoUnderlyingNetwork");
}
public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
runDeviceTests(
TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNullUnderlyingNetwork");
}
public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
runDeviceTests(
TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNonNullUnderlyingNetwork");
}
public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
runDeviceTests(
TEST_PKG, TEST_PKG + ".VpnTest", "testAlwaysMeteredVpnWithNullUnderlyingNetwork");
}
public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
runDeviceTests(
TEST_PKG,
TEST_PKG + ".VpnTest",
"testAlwaysMeteredVpnWithNonNullUnderlyingNetwork");
}
public void testB141603906() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testB141603906");
}
public void testDownloadWithDownloadManagerDisallowed() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest",
"testDownloadWithDownloadManagerDisallowed");
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2020 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.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil;
import com.android.tradefed.targetprep.ITargetPreparer;
public class NetworkPolicyTestsPreparer implements ITargetPreparer {
private ITestDevice mDevice;
private boolean mOriginalAirplaneModeEnabled;
private String mOriginalAppStandbyEnabled;
private String mOriginalBatteryStatsConstants;
private final static String KEY_STABLE_CHARGING_DELAY_MS = "battery_charged_delay_ms";
private final static int DESIRED_STABLE_CHARGING_DELAY_MS = 0;
@Override
public void setUp(TestInformation testInformation) throws DeviceNotAvailableException {
mDevice = testInformation.getDevice();
mOriginalAppStandbyEnabled = getAppStandbyEnabled();
setAppStandbyEnabled("1");
LogUtil.CLog.d("Original app_standby_enabled: " + mOriginalAppStandbyEnabled);
mOriginalBatteryStatsConstants = getBatteryStatsConstants();
setBatteryStatsConstants(
KEY_STABLE_CHARGING_DELAY_MS + "=" + DESIRED_STABLE_CHARGING_DELAY_MS);
LogUtil.CLog.d("Original battery_saver_constants: " + mOriginalBatteryStatsConstants);
mOriginalAirplaneModeEnabled = getAirplaneModeEnabled();
// Turn off airplane mode in case another test left the device in that state.
setAirplaneModeEnabled(false);
LogUtil.CLog.d("Original airplane mode state: " + mOriginalAirplaneModeEnabled);
}
@Override
public void tearDown(TestInformation testInformation, Throwable e)
throws DeviceNotAvailableException {
setAirplaneModeEnabled(mOriginalAirplaneModeEnabled);
setAppStandbyEnabled(mOriginalAppStandbyEnabled);
setBatteryStatsConstants(mOriginalBatteryStatsConstants);
}
private void setAirplaneModeEnabled(boolean enable) throws DeviceNotAvailableException {
executeCmd("cmd connectivity airplane-mode " + (enable ? "enable" : "disable"));
}
private boolean getAirplaneModeEnabled() throws DeviceNotAvailableException {
return "enabled".equals(executeCmd("cmd connectivity airplane-mode").trim());
}
private void setAppStandbyEnabled(String appStandbyEnabled) throws DeviceNotAvailableException {
if ("null".equals(appStandbyEnabled)) {
executeCmd("settings delete global app_standby_enabled");
} else {
executeCmd("settings put global app_standby_enabled " + appStandbyEnabled);
}
}
private String getAppStandbyEnabled() throws DeviceNotAvailableException {
return executeCmd("settings get global app_standby_enabled").trim();
}
private void setBatteryStatsConstants(String batteryStatsConstants)
throws DeviceNotAvailableException {
executeCmd("settings put global battery_stats_constants \"" + batteryStatsConstants + "\"");
}
private String getBatteryStatsConstants() throws DeviceNotAvailableException {
return executeCmd("settings get global battery_stats_constants");
}
private String executeCmd(String cmd) throws DeviceNotAvailableException {
final String output = mDevice.executeShellCommand(cmd).trim();
LogUtil.CLog.d("Output for '%s': %s", cmd, output);
return output;
}
}

View File

@@ -0,0 +1,169 @@
/*
* Copyright (C) 2018 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 android.security.cts;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
import java.lang.Integer;
import java.lang.String;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
/**
* Host-side tests for values in /proc/net.
*
* These tests analyze /proc/net to verify that certain networking properties are correct.
*/
public class ProcNetTest extends DeviceTestCase implements IBuildReceiver, IDeviceTest {
private static final String SPI_TIMEOUT_SYSCTL = "/proc/sys/net/core/xfrm_acq_expires";
private static final int MIN_ACQ_EXPIRES = 3600;
// Global sysctls. Must be present and set to 1.
private static final String[] GLOBAL_SYSCTLS = {
"/proc/sys/net/ipv4/fwmark_reflect",
"/proc/sys/net/ipv6/fwmark_reflect",
"/proc/sys/net/ipv4/tcp_fwmark_accept",
};
// Per-interface IPv6 autoconf sysctls.
private static final String IPV6_SYSCTL_DIR = "/proc/sys/net/ipv6/conf";
private static final String AUTOCONF_SYSCTL = "accept_ra_rt_table";
// Expected values for MIN|MAX_PLEN.
private static final String ACCEPT_RA_RT_INFO_MIN_PLEN_STRING = "accept_ra_rt_info_min_plen";
private static final int ACCEPT_RA_RT_INFO_MIN_PLEN_VALUE = 48;
private static final String ACCEPT_RA_RT_INFO_MAX_PLEN_STRING = "accept_ra_rt_info_max_plen";
private static final int ACCEPT_RA_RT_INFO_MAX_PLEN_VALUE = 64;
// Expected values for RFC 7559 router soliciations.
// Maximum number of router solicitations to send. -1 means no limit.
private static final int IPV6_WIFI_ROUTER_SOLICITATIONS = -1;
private ITestDevice mDevice;
private IBuildInfo mBuild;
private String[] mSysctlDirs;
/**
* {@inheritDoc}
*/
@Override
public void setBuild(IBuildInfo build) {
mBuild = build;
}
/**
* {@inheritDoc}
*/
@Override
public void setDevice(ITestDevice device) {
super.setDevice(device);
mDevice = device;
}
@Override
protected void setUp() throws Exception {
super.setUp();
mSysctlDirs = getSysctlDirs();
}
private String[] getSysctlDirs() throws Exception {
String interfaceDirs[] = mDevice.executeAdbCommand("shell", "ls", "-1",
IPV6_SYSCTL_DIR).split("\n");
List<String> interfaceDirsList = new ArrayList<String>(Arrays.asList(interfaceDirs));
interfaceDirsList.remove("all");
interfaceDirsList.remove("lo");
return interfaceDirsList.toArray(new String[interfaceDirsList.size()]);
}
protected void assertLess(String sysctl, int a, int b) {
assertTrue("value of " + sysctl + ": expected < " + b + " but was: " + a, a < b);
}
protected void assertAtLeast(String sysctl, int a, int b) {
assertTrue("value of " + sysctl + ": expected >= " + b + " but was: " + a, a >= b);
}
public int readIntFromPath(String path) throws Exception {
String mode = mDevice.executeAdbCommand("shell", "stat", "-c", "%a", path).trim();
String user = mDevice.executeAdbCommand("shell", "stat", "-c", "%u", path).trim();
String group = mDevice.executeAdbCommand("shell", "stat", "-c", "%g", path).trim();
assertEquals(mode, "644");
assertEquals(user, "0");
assertEquals(group, "0");
return Integer.parseInt(mDevice.executeAdbCommand("shell", "cat", path).trim());
}
/**
* Checks that SPI default timeouts are overridden, and set to a reasonable length of time
*/
public void testMinAcqExpires() throws Exception {
int value = readIntFromPath(SPI_TIMEOUT_SYSCTL);
assertAtLeast(SPI_TIMEOUT_SYSCTL, value, MIN_ACQ_EXPIRES);
}
/**
* Checks that the sysctls for multinetwork kernel features are present and
* enabled.
*/
public void testProcSysctls() throws Exception {
for (String sysctl : GLOBAL_SYSCTLS) {
int value = readIntFromPath(sysctl);
assertEquals(sysctl, 1, value);
}
for (String interfaceDir : mSysctlDirs) {
String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + AUTOCONF_SYSCTL;
int value = readIntFromPath(path);
assertLess(path, value, 0);
}
}
/**
* Verify that accept_ra_rt_info_{min,max}_plen exists and is set to the expected value
*/
public void testAcceptRaRtInfoMinMaxPlen() throws Exception {
for (String interfaceDir : mSysctlDirs) {
String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_min_plen";
int value = readIntFromPath(path);
assertEquals(path, value, ACCEPT_RA_RT_INFO_MIN_PLEN_VALUE);
path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_max_plen";
value = readIntFromPath(path);
assertEquals(path, value, ACCEPT_RA_RT_INFO_MAX_PLEN_VALUE);
}
}
/**
* Verify that router_solicitations exists and is set to the expected value
* and verify that router_solicitation_max_interval exists and is in an acceptable interval.
*/
public void testRouterSolicitations() throws Exception {
for (String interfaceDir : mSysctlDirs) {
String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitations";
int value = readIntFromPath(path);
assertEquals(IPV6_WIFI_ROUTER_SOLICITATIONS, value);
path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitation_max_interval";
int interval = readIntFromPath(path);
final int lowerBoundSec = 15 * 60;
final int upperBoundSec = 60 * 60;
assertTrue(lowerBoundSec <= interval);
assertTrue(interval <= upperBoundSec);
}
}
}

84
tests/cts/net/Android.bp Normal file
View File

@@ -0,0 +1,84 @@
// Copyright (C) 2008 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.
java_defaults {
name: "CtsNetTestCasesDefaults",
defaults: ["cts_defaults"],
// Include both the 32 and 64 bit versions
compile_multilib: "both",
libs: [
"voip-common",
"android.test.base",
],
jni_libs: [
"libcts_jni",
"libnativedns_jni",
"libnativemultinetwork_jni",
"libnativehelper_compat_libc++",
],
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
jarjar_rules: "jarjar-rules-shared.txt",
static_libs: [
"FrameworksNetCommonTests",
"TestNetworkStackLib",
"core-tests-support",
"cts-net-utils",
"ctstestrunner-axt",
"junit",
"junit-params",
"net-utils-framework-common",
"truth-prebuilt",
],
// uncomment when b/13249961 is fixed
// sdk_version: "current",
platform_apis: true,
}
// Networking CTS tests for development and release. These tests always target the platform SDK
// version, and are subject to all the restrictions appropriate to that version. Before SDK
// finalization, these tests have a min_sdk_version of 10000, and cannot be installed on release
// devices.
android_test {
name: "CtsNetTestCases",
defaults: ["CtsNetTestCasesDefaults"],
test_suites: [
"cts",
"general-tests",
],
test_config_template: "AndroidTestTemplate.xml",
}
// Networking CTS tests that target the latest released SDK. These tests can be installed on release
// devices at any point in the Android release cycle and are useful for qualifying mainline modules
// on release devices.
android_test {
name: "CtsNetTestCasesLatestSdk",
defaults: ["CtsNetTestCasesDefaults"],
jni_uses_sdk_apis: true,
min_sdk_version: "29",
target_sdk_version: "30",
test_suites: [
"general-tests",
"mts",
],
test_config_template: "AndroidTestTemplate.xml",
}

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2007 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="android.net.cts"
android:targetSandboxVersion="2">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<!-- This test also uses signature permissions through adopting the shell identity.
The permissions acquired that way include (probably not exhaustive) :
android.permission.MANAGE_TEST_NETWORKS
-->
<application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.net.cts"
android:label="CTS tests of android.net">
<meta-data android:name="listener"
android:value="com.android.cts.runner.CtsTestRunListener" />
</instrumentation>
</manifest>

View File

@@ -0,0 +1,35 @@
<!-- Copyright (C) 2015 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.
-->
<configuration description="Test config for {MODULE}">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="{MODULE}.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.cts" />
<option name="runtime-hint" value="9m4s" />
<option name="hidden-api-checks" value="false" />
<option name="isolated-storage" value="false" />
</test>
</configuration>

3
tests/cts/net/OWNERS Normal file
View File

@@ -0,0 +1,3 @@
# Bug component: 31808
lorenzo@google.com
satk@google.com

View File

@@ -0,0 +1,23 @@
{
// TODO: move to mainline-presubmit once supported
"presubmit": [
{
"name": "CtsNetTestCasesLatestSdk",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
}
]
}
],
"mainline-presubmit": [
{
"name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
}
]
}
]
}

View File

@@ -0,0 +1,51 @@
// Copyright (C) 2019 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.
android_test {
name: "CtsNetApi23TestCases",
defaults: ["cts_defaults"],
// Include both the 32 and 64 bit versions
compile_multilib: "both",
libs: [
"android.test.base",
],
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
static_libs: [
"core-tests-support",
"compatibility-device-util-axt",
"cts-net-utils",
"ctstestrunner-axt",
"ctstestserver",
"mockwebserver",
"junit",
"junit-params",
"truth-prebuilt",
],
platform_apis: true,
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
}

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2019 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="android.net.cts.api23test">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
<receiver android:name=".ConnectivityReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.net.cts.api23test"
android:label="CTS tests of android.net">
<meta-data android:name="listener"
android:value="com.android.cts.runner.CtsTestRunListener" />
</instrumentation>
</manifest>

View File

@@ -0,0 +1,31 @@
<!-- Copyright (C) 2019 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.
-->
<configuration description="Config for CTS Net API23 test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsNetApi23TestCases.apk" />
<option name="test-file-name" value="CtsNetTestAppForApi23.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.cts.api23test" />
<option name="hidden-api-checks" value="false" />
</test>
</configuration>

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2019 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 android.net.cts.api23test;
import static android.content.pm.PackageManager.FEATURE_WIFI;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.cts.util.CtsNetUtils;
import android.os.Looper;
import android.test.AndroidTestCase;
import android.util.Log;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class ConnectivityManagerApi23Test extends AndroidTestCase {
private static final String TAG = ConnectivityManagerApi23Test.class.getSimpleName();
private static final int SEND_BROADCAST_TIMEOUT = 30000;
// Intent string to get the number of wifi CONNECTIVITY_ACTION callbacks the test app has seen
public static final String GET_WIFI_CONNECTIVITY_ACTION_COUNT =
"android.net.cts.appForApi23.getWifiConnectivityActionCount";
// Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
private Context mContext;
private PackageManager mPackageManager;
private CtsNetUtils mCtsNetUtils;
@Override
protected void setUp() throws Exception {
super.setUp();
Looper.prepare();
mContext = getContext();
mPackageManager = mContext.getPackageManager();
mCtsNetUtils = new CtsNetUtils(mContext);
}
/**
* Tests reporting of connectivity changed.
*/
public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
return;
}
ConnectivityReceiver.prepare();
mCtsNetUtils.toggleWifi();
// The connectivity broadcast has been sent; push through a terminal broadcast
// to wait for in the receive to confirm it didn't see the connectivity change.
Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
finalIntent.setClass(mContext, ConnectivityReceiver.class);
mContext.sendBroadcast(finalIntent);
assertFalse(ConnectivityReceiver.waitForBroadcast());
}
public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
throws InterruptedException {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot"
+ "execute unless device supports WiFi");
return;
}
mContext.startActivity(new Intent()
.setComponent(new ComponentName("android.net.cts.appForApi23",
"android.net.cts.appForApi23.ConnectivityListeningActivity"))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Thread.sleep(200);
mCtsNetUtils.toggleWifi();
Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
getConnectivityCount, SEND_BROADCAST_TIMEOUT));
}
public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
return;
}
ConnectivityReceiver.prepare();
ConnectivityReceiver receiver = new ConnectivityReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(receiver, filter);
mCtsNetUtils.toggleWifi();
Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
finalIntent.setClass(mContext, ConnectivityReceiver.class);
mContext.sendBroadcast(finalIntent);
assertTrue(ConnectivityReceiver.waitForBroadcast());
}
private int sendOrderedBroadcastAndReturnResultCode(
Intent intent, int timeoutMs) throws InterruptedException {
final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
result.offer(getResultCode());
}
}, null, 0, null, null);
Integer resultCode = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
assertNotNull("Timed out (more than " + timeoutMs +
" milliseconds) waiting for result code for broadcast", resultCode);
return resultCode;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2016 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 android.net.cts.api23test;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ConnectivityReceiver extends BroadcastReceiver {
static boolean sReceivedConnectivity;
static boolean sReceivedFinal;
static CountDownLatch sLatch;
static void prepare() {
synchronized (ConnectivityReceiver.class) {
sReceivedConnectivity = sReceivedFinal = false;
sLatch = new CountDownLatch(1);
}
}
static boolean waitForBroadcast() {
try {
sLatch.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
synchronized (ConnectivityReceiver.class) {
sLatch = null;
if (!sReceivedFinal) {
throw new IllegalStateException("Never received final broadcast");
}
return sReceivedConnectivity;
}
}
static final String FINAL_ACTION = "android.net.cts.action.FINAL";
@Override
public void onReceive(Context context, Intent intent) {
Log.i("ConnectivityReceiver", "Received: " + intent.getAction());
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
sReceivedConnectivity = true;
} else if (FINAL_ACTION.equals(intent.getAction())) {
sReceivedFinal = true;
if (sLatch != null) {
sLatch.countDown();
}
}
}
}

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2016 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.
android_test {
name: "CtsNetTestAppForApi23",
defaults: ["cts_defaults"],
// Include both the 32 and 64 bit versions
compile_multilib: "both",
srcs: ["src/**/*.java"],
sdk_version: "23",
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
}

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2016 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="android.net.cts.appForApi23">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application>
<receiver android:name=".ConnectivityReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="android.net.cts.appForApi23.getWifiConnectivityActionCount" />
</intent-filter>
</receiver>
<activity android:name=".ConnectivityListeningActivity"
android:label="ConnectivityListeningActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016 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 android.net.cts.appForApi23;
import android.app.Activity;
// Stub activity used to start the app
public class ConnectivityListeningActivity extends Activity {
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2016 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 android.net.cts.appForApi23;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
public class ConnectivityReceiver extends BroadcastReceiver {
public static String GET_WIFI_CONNECTIVITY_ACTION_COUNT =
"android.net.cts.appForApi23.getWifiConnectivityActionCount";
private static int sWifiConnectivityActionCount = 0;
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
int networkType = intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, 0);
if (networkType == ConnectivityManager.TYPE_WIFI) {
sWifiConnectivityActionCount++;
}
}
if (GET_WIFI_CONNECTIVITY_ACTION_COUNT.equals(intent.getAction())) {
setResultCode(sWifiConnectivityActionCount);
}
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright (C) 2018 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.
*/
-->
<!-- This test config file is for NetworkWatchlistTest tests -->
<watchlist-config>
<sha256-domain>
</sha256-domain>
<sha256-ip>
</sha256-ip>
<crc32-domain>
</crc32-domain>
<crc32-ip>
</crc32-ip>
</watchlist-config>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright (C) 2018 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.
*/
-->
<!-- This test config file just contains some random hashes for testing
ConnectivityManager.getWatchlistConfigHash() -->
<watchlist-config>
<sha256-domain>
<hash>F0905DA7549614957B449034C281EF7BDEFDBC2B6E050AD1E78D6DE18FBD0D5F</hash>
</sha256-domain>
<sha256-ip>
<hash>18DD41C9F2E8E4879A1575FB780514EF33CF6E1F66578C4AE7CCA31F49B9F2EC</hash>
</sha256-ip>
<crc32-domain>
<hash>AAAAAAAA</hash>
</crc32-domain>
<crc32-ip>
<hash>BBBBBBBB</hash>
</crc32-ip>
</watchlist-config>

View File

@@ -0,0 +1,2 @@
# Module library in frameworks/libs/net
rule com.android.net.module.util.** android.net.cts.util.@1

View File

@@ -0,0 +1,51 @@
// Copyright (C) 2013 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.
cc_library_shared {
name: "libnativedns_jni",
srcs: ["NativeDnsJni.c"],
sdk_version: "current",
shared_libs: [
"libnativehelper_compat_libc++",
"liblog",
],
stl: "libc++_static",
cflags: [
"-Wall",
"-Werror",
"-Wno-unused-parameter",
],
}
cc_library_shared {
name: "libnativemultinetwork_jni",
srcs: ["NativeMultinetworkJni.cpp"],
sdk_version: "current",
cflags: [
"-Wall",
"-Werror",
"-Wno-format",
],
shared_libs: [
"libandroid",
"libnativehelper_compat_libc++",
"liblog",
],
stl: "libc++_static",
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright (C) 2010 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.
*/
#include <arpa/inet.h>
#include <jni.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <android/log.h>
#define LOG_TAG "NativeDns-JNI"
#define LOGD(fmt, ...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)
const char *GoogleDNSIpV4Address="8.8.8.8";
const char *GoogleDNSIpV4Address2="8.8.4.4";
const char *GoogleDNSIpV6Address="2001:4860:4860::8888";
const char *GoogleDNSIpV6Address2="2001:4860:4860::8844";
JNIEXPORT jboolean Java_android_net_cts_DnsTest_testNativeDns(JNIEnv* env, jclass class)
{
const char *node = "www.google.com";
char *service = NULL;
struct addrinfo *answer;
int res = getaddrinfo(node, service, NULL, &answer);
LOGD("getaddrinfo(www.google.com) gave res=%d (%s)", res, gai_strerror(res));
if (res != 0) return JNI_FALSE;
// check for v4 & v6
{
int foundv4 = 0;
int foundv6 = 0;
struct addrinfo *current = answer;
while (current != NULL) {
char buf[256];
if (current->ai_addr->sa_family == AF_INET) {
inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
buf, sizeof(buf));
foundv4 = 1;
LOGD(" %s", buf);
} else if (current->ai_addr->sa_family == AF_INET6) {
inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
buf, sizeof(buf));
foundv6 = 1;
LOGD(" %s", buf);
}
current = current->ai_next;
}
freeaddrinfo(answer);
answer = NULL;
if (foundv4 != 1 && foundv6 != 1) {
LOGD("getaddrinfo(www.google.com) didn't find either v4 or v6 address");
return JNI_FALSE;
}
}
node = "ipv6.google.com";
res = getaddrinfo(node, service, NULL, &answer);
LOGD("getaddrinfo(ipv6.google.com) gave res=%d", res);
if (res != 0) return JNI_FALSE;
{
int foundv4 = 0;
int foundv6 = 0;
struct addrinfo *current = answer;
while (current != NULL) {
char buf[256];
if (current->ai_addr->sa_family == AF_INET) {
inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
buf, sizeof(buf));
LOGD(" %s", buf);
foundv4 = 1;
} else if (current->ai_addr->sa_family == AF_INET6) {
inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
buf, sizeof(buf));
LOGD(" %s", buf);
foundv6 = 1;
}
current = current->ai_next;
}
freeaddrinfo(answer);
answer = NULL;
if (foundv4 == 1 || foundv6 != 1) {
LOGD("getaddrinfo(ipv6.google.com) didn't find only v6");
return JNI_FALSE;
}
}
// getnameinfo
struct sockaddr_in sa4;
sa4.sin_family = AF_INET;
sa4.sin_port = 0;
inet_pton(AF_INET, GoogleDNSIpV4Address, &(sa4.sin_addr));
struct sockaddr_in6 sa6;
sa6.sin6_family = AF_INET6;
sa6.sin6_port = 0;
sa6.sin6_flowinfo = 0;
sa6.sin6_scope_id = 0;
inet_pton(AF_INET6, GoogleDNSIpV6Address2, &(sa6.sin6_addr));
char buf[NI_MAXHOST];
int flags = NI_NAMEREQD;
res = getnameinfo((const struct sockaddr*)&sa4, sizeof(sa4), buf, sizeof(buf), NULL, 0, flags);
if (res != 0) {
LOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV4Address, res,
gai_strerror(res));
return JNI_FALSE;
}
if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
LOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
GoogleDNSIpV4Address, buf);
return JNI_FALSE;
}
memset(buf, 0, sizeof(buf));
res = getnameinfo((const struct sockaddr*)&sa6, sizeof(sa6), buf, sizeof(buf), NULL, 0, flags);
if (res != 0) {
LOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV6Address2,
res, gai_strerror(res));
return JNI_FALSE;
}
if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
LOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
GoogleDNSIpV6Address2, buf);
return JNI_FALSE;
}
// gethostbyname
struct hostent *my_hostent = gethostbyname("www.youtube.com");
if (my_hostent == NULL) {
LOGD("gethostbyname(www.youtube.com) gave null response");
return JNI_FALSE;
}
if ((my_hostent->h_addr_list == NULL) || (*my_hostent->h_addr_list == NULL)) {
LOGD("gethostbyname(www.youtube.com) gave 0 addresses");
return JNI_FALSE;
}
{
char **current = my_hostent->h_addr_list;
while (*current != NULL) {
char buf[256];
inet_ntop(my_hostent->h_addrtype, *current, buf, sizeof(buf));
LOGD("gethostbyname(www.youtube.com) gave %s", buf);
current++;
}
}
// gethostbyaddr
char addr6[16];
inet_pton(AF_INET6, GoogleDNSIpV6Address, addr6);
my_hostent = gethostbyaddr(addr6, sizeof(addr6), AF_INET6);
if (my_hostent == NULL) {
LOGD("gethostbyaddr(%s (GoogleDNS) ) gave null response", GoogleDNSIpV6Address);
return JNI_FALSE;
}
LOGD("gethostbyaddr(%s (GoogleDNS) ) gave %s for name", GoogleDNSIpV6Address,
my_hostent->h_name ? my_hostent->h_name : "null");
if (my_hostent->h_name == NULL) return JNI_FALSE;
return JNI_TRUE;
}

View File

@@ -0,0 +1,515 @@
/*
* Copyright (C) 2019 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.
*/
#define LOG_TAG "MultinetworkApiTest"
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <errno.h>
#include <inttypes.h>
#include <jni.h>
#include <netdb.h>
#include <poll.h> /* poll */
#include <resolv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <string>
#include <android/log.h>
#include <android/multinetwork.h>
#include <nativehelper/JNIHelp.h>
#define LOGD(fmt, ...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)
#define EXPECT_GE(env, actual, expected, msg) \
do { \
if (actual < expected) { \
jniThrowExceptionFmt(env, "java/lang/AssertionError", \
"%s:%d: %s EXPECT_GE: expected %d, got %d", \
__FILE__, __LINE__, msg, expected, actual); \
} \
} while (0)
#define EXPECT_GT(env, actual, expected, msg) \
do { \
if (actual <= expected) { \
jniThrowExceptionFmt(env, "java/lang/AssertionError", \
"%s:%d: %s EXPECT_GT: expected %d, got %d", \
__FILE__, __LINE__, msg, expected, actual); \
} \
} while (0)
#define EXPECT_EQ(env, expected, actual, msg) \
do { \
if (actual != expected) { \
jniThrowExceptionFmt(env, "java/lang/AssertionError", \
"%s:%d: %s EXPECT_EQ: expected %d, got %d", \
__FILE__, __LINE__, msg, expected, actual); \
} \
} while (0)
static const int MAXPACKET = 8 * 1024;
static const int TIMEOUT_MS = 15000;
static const char kHostname[] = "connectivitycheck.android.com";
static const char kNxDomainName[] = "test1-nx.metric.gstatic.com";
static const char kGoogleName[] = "www.google.com";
int makeQuery(const char* name, int qtype, uint8_t* buf, size_t buflen) {
return res_mkquery(ns_o_query, name, ns_c_in, qtype, NULL, 0, NULL, buf, buflen);
}
int getAsyncResponse(JNIEnv* env, int fd, int timeoutMs, int* rcode, uint8_t* buf, size_t bufLen) {
struct pollfd wait_fd = { .fd = fd, .events = POLLIN };
poll(&wait_fd, 1, timeoutMs);
if (wait_fd.revents & POLLIN) {
int n = android_res_nresult(fd, rcode, buf, bufLen);
// Verify that android_res_nresult() closed the fd
char dummy;
EXPECT_EQ(env, -1, read(fd, &dummy, sizeof(dummy)), "res_nresult check for closing fd");
EXPECT_EQ(env, EBADF, errno, "res_nresult check for errno");
return n;
}
return -ETIMEDOUT;
}
int extractIpAddressAnswers(uint8_t* buf, size_t bufLen, int family) {
ns_msg handle;
if (ns_initparse((const uint8_t*) buf, bufLen, &handle) < 0) {
return -errno;
}
const int ancount = ns_msg_count(handle, ns_s_an);
// Answer count = 0 is valid(e.g. response of query with root)
if (!ancount) {
return 0;
}
ns_rr rr;
bool hasValidAns = false;
for (int i = 0; i < ancount; i++) {
if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
// If there is no valid answer, test will fail.
continue;
}
const uint8_t* rdata = ns_rr_rdata(rr);
char buffer[INET6_ADDRSTRLEN];
if (inet_ntop(family, (const char*) rdata, buffer, sizeof(buffer)) == NULL) {
return -errno;
}
hasValidAns = true;
}
return hasValidAns ? 0 : -EBADMSG;
}
int expectAnswersValid(JNIEnv* env, int fd, int family, int expectedRcode) {
int rcode = -1;
uint8_t buf[MAXPACKET] = {};
int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
if (res < 0) {
return res;
}
EXPECT_EQ(env, expectedRcode, rcode, "rcode is not expected");
if (expectedRcode == ns_r_noerror && res > 0) {
return extractIpAddressAnswers(buf, res, family);
}
return 0;
}
int expectAnswersNotValid(JNIEnv* env, int fd, int expectedErrno) {
int rcode = -1;
uint8_t buf[MAXPACKET] = {};
int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
if (res != expectedErrno) {
LOGD("res:%d, expectedErrno = %d", res, expectedErrno);
return (res > 0) ? -EREMOTEIO : res;
}
return 0;
}
extern "C"
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNqueryCheck(
JNIEnv* env, jclass, jlong nethandle) {
net_handle_t handle = (net_handle_t) nethandle;
// V4
int fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_a, 0);
EXPECT_GE(env, fd, 0, "v4 res_nquery");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
"v4 res_nquery check answers");
// V6
fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_aaaa, 0);
EXPECT_GE(env, fd, 0, "v6 res_nquery");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
"v6 res_nquery check answers");
}
extern "C"
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNsendCheck(
JNIEnv* env, jclass, jlong nethandle) {
net_handle_t handle = (net_handle_t) nethandle;
// V4
uint8_t buf1[MAXPACKET] = {};
int len1 = makeQuery(kGoogleName, ns_t_a, buf1, sizeof(buf1));
EXPECT_GT(env, len1, 0, "v4 res_mkquery 1st");
uint8_t buf2[MAXPACKET] = {};
int len2 = makeQuery(kHostname, ns_t_a, buf2, sizeof(buf2));
EXPECT_GT(env, len2, 0, "v4 res_mkquery 2nd");
int fd1 = android_res_nsend(handle, buf1, len1, 0);
EXPECT_GE(env, fd1, 0, "v4 res_nsend 1st");
int fd2 = android_res_nsend(handle, buf2, len2, 0);
EXPECT_GE(env, fd2, 0, "v4 res_nsend 2nd");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET, ns_r_noerror),
"v4 res_nsend 2nd check answers");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET, ns_r_noerror),
"v4 res_nsend 1st check answers");
// V6
memset(buf1, 0, sizeof(buf1));
memset(buf2, 0, sizeof(buf2));
len1 = makeQuery(kGoogleName, ns_t_aaaa, buf1, sizeof(buf1));
EXPECT_GT(env, len1, 0, "v6 res_mkquery 1st");
len2 = makeQuery(kHostname, ns_t_aaaa, buf2, sizeof(buf2));
EXPECT_GT(env, len2, 0, "v6 res_mkquery 2nd");
fd1 = android_res_nsend(handle, buf1, len1, 0);
EXPECT_GE(env, fd1, 0, "v6 res_nsend 1st");
fd2 = android_res_nsend(handle, buf2, len2, 0);
EXPECT_GE(env, fd2, 0, "v6 res_nsend 2nd");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET6, ns_r_noerror),
"v6 res_nsend 2nd check answers");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET6, ns_r_noerror),
"v6 res_nsend 1st check answers");
}
extern "C"
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNnxDomainCheck(
JNIEnv* env, jclass, jlong nethandle) {
net_handle_t handle = (net_handle_t) nethandle;
// res_nquery V4 NXDOMAIN
int fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_a, 0);
EXPECT_GE(env, fd, 0, "v4 res_nquery NXDOMAIN");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
"v4 res_nquery NXDOMAIN check answers");
// res_nquery V6 NXDOMAIN
fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_aaaa, 0);
EXPECT_GE(env, fd, 0, "v6 res_nquery NXDOMAIN");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET6, ns_r_nxdomain),
"v6 res_nquery NXDOMAIN check answers");
uint8_t buf[MAXPACKET] = {};
// res_nsend V4 NXDOMAIN
int len = makeQuery(kNxDomainName, ns_t_a, buf, sizeof(buf));
EXPECT_GT(env, len, 0, "v4 res_mkquery NXDOMAIN");
fd = android_res_nsend(handle, buf, len, 0);
EXPECT_GE(env, fd, 0, "v4 res_nsend NXDOMAIN");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
"v4 res_nsend NXDOMAIN check answers");
// res_nsend V6 NXDOMAIN
memset(buf, 0, sizeof(buf));
len = makeQuery(kNxDomainName, ns_t_aaaa, buf, sizeof(buf));
EXPECT_GT(env, len, 0, "v6 res_mkquery NXDOMAIN");
fd = android_res_nsend(handle, buf, len, 0);
EXPECT_GE(env, fd, 0, "v6 res_nsend NXDOMAIN");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET6, ns_r_nxdomain),
"v6 res_nsend NXDOMAIN check answers");
}
extern "C"
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNcancelCheck(
JNIEnv* env, jclass, jlong nethandle) {
net_handle_t handle = (net_handle_t) nethandle;
int fd = android_res_nquery(handle, kGoogleName, ns_c_in, ns_t_a, 0);
errno = 0;
android_res_cancel(fd);
int err = errno;
EXPECT_EQ(env, 0, err, "res_cancel");
// DO NOT call cancel or result with the same fd more than once,
// otherwise it will hit fdsan double-close fd.
}
extern "C"
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNapiMalformedCheck(
JNIEnv* env, jclass, jlong nethandle) {
net_handle_t handle = (net_handle_t) nethandle;
// It is the equivalent of "dig . a", Query with an empty name.
int fd = android_res_nquery(handle, "", ns_c_in, ns_t_a, 0);
EXPECT_GE(env, fd, 0, "res_nquery root");
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
"res_nquery root check answers");
// Label limit 63
std::string exceedingLabelQuery = "www." + std::string(70, 'g') + ".com";
// Name limit 255
std::string exceedingDomainQuery = "www." + std::string(255, 'g') + ".com";
fd = android_res_nquery(handle, exceedingLabelQuery.c_str(), ns_c_in, ns_t_a, 0);
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingLabelQuery");
fd = android_res_nquery(handle, exceedingDomainQuery.c_str(), ns_c_in, ns_t_aaaa, 0);
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingDomainQuery");
uint8_t buf[10] = {};
// empty BLOB
fd = android_res_nsend(handle, buf, 10, 0);
EXPECT_GE(env, fd, 0, "res_nsend empty BLOB");
EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
"res_nsend empty BLOB check answers");
uint8_t largeBuf[2 * MAXPACKET] = {};
// A buffer larger than 8KB
fd = android_res_nsend(handle, largeBuf, sizeof(largeBuf), 0);
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend buffer larger than 8KB");
// 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
// commands to 4096 bytes.
fd = android_res_nsend(handle, largeBuf, 5000, 0);
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0");
// 500 bytes filled with 0
fd = android_res_nsend(handle, largeBuf, 500, 0);
EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0");
EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
"res_nsend 500 bytes filled with 0 check answers");
// 5000 bytes filled with 0xFF
uint8_t ffBuf[5001] = {};
memset(ffBuf, 0xFF, sizeof(ffBuf));
ffBuf[5000] = '\0';
fd = android_res_nsend(handle, ffBuf, sizeof(ffBuf), 0);
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0xFF");
// 500 bytes filled with 0xFF
ffBuf[500] = '\0';
fd = android_res_nsend(handle, ffBuf, 501, 0);
EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0xFF");
EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
"res_nsend 500 bytes filled with 0xFF check answers");
}
extern "C"
JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runGetaddrinfoCheck(
JNIEnv*, jclass, jlong nethandle) {
net_handle_t handle = (net_handle_t) nethandle;
struct addrinfo *res = NULL;
errno = 0;
int rval = android_getaddrinfofornetwork(handle, kHostname, NULL, NULL, &res);
const int saved_errno = errno;
freeaddrinfo(res);
LOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
handle, kHostname, rval, saved_errno);
return rval == 0 ? 0 : -saved_errno;
}
extern "C"
JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetprocnetwork(
JNIEnv*, jclass, jlong nethandle) {
net_handle_t handle = (net_handle_t) nethandle;
errno = 0;
int rval = android_setprocnetwork(handle);
const int saved_errno = errno;
LOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
handle, rval, saved_errno);
return rval == 0 ? 0 : -saved_errno;
}
extern "C"
JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetsocknetwork(
JNIEnv*, jclass, jlong nethandle) {
net_handle_t handle = (net_handle_t) nethandle;
errno = 0;
int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0) {
LOGD("socket() failed, errno=%d", errno);
return -errno;
}
errno = 0;
int rval = android_setsocknetwork(handle, fd);
const int saved_errno = errno;
LOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
handle, fd, rval, saved_errno);
close(fd);
return rval == 0 ? 0 : -saved_errno;
}
// Use sizeof("x") - 1 because we need a compile-time constant, and strlen("x")
// isn't guaranteed to fold to a constant.
static const int kSockaddrStrLen = INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
void sockaddr_ntop(const struct sockaddr *sa, socklen_t salen, char *dst, const size_t size) {
char addrstr[INET6_ADDRSTRLEN];
char portstr[sizeof("65535")];
char buf[kSockaddrStrLen+1];
int ret = getnameinfo(sa, salen,
addrstr, sizeof(addrstr),
portstr, sizeof(portstr),
NI_NUMERICHOST | NI_NUMERICSERV);
if (ret == 0) {
snprintf(buf, sizeof(buf),
(sa->sa_family == AF_INET6) ? "[%s]:%s" : "%s:%s",
addrstr, portstr);
} else {
sprintf(buf, "???");
}
strlcpy(dst, buf, size);
}
extern "C"
JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runDatagramCheck(
JNIEnv*, jclass, jlong nethandle) {
const struct addrinfo kHints = {
.ai_flags = AI_ADDRCONFIG,
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_DGRAM,
.ai_protocol = IPPROTO_UDP,
};
struct addrinfo *res = NULL;
net_handle_t handle = (net_handle_t) nethandle;
static const char kPort[] = "443";
int rval = android_getaddrinfofornetwork(handle, kHostname, kPort, &kHints, &res);
if (rval != 0) {
LOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
handle, kHostname, rval, errno);
freeaddrinfo(res);
return -errno;
}
// Rely upon getaddrinfo sorting the best destination to the front.
int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (fd < 0) {
LOGD("socket(%d, %d, %d) failed, errno=%d",
res->ai_family, res->ai_socktype, res->ai_protocol, errno);
freeaddrinfo(res);
return -errno;
}
rval = android_setsocknetwork(handle, fd);
LOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
handle, fd, rval, errno);
if (rval != 0) {
close(fd);
freeaddrinfo(res);
return -errno;
}
char addrstr[kSockaddrStrLen+1];
sockaddr_ntop(res->ai_addr, res->ai_addrlen, addrstr, sizeof(addrstr));
LOGD("Attempting connect() to %s ...", addrstr);
rval = connect(fd, res->ai_addr, res->ai_addrlen);
if (rval != 0) {
close(fd);
freeaddrinfo(res);
return -errno;
}
freeaddrinfo(res);
struct sockaddr_storage src_addr;
socklen_t src_addrlen = sizeof(src_addr);
if (getsockname(fd, (struct sockaddr *)&src_addr, &src_addrlen) != 0) {
close(fd);
return -errno;
}
sockaddr_ntop((const struct sockaddr *)&src_addr, sizeof(src_addr), addrstr, sizeof(addrstr));
LOGD("... from %s", addrstr);
// Don't let reads or writes block indefinitely.
const struct timeval timeo = { 2, 0 }; // 2 seconds
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
// For reference see:
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-invariants
uint8_t quic_packet[1200] = {
0xc0, // long header
0xaa, 0xda, 0xca, 0xca, // reserved-space version number
0x08, // destination connection ID length
0, 0, 0, 0, 0, 0, 0, 0, // 64bit connection ID
0x00, // source connection ID length
};
arc4random_buf(quic_packet + 6, 8); // random connection ID
uint8_t response[1500];
ssize_t sent, rcvd;
static const int MAX_RETRIES = 5;
int i, errnum = 0;
for (i = 0; i < MAX_RETRIES; i++) {
sent = send(fd, quic_packet, sizeof(quic_packet), 0);
if (sent < (ssize_t)sizeof(quic_packet)) {
errnum = errno;
LOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
close(fd);
return -errnum;
}
rcvd = recv(fd, response, sizeof(response), 0);
if (rcvd > 0) {
break;
} else {
errnum = errno;
LOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
i + 1, MAX_RETRIES, rcvd, errnum);
}
}
if (rcvd < 15) {
LOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
if (rcvd <= 0) {
LOGD("Does this network block UDP port %s?", kPort);
}
close(fd);
return -EPROTO;
}
int conn_id_cmp = memcmp(quic_packet + 6, response + 7, 8);
if (conn_id_cmp != 0) {
LOGD("sent and received connection IDs do not match");
close(fd);
return -EPROTO;
}
// TODO: Replace this quick 'n' dirty test with proper QUIC-capable code.
close(fd);
return 0;
}

View File

@@ -0,0 +1,41 @@
cc_defaults {
name: "dns_async_defaults",
cflags: [
"-fstack-protector-all",
"-g",
"-Wall",
"-Wextra",
"-Werror",
"-Wnullable-to-nonnull-conversion",
"-Wsign-compare",
"-Wthread-safety",
"-Wunused-parameter",
],
srcs: [
"NativeDnsAsyncTest.cpp",
],
shared_libs: [
"libandroid",
"liblog",
"libutils",
],
}
cc_test {
name: "CtsNativeNetDnsTestCases",
defaults: ["dns_async_defaults"],
multilib: {
lib32: {
suffix: "32",
},
lib64: {
suffix: "64",
},
},
test_suites: [
"cts",
"general-tests",
"mts",
],
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2018 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.
-->
<configuration description="Config for CTS Native Network dns test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
<option name="push" value="CtsNativeNetDnsTestCases->/data/local/tmp/CtsNativeNetDnsTestCases" />
<option name="append-bitness" value="true" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="CtsNativeNetDnsTestCases" />
<option name="runtime-hint" value="1m" />
</test>
</configuration>

View File

@@ -0,0 +1,257 @@
/*
* Copyright (C) 2018 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.
*/
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <error.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <poll.h> /* poll */
#include <resolv.h>
#include <string.h>
#include <sys/socket.h>
#include <android/multinetwork.h>
#include <gtest/gtest.h>
namespace {
constexpr int MAXPACKET = 8 * 1024;
constexpr int PTON_MAX = 16;
constexpr int TIMEOUT_MS = 10000;
int getAsyncResponse(int fd, int timeoutMs, int* rcode, uint8_t* buf, size_t bufLen) {
struct pollfd wait_fd[1];
wait_fd[0].fd = fd;
wait_fd[0].events = POLLIN;
short revents;
int ret;
ret = poll(wait_fd, 1, timeoutMs);
revents = wait_fd[0].revents;
if (revents & POLLIN) {
int n = android_res_nresult(fd, rcode, buf, bufLen);
// Verify that android_res_nresult() closed the fd
char dummy;
EXPECT_EQ(-1, read(fd, &dummy, sizeof dummy));
EXPECT_EQ(EBADF, errno);
return n;
}
return -1;
}
std::vector<std::string> extractIpAddressAnswers(uint8_t* buf, size_t bufLen, int ipType) {
ns_msg handle;
if (ns_initparse((const uint8_t*) buf, bufLen, &handle) < 0) {
return {};
}
const int ancount = ns_msg_count(handle, ns_s_an);
ns_rr rr;
std::vector<std::string> answers;
for (int i = 0; i < ancount; i++) {
if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
continue;
}
const uint8_t* rdata = ns_rr_rdata(rr);
char buffer[INET6_ADDRSTRLEN];
if (inet_ntop(ipType, (const char*) rdata, buffer, sizeof(buffer))) {
answers.push_back(buffer);
}
}
return answers;
}
void expectAnswersValid(int fd, int ipType, int expectedRcode) {
int rcode = -1;
uint8_t buf[MAXPACKET] = {};
int res = getAsyncResponse(fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
EXPECT_GE(res, 0);
EXPECT_EQ(rcode, expectedRcode);
if (expectedRcode == ns_r_noerror) {
auto answers = extractIpAddressAnswers(buf, res, ipType);
EXPECT_GE(answers.size(), 0U);
for (auto &answer : answers) {
char pton[PTON_MAX];
EXPECT_EQ(1, inet_pton(ipType, answer.c_str(), pton));
}
}
}
void expectAnswersNotValid(int fd, int expectedErrno) {
int rcode = -1;
uint8_t buf[MAXPACKET] = {};
int res = getAsyncResponse(fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
EXPECT_EQ(expectedErrno, res);
}
} // namespace
TEST (NativeDnsAsyncTest, Async_Query) {
// V4
int fd1 = android_res_nquery(
NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
EXPECT_GE(fd1, 0);
int fd2 = android_res_nquery(
NETWORK_UNSPECIFIED, "www.youtube.com", ns_c_in, ns_t_a, 0);
EXPECT_GE(fd2, 0);
expectAnswersValid(fd2, AF_INET, ns_r_noerror);
expectAnswersValid(fd1, AF_INET, ns_r_noerror);
// V6
fd1 = android_res_nquery(
NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_aaaa, 0);
EXPECT_GE(fd1, 0);
fd2 = android_res_nquery(
NETWORK_UNSPECIFIED, "www.youtube.com", ns_c_in, ns_t_aaaa, 0);
EXPECT_GE(fd2, 0);
expectAnswersValid(fd2, AF_INET6, ns_r_noerror);
expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
}
TEST (NativeDnsAsyncTest, Async_Send) {
// V4
uint8_t buf1[MAXPACKET] = {};
int len1 = res_mkquery(ns_o_query, "www.googleapis.com",
ns_c_in, ns_t_a, nullptr, 0, nullptr, buf1, sizeof(buf1));
EXPECT_GT(len1, 0);
uint8_t buf2[MAXPACKET] = {};
int len2 = res_mkquery(ns_o_query, "play.googleapis.com",
ns_c_in, ns_t_a, nullptr, 0, nullptr, buf2, sizeof(buf2));
EXPECT_GT(len2, 0);
int fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf1, len1, 0);
EXPECT_GE(fd1, 0);
int fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf2, len2, 0);
EXPECT_GE(fd2, 0);
expectAnswersValid(fd2, AF_INET, ns_r_noerror);
expectAnswersValid(fd1, AF_INET, ns_r_noerror);
// V6
memset(buf1, 0, sizeof(buf1));
memset(buf2, 0, sizeof(buf2));
len1 = res_mkquery(ns_o_query, "www.googleapis.com",
ns_c_in, ns_t_aaaa, nullptr, 0, nullptr, buf1, sizeof(buf1));
EXPECT_GT(len1, 0);
len2 = res_mkquery(ns_o_query, "play.googleapis.com",
ns_c_in, ns_t_aaaa, nullptr, 0, nullptr, buf2, sizeof(buf2));
EXPECT_GT(len2, 0);
fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf1, len1, 0);
EXPECT_GE(fd1, 0);
fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf2, len2, 0);
EXPECT_GE(fd2, 0);
expectAnswersValid(fd2, AF_INET6, ns_r_noerror);
expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
}
TEST (NativeDnsAsyncTest, Async_NXDOMAIN) {
uint8_t buf[MAXPACKET] = {};
int len = res_mkquery(ns_o_query, "test1-nx.metric.gstatic.com",
ns_c_in, ns_t_a, nullptr, 0, nullptr, buf, sizeof(buf));
EXPECT_GT(len, 0);
int fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf, len, ANDROID_RESOLV_NO_CACHE_LOOKUP);
EXPECT_GE(fd1, 0);
len = res_mkquery(ns_o_query, "test2-nx.metric.gstatic.com",
ns_c_in, ns_t_a, nullptr, 0, nullptr, buf, sizeof(buf));
EXPECT_GT(len, 0);
int fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf, len, ANDROID_RESOLV_NO_CACHE_LOOKUP);
EXPECT_GE(fd2, 0);
expectAnswersValid(fd2, AF_INET, ns_r_nxdomain);
expectAnswersValid(fd1, AF_INET, ns_r_nxdomain);
fd1 = android_res_nquery(
NETWORK_UNSPECIFIED, "test3-nx.metric.gstatic.com",
ns_c_in, ns_t_aaaa, ANDROID_RESOLV_NO_CACHE_LOOKUP);
EXPECT_GE(fd1, 0);
fd2 = android_res_nquery(
NETWORK_UNSPECIFIED, "test4-nx.metric.gstatic.com",
ns_c_in, ns_t_aaaa, ANDROID_RESOLV_NO_CACHE_LOOKUP);
EXPECT_GE(fd2, 0);
expectAnswersValid(fd2, AF_INET6, ns_r_nxdomain);
expectAnswersValid(fd1, AF_INET6, ns_r_nxdomain);
}
TEST (NativeDnsAsyncTest, Async_Cancel) {
int fd = android_res_nquery(
NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
errno = 0;
android_res_cancel(fd);
int err = errno;
EXPECT_EQ(err, 0);
// DO NOT call cancel or result with the same fd more than once,
// otherwise it will hit fdsan double-close fd.
}
TEST (NativeDnsAsyncTest, Async_Query_MALFORMED) {
// Empty string to create BLOB and query, we will get empty result and rcode = 0
// on DNSTLS.
int fd = android_res_nquery(
NETWORK_UNSPECIFIED, "", ns_c_in, ns_t_a, 0);
EXPECT_GE(fd, 0);
expectAnswersValid(fd, AF_INET, ns_r_noerror);
std::string exceedingLabelQuery = "www." + std::string(70, 'g') + ".com";
std::string exceedingDomainQuery = "www." + std::string(255, 'g') + ".com";
fd = android_res_nquery(NETWORK_UNSPECIFIED,
exceedingLabelQuery.c_str(), ns_c_in, ns_t_a, 0);
EXPECT_EQ(-EMSGSIZE, fd);
fd = android_res_nquery(NETWORK_UNSPECIFIED,
exceedingDomainQuery.c_str(), ns_c_in, ns_t_a, 0);
EXPECT_EQ(-EMSGSIZE, fd);
}
TEST (NativeDnsAsyncTest, Async_Send_MALFORMED) {
uint8_t buf[10] = {};
// empty BLOB
int fd = android_res_nsend(NETWORK_UNSPECIFIED, buf, 10, 0);
EXPECT_GE(fd, 0);
expectAnswersNotValid(fd, -EINVAL);
std::vector<uint8_t> largeBuf(2 * MAXPACKET, 0);
// A buffer larger than 8KB
fd = android_res_nsend(
NETWORK_UNSPECIFIED, largeBuf.data(), largeBuf.size(), 0);
EXPECT_EQ(-EMSGSIZE, fd);
// 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
// commands to 4096 bytes.
fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 5000, 0);
EXPECT_EQ(-EMSGSIZE, fd);
// 500 bytes filled with 0
fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 500, 0);
EXPECT_GE(fd, 0);
expectAnswersNotValid(fd, -EINVAL);
// 5000 bytes filled with 0xFF
std::vector<uint8_t> ffBuf(5000, 0xFF);
fd = android_res_nsend(
NETWORK_UNSPECIFIED, ffBuf.data(), ffBuf.size(), 0);
EXPECT_EQ(-EMSGSIZE, fd);
// 500 bytes filled with 0xFF
fd = android_res_nsend(NETWORK_UNSPECIFIED, ffBuf.data(), 500, 0);
EXPECT_GE(fd, 0);
expectAnswersNotValid(fd, -EINVAL);
}

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2017 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.
// Build the unit tests.
cc_test {
name: "CtsNativeNetTestCases",
compile_multilib: "both",
multilib: {
lib32: {
suffix: "32",
},
lib64: {
suffix: "64",
},
},
srcs: ["src/NativeQtaguidTest.cpp"],
shared_libs: [
"libutils",
"liblog",
],
static_libs: [
"libgtest",
"libqtaguid",
],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
],
cflags: [
"-Werror",
"-Wall",
],
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 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.
-->
<configuration description="Config for CTS Native Network xt_qtaguid test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
<option name="push" value="CtsNativeNetTestCases->/data/local/tmp/CtsNativeNetTestCases" />
<option name="append-bitness" value="true" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="CtsNativeNetTestCases" />
<option name="runtime-hint" value="1m" />
</test>
</configuration>

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2017 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.
*/
#include <arpa/inet.h>
#include <error.h>
#include <errno.h>
#include <inttypes.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <gtest/gtest.h>
#include <qtaguid/qtaguid.h>
int canAccessQtaguidFile() {
int fd = open("/proc/net/xt_qtaguid/ctrl", O_RDONLY | O_CLOEXEC);
close(fd);
return fd != -1;
}
#define SKIP_IF_QTAGUID_NOT_SUPPORTED() \
do { \
int res = canAccessQtaguidFile(); \
ASSERT_LE(0, res); \
if (!res) { \
GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n"; \
return; \
} \
} while (0)
int getCtrlSkInfo(int tag, uid_t uid, uint64_t* sk_addr, int* ref_cnt) {
FILE *fp;
fp = fopen("/proc/net/xt_qtaguid/ctrl", "r");
if (!fp)
return -ENOENT;
uint64_t full_tag = (uint64_t)tag << 32 | uid;
char pattern[40];
snprintf(pattern, sizeof(pattern), " tag=0x%" PRIx64 " (uid=%" PRIu32 ")", full_tag, uid);
size_t len;
char *line_buffer = NULL;
while(getline(&line_buffer, &len, fp) != -1) {
if (strstr(line_buffer, pattern) == NULL)
continue;
int res;
pid_t dummy_pid;
uint64_t k_tag;
uint32_t k_uid;
const int TOTAL_PARAM = 5;
res = sscanf(line_buffer, "sock=%" PRIx64 " tag=0x%" PRIx64 " (uid=%" PRIu32 ") "
"pid=%u f_count=%u", sk_addr, &k_tag, &k_uid,
&dummy_pid, ref_cnt);
if (!(res == TOTAL_PARAM && k_tag == full_tag && k_uid == uid))
return -EINVAL;
free(line_buffer);
return 0;
}
free(line_buffer);
return -ENOENT;
}
void checkNoSocketPointerLeaks(int family) {
int sockfd = socket(family, SOCK_STREAM, 0);
uid_t uid = getuid();
int tag = arc4random();
int ref_cnt;
uint64_t sk_addr;
uint64_t expect_addr = 0;
EXPECT_EQ(0, legacy_tagSocket(sockfd, tag, uid));
EXPECT_EQ(0, getCtrlSkInfo(tag, uid, &sk_addr, &ref_cnt));
EXPECT_EQ(expect_addr, sk_addr);
close(sockfd);
EXPECT_EQ(-ENOENT, getCtrlSkInfo(tag, uid, &sk_addr, &ref_cnt));
}
TEST (NativeQtaguidTest, close_socket_without_untag) {
SKIP_IF_QTAGUID_NOT_SUPPORTED();
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
uid_t uid = getuid();
int tag = arc4random();
int ref_cnt;
uint64_t dummy_sk;
EXPECT_EQ(0, legacy_tagSocket(sockfd, tag, uid));
EXPECT_EQ(0, getCtrlSkInfo(tag, uid, &dummy_sk, &ref_cnt));
EXPECT_EQ(2, ref_cnt);
close(sockfd);
EXPECT_EQ(-ENOENT, getCtrlSkInfo(tag, uid, &dummy_sk, &ref_cnt));
}
TEST (NativeQtaguidTest, close_socket_without_untag_ipv6) {
SKIP_IF_QTAGUID_NOT_SUPPORTED();
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
uid_t uid = getuid();
int tag = arc4random();
int ref_cnt;
uint64_t dummy_sk;
EXPECT_EQ(0, legacy_tagSocket(sockfd, tag, uid));
EXPECT_EQ(0, getCtrlSkInfo(tag, uid, &dummy_sk, &ref_cnt));
EXPECT_EQ(2, ref_cnt);
close(sockfd);
EXPECT_EQ(-ENOENT, getCtrlSkInfo(tag, uid, &dummy_sk, &ref_cnt));
}
TEST (NativeQtaguidTest, no_socket_addr_leak) {
SKIP_IF_QTAGUID_NOT_SUPPORTED();
checkNoSocketPointerLeaks(AF_INET);
checkNoSocketPointerLeaks(AF_INET6);
}
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2016 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 android.net.cts;
import android.content.ContentResolver;
import android.content.Context;
import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.util.Log;
import java.lang.Thread;
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
public class AirplaneModeTest extends AndroidTestCase {
private static final String TAG = "AirplaneModeTest";
private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
private static final String FEATURE_WIFI = "android.hardware.wifi";
private static final int TIMEOUT_MS = 10 * 1000;
private boolean mHasFeature;
private Context mContext;
private ContentResolver resolver;
public void setup() {
mContext= getContext();
resolver = mContext.getContentResolver();
mHasFeature = (mContext.getPackageManager().hasSystemFeature(FEATURE_BLUETOOTH)
|| mContext.getPackageManager().hasSystemFeature(FEATURE_WIFI));
}
public void testAirplaneMode() {
setup();
if (!mHasFeature) {
Log.i(TAG, "The device doesn't support network bluetooth or wifi feature");
return;
}
for (int testCount = 0; testCount < 2; testCount++) {
if (!doOneTest()) {
fail("Airplane mode failed to change in " + TIMEOUT_MS + "msec");
return;
}
}
}
private boolean doOneTest() {
boolean airplaneModeOn = isAirplaneModeOn();
setAirplaneModeOn(!airplaneModeOn);
try {
Thread.sleep(TIMEOUT_MS);
} catch (InterruptedException e) {
Log.e(TAG, "Sleep time interrupted.", e);
}
if (airplaneModeOn == isAirplaneModeOn()) {
return false;
}
return true;
}
private void setAirplaneModeOn(boolean enabling) {
// Change the system setting for airplane mode
Settings.Global.putInt(resolver, Settings.Global.AIRPLANE_MODE_ON, enabling ? 1 : 0);
}
private boolean isAirplaneModeOn() {
// Read the system setting for airplane mode
return Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2020 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 android.net.cts
import android.Manifest.permission.CONNECTIVITY_INTERNAL
import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.READ_DEVICE_CONFIG
import android.content.pm.PackageManager.FEATURE_TELEPHONY
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
import android.net.Uri
import android.net.cts.NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig
import android.net.cts.NetworkValidationTestUtil.setHttpUrlDeviceConfig
import android.net.cts.NetworkValidationTestUtil.setHttpsUrlDeviceConfig
import android.net.cts.NetworkValidationTestUtil.setUrlExpirationDeviceConfig
import android.net.cts.util.CtsNetUtils
import android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL
import android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL
import android.net.wifi.WifiManager
import android.os.Build
import android.platform.test.annotations.AppModeFull
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
import android.text.TextUtils
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.TestHttpServer
import com.android.testutils.TestHttpServer.Request
import com.android.testutils.isDevSdkInRange
import com.android.testutils.runAsShell
import fi.iki.elonen.NanoHTTPD.Response.Status
import junit.framework.AssertionFailedError
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.runner.RunWith
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import kotlin.test.Test
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
private const val TEST_HTTPS_URL_PATH = "/https_path"
private const val TEST_HTTP_URL_PATH = "/http_path"
private const val TEST_PORTAL_URL_PATH = "/portal_path"
private const val LOCALHOST_HOSTNAME = "localhost"
// Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
private const val WIFI_CONNECT_TIMEOUT_MS = 120_000L
private const val TEST_TIMEOUT_MS = 10_000L
private fun <T> CompletableFuture<T>.assertGet(timeoutMs: Long, message: String): T {
try {
return get(timeoutMs, TimeUnit.MILLISECONDS)
} catch (e: TimeoutException) {
throw AssertionFailedError(message)
}
}
@AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
@RunWith(AndroidJUnit4::class)
class CaptivePortalTest {
private val context: android.content.Context by lazy { getInstrumentation().context }
private val wm by lazy { context.getSystemService(WifiManager::class.java) }
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
private val pm by lazy { context.packageManager }
private val utils by lazy { CtsNetUtils(context) }
private val server = TestHttpServer("localhost")
@Before
fun setUp() {
runAsShell(READ_DEVICE_CONFIG) {
// Verify that the test URLs are not normally set on the device, but do not fail if the
// test URLs are set to what this test uses (URLs on localhost), in case the test was
// interrupted manually and rerun.
assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL)
assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL)
}
clearValidationTestUrlsDeviceConfig()
server.start()
}
@After
fun tearDown() {
clearValidationTestUrlsDeviceConfig()
if (pm.hasSystemFeature(FEATURE_WIFI)) {
reconnectWifi()
}
server.stop()
}
private fun assertEmptyOrLocalhostUrl(urlKey: String) {
val url = DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, urlKey)
assertTrue(TextUtils.isEmpty(url) || LOCALHOST_HOSTNAME == Uri.parse(url).host,
"$urlKey must not be set in production scenarios (current value: $url)")
}
@Test
fun testCaptivePortalIsNotDefaultNetwork() {
assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
utils.ensureWifiConnected()
utils.connectToCell()
// Have network validation use a local server that serves a HTTPS error / HTTP redirect
server.addResponse(Request(TEST_PORTAL_URL_PATH), Status.OK,
content = "Test captive portal content")
server.addResponse(Request(TEST_HTTPS_URL_PATH), Status.INTERNAL_ERROR)
server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT,
locationHeader = makeUrl(TEST_PORTAL_URL_PATH))
setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH))
setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH))
// URL expiration needs to be in the next 10 minutes
setUrlExpirationDeviceConfig(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(9))
// Wait for a captive portal to be detected on the network
val wifiNetworkFuture = CompletableFuture<Network>()
val wifiCb = object : NetworkCallback() {
override fun onCapabilitiesChanged(
network: Network,
nc: NetworkCapabilities
) {
if (nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
wifiNetworkFuture.complete(network)
}
}
}
cm.requestNetwork(NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(), wifiCb)
try {
reconnectWifi()
val network = wifiNetworkFuture.assertGet(WIFI_CONNECT_TIMEOUT_MS,
"Captive portal not detected after ${WIFI_CONNECT_TIMEOUT_MS}ms")
val wifiDefaultMessage = "Wifi should not be the default network when a captive " +
"portal was detected and another network (mobile data) can provide internet " +
"access."
assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
val startPortalAppPermission =
if (isDevSdkInRange(0, Build.VERSION_CODES.Q)) CONNECTIVITY_INTERNAL
else NETWORK_SETTINGS
runAsShell(startPortalAppPermission) { cm.startCaptivePortalApp(network) }
// Expect the portal content to be fetched at some point after detecting the portal.
// Some implementations may fetch the URL before startCaptivePortalApp is called.
assertNotNull(server.requestsRecord.poll(TEST_TIMEOUT_MS, pos = 0) {
it.path == TEST_PORTAL_URL_PATH
}, "The captive portal login page was still not fetched ${TEST_TIMEOUT_MS}ms " +
"after startCaptivePortalApp.")
assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
} finally {
cm.unregisterNetworkCallback(wifiCb)
server.stop()
// disconnectFromCell should be called after connectToCell
utils.disconnectFromCell()
}
}
/**
* Create a URL string that, when fetched, will hit the test server with the given URL [path].
*/
private fun makeUrl(path: String) = "http://localhost:${server.listeningPort}" + path
private fun reconnectWifi() {
utils.ensureWifiDisconnected(null /* wifiNetworkToCheck */)
utils.ensureWifiConnected()
}
}

View File

@@ -0,0 +1,576 @@
/*
* Copyright (C) 2020 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 android.net.cts;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_DNS_EVENTS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_TCP_METRICS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
import static android.net.ConnectivityDiagnosticsManager.persistableBundleEquals;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityDiagnosticsManager;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.Process;
import android.platform.test.annotations.AppModeFull;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.util.ArrayUtils;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.SkipPresubmit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@RunWith(DevSdkIgnoreRunner.class)
@IgnoreUpTo(Build.VERSION_CODES.Q) // ConnectivityDiagnosticsManager did not exist in Q
@AppModeFull(reason = "CHANGE_NETWORK_STATE, MANAGE_TEST_NETWORKS not grantable to instant apps")
public class ConnectivityDiagnosticsManagerTest {
private static final int CALLBACK_TIMEOUT_MILLIS = 5000;
private static final int NO_CALLBACK_INVOKED_TIMEOUT = 500;
private static final long TIMESTAMP = 123456789L;
private static final int DNS_CONSECUTIVE_TIMEOUTS = 5;
private static final int COLLECTION_PERIOD_MILLIS = 5000;
private static final int FAIL_RATE_PERCENTAGE = 100;
private static final int UNKNOWN_DETECTION_METHOD = 4;
private static final int FILTERED_UNKNOWN_DETECTION_METHOD = 0;
private static final int CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT = 5000;
private static final int DELAY_FOR_ADMIN_UIDS_MILLIS = 2000;
private static final Executor INLINE_EXECUTOR = x -> x.run();
private static final NetworkRequest TEST_NETWORK_REQUEST =
new NetworkRequest.Builder()
.addTransportType(TRANSPORT_TEST)
.removeCapability(NET_CAPABILITY_TRUSTED)
.removeCapability(NET_CAPABILITY_NOT_VPN)
.build();
private static final String SHA_256 = "SHA-256";
private static final NetworkRequest CELLULAR_NETWORK_REQUEST =
new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.build();
private static final IBinder BINDER = new Binder();
private Context mContext;
private ConnectivityManager mConnectivityManager;
private ConnectivityDiagnosticsManager mCdm;
private CarrierConfigManager mCarrierConfigManager;
private PackageManager mPackageManager;
private TelephonyManager mTelephonyManager;
// Callback used to keep TestNetworks up when there are no other outstanding NetworkRequests
// for it.
private TestNetworkCallback mTestNetworkCallback;
private Network mTestNetwork;
private ParcelFileDescriptor mTestNetworkFD;
private List<TestConnectivityDiagnosticsCallback> mRegisteredCallbacks;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
mCdm = mContext.getSystemService(ConnectivityDiagnosticsManager.class);
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
mPackageManager = mContext.getPackageManager();
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
mTestNetworkCallback = new TestNetworkCallback();
mConnectivityManager.requestNetwork(TEST_NETWORK_REQUEST, mTestNetworkCallback);
mRegisteredCallbacks = new ArrayList<>();
}
@After
public void tearDown() throws Exception {
mConnectivityManager.unregisterNetworkCallback(mTestNetworkCallback);
if (mTestNetwork != null) {
runWithShellPermissionIdentity(() -> {
final TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
tnm.teardownTestNetwork(mTestNetwork);
});
mTestNetwork = null;
}
if (mTestNetworkFD != null) {
mTestNetworkFD.close();
mTestNetworkFD = null;
}
for (TestConnectivityDiagnosticsCallback cb : mRegisteredCallbacks) {
mCdm.unregisterConnectivityDiagnosticsCallback(cb);
}
}
@Test
public void testRegisterConnectivityDiagnosticsCallback() throws Exception {
mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
mTestNetwork = mTestNetworkCallback.waitForAvailable();
final TestConnectivityDiagnosticsCallback cb =
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
final String interfaceName =
mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
cb.assertNoCallback();
}
@SkipPresubmit(reason = "Flaky: b/159718782; add to presubmit after fixing")
@Test
public void testRegisterCallbackWithCarrierPrivileges() throws Exception {
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
final int subId = SubscriptionManager.getDefaultSubscriptionId();
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
fail("Need an active subscription. Please ensure that the device has working mobile"
+ " data.");
}
final CarrierConfigReceiver carrierConfigReceiver = new CarrierConfigReceiver(subId);
mContext.registerReceiver(
carrierConfigReceiver,
new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
final TestNetworkCallback testNetworkCallback = new TestNetworkCallback();
try {
doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
subId, carrierConfigReceiver, testNetworkCallback);
} finally {
runWithShellPermissionIdentity(
() -> mCarrierConfigManager.overrideConfig(subId, null),
android.Manifest.permission.MODIFY_PHONE_STATE);
mConnectivityManager.unregisterNetworkCallback(testNetworkCallback);
mContext.unregisterReceiver(carrierConfigReceiver);
}
}
private String getCertHashForThisPackage() throws Exception {
final PackageInfo pkgInfo =
mPackageManager.getPackageInfo(
mContext.getOpPackageName(), PackageManager.GET_SIGNATURES);
final MessageDigest md = MessageDigest.getInstance(SHA_256);
final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray());
return IccUtils.bytesToHexString(certHash);
}
private void doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
int subId,
@NonNull CarrierConfigReceiver carrierConfigReceiver,
@NonNull TestNetworkCallback testNetworkCallback)
throws Exception {
final PersistableBundle carrierConfigs = new PersistableBundle();
carrierConfigs.putStringArray(
CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
new String[] {getCertHashForThisPackage()});
runWithShellPermissionIdentity(
() -> {
mCarrierConfigManager.overrideConfig(subId, carrierConfigs);
mCarrierConfigManager.notifyConfigChangedForSubId(subId);
},
android.Manifest.permission.MODIFY_PHONE_STATE);
// TODO(b/157779832): This should use android.permission.CHANGE_NETWORK_STATE. However, the
// shell does not have CHANGE_NETWORK_STATE, so use CONNECTIVITY_INTERNAL until the shell
// permissions are updated.
runWithShellPermissionIdentity(
() -> mConnectivityManager.requestNetwork(
CELLULAR_NETWORK_REQUEST, testNetworkCallback),
android.Manifest.permission.CONNECTIVITY_INTERNAL);
final Network network = testNetworkCallback.waitForAvailable();
assertNotNull(network);
assertTrue("Didn't receive broadcast for ACTION_CARRIER_CONFIG_CHANGED for subId=" + subId,
carrierConfigReceiver.waitForCarrierConfigChanged());
assertTrue("Don't have Carrier Privileges after adding cert for this package",
mTelephonyManager.createForSubscriptionId(subId).hasCarrierPrivileges());
// Wait for CarrierPrivilegesTracker to receive the ACTION_CARRIER_CONFIG_CHANGED
// broadcast. CPT then needs to update the corresponding DataConnection, which then
// updates ConnectivityService. Unfortunately, this update to the NetworkCapabilities in
// CS does not trigger NetworkCallback#onCapabilitiesChanged as changing the
// administratorUids is not a publicly visible change. In lieu of a better signal to
// detministically wait for, use Thread#sleep here.
// TODO(b/157949581): replace this Thread#sleep with a deterministic signal
Thread.sleep(DELAY_FOR_ADMIN_UIDS_MILLIS);
final TestConnectivityDiagnosticsCallback connDiagsCallback =
createAndRegisterConnectivityDiagnosticsCallback(CELLULAR_NETWORK_REQUEST);
final String interfaceName =
mConnectivityManager.getLinkProperties(network).getInterfaceName();
connDiagsCallback.expectOnConnectivityReportAvailable(
network, interfaceName, TRANSPORT_CELLULAR);
connDiagsCallback.assertNoCallback();
}
@Test
public void testRegisterDuplicateConnectivityDiagnosticsCallback() {
final TestConnectivityDiagnosticsCallback cb =
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
try {
mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
fail("Registering the same callback twice should throw an IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testUnregisterConnectivityDiagnosticsCallback() {
final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
mCdm.unregisterConnectivityDiagnosticsCallback(cb);
}
@Test
public void testUnregisterUnknownConnectivityDiagnosticsCallback() {
// Expected to silently ignore the unregister() call
mCdm.unregisterConnectivityDiagnosticsCallback(new TestConnectivityDiagnosticsCallback());
}
@Test
public void testOnConnectivityReportAvailable() throws Exception {
final TestConnectivityDiagnosticsCallback cb =
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
mTestNetwork = mTestNetworkCallback.waitForAvailable();
final String interfaceName =
mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
cb.assertNoCallback();
}
@Test
public void testOnDataStallSuspected_DnsEvents() throws Exception {
final PersistableBundle extras = new PersistableBundle();
extras.putInt(KEY_DNS_CONSECUTIVE_TIMEOUTS, DNS_CONSECUTIVE_TIMEOUTS);
verifyOnDataStallSuspected(DETECTION_METHOD_DNS_EVENTS, TIMESTAMP, extras);
}
@Test
public void testOnDataStallSuspected_TcpMetrics() throws Exception {
final PersistableBundle extras = new PersistableBundle();
extras.putInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS, COLLECTION_PERIOD_MILLIS);
extras.putInt(KEY_TCP_PACKET_FAIL_RATE, FAIL_RATE_PERCENTAGE);
verifyOnDataStallSuspected(DETECTION_METHOD_TCP_METRICS, TIMESTAMP, extras);
}
@Test
public void testOnDataStallSuspected_UnknownDetectionMethod() throws Exception {
verifyOnDataStallSuspected(
UNKNOWN_DETECTION_METHOD,
FILTERED_UNKNOWN_DETECTION_METHOD,
TIMESTAMP,
PersistableBundle.EMPTY);
}
private void verifyOnDataStallSuspected(
int detectionMethod, long timestampMillis, @NonNull PersistableBundle extras)
throws Exception {
// Input detection method is expected to match received detection method
verifyOnDataStallSuspected(detectionMethod, detectionMethod, timestampMillis, extras);
}
private void verifyOnDataStallSuspected(
int inputDetectionMethod,
int expectedDetectionMethod,
long timestampMillis,
@NonNull PersistableBundle extras)
throws Exception {
mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
mTestNetwork = mTestNetworkCallback.waitForAvailable();
final TestConnectivityDiagnosticsCallback cb =
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
final String interfaceName =
mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
runWithShellPermissionIdentity(
() -> mConnectivityManager.simulateDataStall(
inputDetectionMethod, timestampMillis, mTestNetwork, extras),
android.Manifest.permission.MANAGE_TEST_NETWORKS);
cb.expectOnDataStallSuspected(
mTestNetwork, interfaceName, expectedDetectionMethod, timestampMillis, extras);
cb.assertNoCallback();
}
@Test
public void testOnNetworkConnectivityReportedTrue() throws Exception {
verifyOnNetworkConnectivityReported(true /* hasConnectivity */);
}
@Test
public void testOnNetworkConnectivityReportedFalse() throws Exception {
verifyOnNetworkConnectivityReported(false /* hasConnectivity */);
}
private void verifyOnNetworkConnectivityReported(boolean hasConnectivity) throws Exception {
mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
mTestNetwork = mTestNetworkCallback.waitForAvailable();
final TestConnectivityDiagnosticsCallback cb =
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
// onConnectivityReportAvailable always invoked when the test network is established
final String interfaceName =
mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
cb.assertNoCallback();
mConnectivityManager.reportNetworkConnectivity(mTestNetwork, hasConnectivity);
cb.expectOnNetworkConnectivityReported(mTestNetwork, hasConnectivity);
// if hasConnectivity does not match the network's known connectivity, it will be
// revalidated which will trigger another onConnectivityReportAvailable callback.
if (!hasConnectivity) {
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
}
cb.assertNoCallback();
}
private TestConnectivityDiagnosticsCallback createAndRegisterConnectivityDiagnosticsCallback(
NetworkRequest request) {
final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
mCdm.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, cb);
mRegisteredCallbacks.add(cb);
return cb;
}
/**
* Registers a test NetworkAgent with ConnectivityService with limited capabilities, which leads
* to the Network being validated.
*/
@NonNull
private TestNetworkInterface setUpTestNetwork() throws Exception {
final int[] administratorUids = new int[] {Process.myUid()};
return callWithShellPermissionIdentity(
() -> {
final TestNetworkManager tnm =
mContext.getSystemService(TestNetworkManager.class);
final TestNetworkInterface tni = tnm.createTunInterface(new LinkAddress[0]);
tnm.setupTestNetwork(tni.getInterfaceName(), administratorUids, BINDER);
return tni;
});
}
private static class TestConnectivityDiagnosticsCallback
extends ConnectivityDiagnosticsCallback {
private final ArrayTrackRecord<Object>.ReadHead mHistory =
new ArrayTrackRecord<Object>().newReadHead();
@Override
public void onConnectivityReportAvailable(ConnectivityReport report) {
mHistory.add(report);
}
@Override
public void onDataStallSuspected(DataStallReport report) {
mHistory.add(report);
}
@Override
public void onNetworkConnectivityReported(Network network, boolean hasConnectivity) {
mHistory.add(new Pair<Network, Boolean>(network, hasConnectivity));
}
public void expectOnConnectivityReportAvailable(
@NonNull Network network, @NonNull String interfaceName) {
expectOnConnectivityReportAvailable(network, interfaceName, TRANSPORT_TEST);
}
public void expectOnConnectivityReportAvailable(
@NonNull Network network, @NonNull String interfaceName, int transportType) {
final ConnectivityReport result =
(ConnectivityReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
assertEquals(network, result.getNetwork());
final NetworkCapabilities nc = result.getNetworkCapabilities();
assertNotNull(nc);
assertTrue(nc.hasTransport(transportType));
assertNotNull(result.getLinkProperties());
assertEquals(interfaceName, result.getLinkProperties().getInterfaceName());
final PersistableBundle extras = result.getAdditionalInfo();
assertTrue(extras.containsKey(KEY_NETWORK_VALIDATION_RESULT));
final int validationResult = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
assertEquals("Network validation result is not 'valid'",
NETWORK_VALIDATION_RESULT_VALID, validationResult);
assertTrue(extras.containsKey(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK));
final int probesSucceeded = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
assertTrue("PROBES_SUCCEEDED mask not in expected range", probesSucceeded >= 0);
assertTrue(extras.containsKey(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK));
final int probesAttempted = extras.getInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK);
assertTrue("PROBES_ATTEMPTED mask not in expected range", probesAttempted >= 0);
}
public void expectOnDataStallSuspected(
@NonNull Network network,
@NonNull String interfaceName,
int detectionMethod,
long timestampMillis,
@NonNull PersistableBundle extras) {
final DataStallReport result =
(DataStallReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
assertEquals(network, result.getNetwork());
assertEquals(detectionMethod, result.getDetectionMethod());
assertEquals(timestampMillis, result.getReportTimestamp());
final NetworkCapabilities nc = result.getNetworkCapabilities();
assertNotNull(nc);
assertTrue(nc.hasTransport(TRANSPORT_TEST));
assertNotNull(result.getLinkProperties());
assertEquals(interfaceName, result.getLinkProperties().getInterfaceName());
assertTrue(persistableBundleEquals(extras, result.getStallDetails()));
}
public void expectOnNetworkConnectivityReported(
@NonNull Network network, boolean hasConnectivity) {
final Pair<Network, Boolean> result =
(Pair<Network, Boolean>) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
assertEquals(network, result.first /* network */);
assertEquals(hasConnectivity, result.second /* hasConnectivity */);
}
public void assertNoCallback() {
// If no more callbacks exist, there should be nothing left in the ReadHead
assertNull("Unexpected event in history",
mHistory.poll(NO_CALLBACK_INVOKED_TIMEOUT, x -> true));
}
}
private class CarrierConfigReceiver extends BroadcastReceiver {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final int mSubId;
CarrierConfigReceiver(int subId) {
mSubId = subId;
}
@Override
public void onReceive(Context context, Intent intent) {
if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
return;
}
final int subId =
intent.getIntExtra(
CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (mSubId != subId) return;
final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId);
if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) return;
final String[] certs =
carrierConfigs.getStringArray(
CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
try {
if (ArrayUtils.contains(certs, getCertHashForThisPackage())) {
mLatch.countDown();
}
} catch (Exception e) {
}
}
boolean waitForCarrierConfigChanged() throws Exception {
return mLatch.await(CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2008 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 android.net.cts;
import android.net.Credentials;
import android.test.AndroidTestCase;
public class CredentialsTest extends AndroidTestCase {
public void testCredentials() {
// new the Credentials instance
// Test with zero inputs
Credentials cred = new Credentials(0, 0, 0);
assertEquals(0, cred.getGid());
assertEquals(0, cred.getPid());
assertEquals(0, cred.getUid());
// Test with big integer
cred = new Credentials(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
assertEquals(Integer.MAX_VALUE, cred.getGid());
assertEquals(Integer.MAX_VALUE, cred.getPid());
assertEquals(Integer.MAX_VALUE, cred.getUid());
// Test with big negative integer
cred = new Credentials(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
assertEquals(Integer.MIN_VALUE, cred.getGid());
assertEquals(Integer.MIN_VALUE, cred.getPid());
assertEquals(Integer.MIN_VALUE, cred.getUid());
}
}

View File

@@ -0,0 +1,756 @@
/*
* Copyright (C) 2019 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 android.net.cts;
import static android.net.DnsResolver.CLASS_IN;
import static android.net.DnsResolver.FLAG_EMPTY;
import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
import static android.net.DnsResolver.TYPE_A;
import static android.net.DnsResolver.TYPE_AAAA;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.system.OsConstants.ETIMEDOUT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.ContentResolver;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.DnsResolver;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.ParseException;
import android.net.cts.util.CtsNetUtils;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
import android.system.ErrnoException;
import android.test.AndroidTestCase;
import android.util.Log;
import com.android.net.module.util.DnsPacket;
import com.android.testutils.SkipPresubmit;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
public class DnsResolverTest extends AndroidTestCase {
private static final String TAG = "DnsResolverTest";
private static final char[] HEX_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
static final String TEST_DOMAIN = "www.google.com";
static final String TEST_NX_DOMAIN = "test1-nx.metric.gstatic.com";
static final String INVALID_PRIVATE_DNS_SERVER = "invalid.google";
static final String GOOGLE_PRIVATE_DNS_SERVER = "dns.google";
static final byte[] TEST_BLOB = new byte[]{
/* Header */
0x55, 0x66, /* Transaction ID */
0x01, 0x00, /* Flags */
0x00, 0x01, /* Questions */
0x00, 0x00, /* Answer RRs */
0x00, 0x00, /* Authority RRs */
0x00, 0x00, /* Additional RRs */
/* Queries */
0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
0x00, 0x01, /* Type */
0x00, 0x01 /* Class */
};
static final int TIMEOUT_MS = 12_000;
static final int CANCEL_TIMEOUT_MS = 3_000;
static final int CANCEL_RETRY_TIMES = 5;
static final int QUERY_TIMES = 10;
static final int NXDOMAIN = 3;
private ContentResolver mCR;
private ConnectivityManager mCM;
private CtsNetUtils mCtsNetUtils;
private Executor mExecutor;
private Executor mExecutorInline;
private DnsResolver mDns;
private String mOldMode;
private String mOldDnsSpecifier;
@Override
protected void setUp() throws Exception {
super.setUp();
mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
mDns = DnsResolver.getInstance();
mExecutor = new Handler(Looper.getMainLooper())::post;
mExecutorInline = (Runnable r) -> r.run();
mCR = getContext().getContentResolver();
mCtsNetUtils = new CtsNetUtils(getContext());
mCtsNetUtils.storePrivateDnsSetting();
}
@Override
protected void tearDown() throws Exception {
mCtsNetUtils.restorePrivateDnsSetting();
super.tearDown();
}
private static String byteArrayToHexString(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; ++i) {
int b = bytes[i] & 0xFF;
hexChars[i * 2] = HEX_CHARS[b >>> 4];
hexChars[i * 2 + 1] = HEX_CHARS[b & 0x0F];
}
return new String(hexChars);
}
private Network[] getTestableNetworks() {
final ArrayList<Network> testableNetworks = new ArrayList<Network>();
for (Network network : mCM.getAllNetworks()) {
final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
if (nc != null
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
testableNetworks.add(network);
}
}
assertTrue(
"This test requires that at least one network be connected. " +
"Please ensure that the device is connected to a network.",
testableNetworks.size() >= 1);
// In order to test query with null network, add null as an element.
// Test cases which query with null network will go on default network.
testableNetworks.add(null);
return testableNetworks.toArray(new Network[0]);
}
static private void assertGreaterThan(String msg, int first, int second) {
assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
}
private static class DnsParseException extends Exception {
public DnsParseException(String msg) {
super(msg);
}
}
private static class DnsAnswer extends DnsPacket {
DnsAnswer(@NonNull byte[] data) throws DnsParseException {
super(data);
// Check QR field.(query (0), or a response (1)).
if ((mHeader.flags & (1 << 15)) == 0) {
throw new DnsParseException("Not an answer packet");
}
}
int getRcode() {
return mHeader.rcode;
}
int getANCount() {
return mHeader.getRecordCount(ANSECTION);
}
int getQDCount() {
return mHeader.getRecordCount(QDSECTION);
}
}
/**
* A query callback that ensures that the query is cancelled and that onAnswer is never
* called. If the query succeeds before it is cancelled, needRetry will return true so the
* test can retry.
*/
class VerifyCancelCallback implements DnsResolver.Callback<byte[]> {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final String mMsg;
private final CancellationSignal mCancelSignal;
private int mRcode;
private DnsAnswer mDnsAnswer;
private String mErrorMsg = null;
VerifyCancelCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
mMsg = msg;
mCancelSignal = cancel;
}
VerifyCancelCallback(@NonNull String msg) {
this(msg, null);
}
public boolean waitForAnswer(int timeout) throws InterruptedException {
return mLatch.await(timeout, TimeUnit.MILLISECONDS);
}
public boolean waitForAnswer() throws InterruptedException {
return waitForAnswer(TIMEOUT_MS);
}
public boolean needRetry() throws InterruptedException {
return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
@Override
public void onAnswer(@NonNull byte[] answer, int rcode) {
if (mCancelSignal != null && mCancelSignal.isCanceled()) {
mErrorMsg = mMsg + " should not have returned any answers";
mLatch.countDown();
return;
}
mRcode = rcode;
try {
mDnsAnswer = new DnsAnswer(answer);
} catch (ParseException | DnsParseException e) {
mErrorMsg = mMsg + e.getMessage();
mLatch.countDown();
return;
}
Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
mLatch.countDown();
}
@Override
public void onError(@NonNull DnsResolver.DnsException error) {
mErrorMsg = mMsg + error.getMessage();
mLatch.countDown();
}
private void assertValidAnswer() {
assertNull(mErrorMsg);
assertNotNull(mMsg + " No valid answer", mDnsAnswer);
assertEquals(mMsg + " Unexpected error: reported rcode" + mRcode +
" blob's rcode " + mDnsAnswer.getRcode(), mRcode, mDnsAnswer.getRcode());
}
public void assertHasAnswer() {
assertValidAnswer();
// Check rcode field.(0, No error condition).
assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
// Check answer counts.
assertGreaterThan(mMsg + " No answer found", mDnsAnswer.getANCount(), 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
public void assertNXDomain() {
assertValidAnswer();
// Check rcode field.(3, NXDomain).
assertEquals(mMsg + " Unexpected rcode: " + mRcode, mRcode, NXDOMAIN);
// Check answer counts. Expect 0 answer.
assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
public void assertEmptyAnswer() {
assertValidAnswer();
// Check rcode field.(0, No error condition).
assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
// Check answer counts. Expect 0 answer.
assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
}
public void testRawQuery() throws Exception {
doTestRawQuery(mExecutor);
}
public void testRawQueryInline() throws Exception {
doTestRawQuery(mExecutorInline);
}
public void testRawQueryBlob() throws Exception {
doTestRawQueryBlob(mExecutor);
}
public void testRawQueryBlobInline() throws Exception {
doTestRawQueryBlob(mExecutorInline);
}
public void testRawQueryRoot() throws Exception {
doTestRawQueryRoot(mExecutor);
}
public void testRawQueryRootInline() throws Exception {
doTestRawQueryRoot(mExecutorInline);
}
public void testRawQueryNXDomain() throws Exception {
doTestRawQueryNXDomain(mExecutor);
}
public void testRawQueryNXDomainInline() throws Exception {
doTestRawQueryNXDomain(mExecutorInline);
}
public void testRawQueryNXDomainWithPrivateDns() throws Exception {
doTestRawQueryNXDomainWithPrivateDns(mExecutor);
}
public void testRawQueryNXDomainInlineWithPrivateDns() throws Exception {
doTestRawQueryNXDomainWithPrivateDns(mExecutorInline);
}
public void doTestRawQuery(Executor executor) throws InterruptedException {
final String msg = "RawQuery " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertHasAnswer();
}
}
public void doTestRawQueryBlob(Executor executor) throws InterruptedException {
final byte[] blob = new byte[]{
/* Header */
0x55, 0x66, /* Transaction ID */
0x01, 0x00, /* Flags */
0x00, 0x01, /* Questions */
0x00, 0x00, /* Answer RRs */
0x00, 0x00, /* Authority RRs */
0x00, 0x00, /* Additional RRs */
/* Queries */
0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
0x00, 0x01, /* Type */
0x00, 0x01 /* Class */
};
final String msg = "RawQuery blob " + byteArrayToHexString(blob);
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, blob, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertHasAnswer();
}
}
public void doTestRawQueryRoot(Executor executor) throws InterruptedException {
final String dname = "";
final String msg = "RawQuery empty dname(ROOT) ";
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
// Except no answer record because the root does not have AAAA records.
callback.assertEmptyAnswer();
}
}
public void doTestRawQueryNXDomain(Executor executor) throws InterruptedException {
final String msg = "RawQuery " + TEST_NX_DOMAIN;
for (Network network : getTestableNetworks()) {
final NetworkCapabilities nc = (network != null)
? mCM.getNetworkCapabilities(network)
: mCM.getNetworkCapabilities(mCM.getActiveNetwork());
assertNotNull("Couldn't determine NetworkCapabilities for " + network, nc);
// Some cellular networks configure their DNS servers never to return NXDOMAIN, so don't
// test NXDOMAIN on these DNS servers.
// b/144521720
if (nc.hasTransport(TRANSPORT_CELLULAR)) continue;
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, TEST_NX_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertNXDomain();
}
}
public void doTestRawQueryNXDomainWithPrivateDns(Executor executor)
throws InterruptedException {
final String msg = "RawQuery " + TEST_NX_DOMAIN + " with private DNS";
// Enable private DNS strict mode and set server to dns.google before doing NxDomain test.
// b/144521720
mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
for (Network network : getTestableNetworks()) {
final Network networkForPrivateDns =
(network != null) ? network : mCM.getActiveNetwork();
assertNotNull("Can't find network to await private DNS on", networkForPrivateDns);
mCtsNetUtils.awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
networkForPrivateDns, GOOGLE_PRIVATE_DNS_SERVER, true);
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, TEST_NX_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertNXDomain();
}
}
public void testRawQueryCancel() throws InterruptedException {
final String msg = "Test cancel RawQuery " + TEST_DOMAIN;
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
// that the query is cancelled before it succeeds. If it is not cancelled before it
// succeeds, retry the test until it is.
for (Network network : getTestableNetworks()) {
boolean retry = false;
int round = 0;
do {
if (++round > CANCEL_RETRY_TIMES) {
fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
}
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
});
retry = callback.needRetry();
assertTrue(msg + " query was not cancelled",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} while (retry);
}
}
public void testRawQueryBlobCancel() throws InterruptedException {
final String msg = "Test cancel RawQuery blob " + byteArrayToHexString(TEST_BLOB);
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
// that the query is cancelled before it succeeds. If it is not cancelled before it
// succeeds, retry the test until it is.
for (Network network : getTestableNetworks()) {
boolean retry = false;
int round = 0;
do {
if (++round > CANCEL_RETRY_TIMES) {
fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
}
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
mDns.rawQuery(network, TEST_BLOB, FLAG_EMPTY, mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
});
retry = callback.needRetry();
assertTrue(msg + " cancel is not done",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} while (retry);
}
}
public void testCancelBeforeQuery() throws InterruptedException {
final String msg = "Test cancelled RawQuery " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
final CancellationSignal cancelSignal = new CancellationSignal();
cancelSignal.cancel();
mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mExecutor, cancelSignal, callback);
assertTrue(msg + " should not return any answers",
!callback.waitForAnswer(CANCEL_TIMEOUT_MS));
}
}
/**
* A query callback for InetAddress that ensures that the query is
* cancelled and that onAnswer is never called. If the query succeeds
* before it is cancelled, needRetry will return true so the
* test can retry.
*/
class VerifyCancelInetAddressCallback implements DnsResolver.Callback<List<InetAddress>> {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final String mMsg;
private final List<InetAddress> mAnswers;
private final CancellationSignal mCancelSignal;
private String mErrorMsg = null;
VerifyCancelInetAddressCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
this.mMsg = msg;
this.mCancelSignal = cancel;
mAnswers = new ArrayList<>();
}
public boolean waitForAnswer() throws InterruptedException {
return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public boolean needRetry() throws InterruptedException {
return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public boolean isAnswerEmpty() {
return mAnswers.isEmpty();
}
public boolean hasIpv6Answer() {
for (InetAddress answer : mAnswers) {
if (answer instanceof Inet6Address) return true;
}
return false;
}
public boolean hasIpv4Answer() {
for (InetAddress answer : mAnswers) {
if (answer instanceof Inet4Address) return true;
}
return false;
}
public void assertNoError() {
assertNull(mErrorMsg);
}
@Override
public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
if (mCancelSignal != null && mCancelSignal.isCanceled()) {
mErrorMsg = mMsg + " should not have returned any answers";
mLatch.countDown();
return;
}
for (InetAddress addr : answerList) {
Log.d(TAG, "Reported addr: " + addr.toString());
}
mAnswers.clear();
mAnswers.addAll(answerList);
mLatch.countDown();
}
@Override
public void onError(@NonNull DnsResolver.DnsException error) {
mErrorMsg = mMsg + error.getMessage();
}
}
public void testQueryForInetAddress() throws Exception {
doTestQueryForInetAddress(mExecutor);
}
public void testQueryForInetAddressInline() throws Exception {
doTestQueryForInetAddress(mExecutorInline);
}
public void testQueryForInetAddressIpv4() throws Exception {
doTestQueryForInetAddressIpv4(mExecutor);
}
public void testQueryForInetAddressIpv4Inline() throws Exception {
doTestQueryForInetAddressIpv4(mExecutorInline);
}
public void testQueryForInetAddressIpv6() throws Exception {
doTestQueryForInetAddressIpv6(mExecutor);
}
public void testQueryForInetAddressIpv6Inline() throws Exception {
doTestQueryForInetAddressIpv6(mExecutorInline);
}
public void testContinuousQueries() throws Exception {
doTestContinuousQueries(mExecutor);
}
@SkipPresubmit(reason = "Flaky: b/159762682; add to presubmit after fixing")
public void testContinuousQueriesInline() throws Exception {
doTestContinuousQueries(mExecutorInline);
}
public void doTestQueryForInetAddress(Executor executor) throws InterruptedException {
final String msg = "Test query for InetAddress " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertNoError();
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
}
}
public void testQueryCancelForInetAddress() throws InterruptedException {
final String msg = "Test cancel query for InetAddress " + TEST_DOMAIN;
// Start a DNS query and the cancel it immediately. Use VerifyCancelInetAddressCallback to
// expect that the query is cancelled before it succeeds. If it is not cancelled before it
// succeeds, retry the test until it is.
for (Network network : getTestableNetworks()) {
boolean retry = false;
int round = 0;
do {
if (++round > CANCEL_RETRY_TIMES) {
fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
}
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, cancelSignal);
mDns.query(network, TEST_DOMAIN, FLAG_EMPTY, mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
});
retry = callback.needRetry();
assertTrue(msg + " query was not cancelled",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} while (retry);
}
}
public void doTestQueryForInetAddressIpv4(Executor executor) throws InterruptedException {
final String msg = "Test query for IPv4 InetAddress " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
mDns.query(network, TEST_DOMAIN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertNoError();
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
assertTrue(msg + " returned Ipv6 results", !callback.hasIpv6Answer());
}
}
public void doTestQueryForInetAddressIpv6(Executor executor) throws InterruptedException {
final String msg = "Test query for IPv6 InetAddress " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
mDns.query(network, TEST_DOMAIN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertNoError();
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
assertTrue(msg + " returned Ipv4 results", !callback.hasIpv4Answer());
}
}
public void testPrivateDnsBypass() throws InterruptedException {
final Network[] testNetworks = getTestableNetworks();
// Set an invalid private DNS server
mCtsNetUtils.setPrivateDnsStrictMode(INVALID_PRIVATE_DNS_SERVER);
final String msg = "Test PrivateDnsBypass " + TEST_DOMAIN;
for (Network network : testNetworks) {
// This test cannot be ran with null network because we need to explicitly pass a
// private DNS bypassable network or bind one.
if (network == null) continue;
// wait for private DNS setting propagating
mCtsNetUtils.awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
network, INVALID_PRIVATE_DNS_SERVER, false);
final CountDownLatch latch = new CountDownLatch(1);
final DnsResolver.Callback<List<InetAddress>> errorCallback =
new DnsResolver.Callback<List<InetAddress>>() {
@Override
public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
fail(msg + " should not get valid answer");
}
@Override
public void onError(@NonNull DnsResolver.DnsException error) {
assertEquals(DnsResolver.ERROR_SYSTEM, error.code);
assertEquals(ETIMEDOUT, ((ErrnoException) error.getCause()).errno);
latch.countDown();
}
};
// Private DNS strict mode with invalid DNS server is set
// Expect no valid answer returned but ErrnoException with ETIMEDOUT
mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, errorCallback);
assertTrue(msg + " invalid server round. No response after " + TIMEOUT_MS + "ms.",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
// Bypass privateDns, expect query works fine
mDns.query(network.getPrivateDnsBypassingCopy(),
TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
assertTrue(msg + " bypass private DNS round. No answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertNoError();
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
// To ensure private DNS bypass still work even if passing null network.
// Bind process network with a private DNS bypassable network.
mCM.bindProcessToNetwork(network.getPrivateDnsBypassingCopy());
final VerifyCancelInetAddressCallback callbackWithNullNetwork =
new VerifyCancelInetAddressCallback(msg + " with null network ", null);
mDns.query(null,
TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callbackWithNullNetwork);
assertTrue(msg + " with null network bypass private DNS round. No answer after " +
TIMEOUT_MS + "ms.", callbackWithNullNetwork.waitForAnswer());
callbackWithNullNetwork.assertNoError();
assertTrue(msg + " with null network returned 0 results",
!callbackWithNullNetwork.isAnswerEmpty());
// Reset process network to default.
mCM.bindProcessToNetwork(null);
}
}
public void doTestContinuousQueries(Executor executor) throws InterruptedException {
final String msg = "Test continuous " + QUERY_TIMES + " queries " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
for (int i = 0; i < QUERY_TIMES ; ++i) {
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, null);
// query v6/v4 in turn
boolean queryV6 = (i % 2 == 0);
mDns.query(network, TEST_DOMAIN, queryV6 ? TYPE_AAAA : TYPE_A,
FLAG_NO_CACHE_LOOKUP, executor, null, callback);
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
callback.waitForAnswer());
callback.assertNoError();
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
assertTrue(msg + " returned " + (queryV6 ? "Ipv4" : "Ipv6") + " results",
queryV6 ? !callback.hasIpv4Answer() : !callback.hasIpv6Answer());
}
}
}
}

View File

@@ -0,0 +1,309 @@
/*
* Copyright (C) 2013 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 android.net.cts;
import android.content.Context;
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.NetworkInfo;
import android.os.SystemClock;
import android.test.AndroidTestCase;
import android.util.Log;
import com.android.testutils.SkipPresubmit;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class DnsTest extends AndroidTestCase {
static {
System.loadLibrary("nativedns_jni");
}
private static final boolean DBG = false;
private static final String TAG = "DnsTest";
private static final String PROXY_NETWORK_TYPE = "PROXY";
private ConnectivityManager mCm;
public void setUp() {
mCm = getContext().getSystemService(ConnectivityManager.class);
}
/**
* @return true on success
*/
private static native boolean testNativeDns();
/**
* Verify:
* DNS works - forwards and backwards, giving ipv4 and ipv6
* Test that DNS work on v4 and v6 networks
* Test Native dns calls (4)
* Todo:
* Cache is flushed when we change networks
* have per-network caches
* No cache when there's no network
* Perf - measure size of first and second tier caches and their effect
* Assert requires network permission
*/
@SkipPresubmit(reason = "IPv6 support may be missing on presubmit virtual hardware")
public void testDnsWorks() throws Exception {
ensureIpv6Connectivity();
InetAddress addrs[] = {};
try {
addrs = InetAddress.getAllByName("www.google.com");
} catch (UnknownHostException e) {}
assertTrue("[RERUN] DNS could not resolve www.google.com. Check internet connection",
addrs.length != 0);
boolean foundV4 = false, foundV6 = false;
for (InetAddress addr : addrs) {
if (addr instanceof Inet4Address) foundV4 = true;
else if (addr instanceof Inet6Address) foundV6 = true;
if (DBG) Log.e(TAG, "www.google.com gave " + addr.toString());
}
// We should have at least one of the addresses to connect!
assertTrue("www.google.com must have IPv4 and/or IPv6 address", foundV4 || foundV6);
// Skip the rest of the test if the active network for watch is PROXY.
// TODO: Check NetworkInfo type in addition to type name once ag/601257 is merged.
if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
&& activeNetworkInfoIsProxy()) {
Log.i(TAG, "Skipping test because the active network type name is PROXY.");
return;
}
// Clear test state so we don't get confused with the previous results.
addrs = new InetAddress[0];
foundV4 = foundV6 = false;
try {
addrs = InetAddress.getAllByName("ipv6.google.com");
} catch (UnknownHostException e) {}
String msg =
"[RERUN] DNS could not resolve ipv6.google.com, check the network supports IPv6. lp=" +
mCm.getActiveLinkProperties();
assertTrue(msg, addrs.length != 0);
for (InetAddress addr : addrs) {
msg = "[RERUN] ipv6.google.com returned IPv4 address: " + addr.getHostAddress() +
", check your network's DNS server. lp=" + mCm.getActiveLinkProperties();
assertFalse (msg, addr instanceof Inet4Address);
foundV6 |= (addr instanceof Inet6Address);
if (DBG) Log.e(TAG, "ipv6.google.com gave " + addr.toString());
}
assertTrue(foundV6);
assertTrue(testNativeDns());
}
private static final String[] URLS = { "www.google.com", "ipv6.google.com", "www.yahoo.com",
"facebook.com", "youtube.com", "blogspot.com", "baidu.com", "wikipedia.org",
// live.com fails rev lookup.
"twitter.com", "qq.com", "msn.com", "yahoo.co.jp", "linkedin.com",
"taobao.com", "google.co.in", "sina.com.cn", "amazon.com", "wordpress.com",
"google.co.uk", "ebay.com", "yandex.ru", "163.com", "google.co.jp", "google.fr",
"microsoft.com", "paypal.com", "google.com.br", "flickr.com",
"mail.ru", "craigslist.org", "fc2.com", "google.it",
// "apple.com", fails rev lookup
"google.es",
"imdb.com", "google.ru", "soho.com", "bbc.co.uk", "vkontakte.ru", "ask.com",
"tumblr.com", "weibo.com", "go.com", "xvideos.com", "livejasmin.com", "cnn.com",
"youku.com", "blogspot.com", "soso.com", "google.ca", "aol.com", "tudou.com",
"xhamster.com", "megaupload.com", "ifeng.com", "zedo.com", "mediafire.com", "ameblo.jp",
"pornhub.com", "google.co.id", "godaddy.com", "adobe.com", "rakuten.co.jp", "about.com",
"espn.go.com", "4shared.com", "alibaba.com","ebay.de", "yieldmanager.com",
"wordpress.org", "livejournal.com", "google.com.tr", "google.com.mx", "renren.com",
"livedoor.com", "google.com.au", "youporn.com", "uol.com.br", "cnet.com", "conduit.com",
"google.pl", "myspace.com", "nytimes.com", "ebay.co.uk", "chinaz.com", "hao123.com",
"thepiratebay.org", "doubleclick.com", "alipay.com", "netflix.com", "cnzz.com",
"huffingtonpost.com", "twitpic.com", "weather.com", "babylon.com", "amazon.de",
"dailymotion.com", "orkut.com", "orkut.com.br", "google.com.sa", "odnoklassniki.ru",
"amazon.co.jp", "google.nl", "goo.ne.jp", "stumbleupon.com", "tube8.com", "tmall.com",
"imgur.com", "globo.com", "secureserver.net", "fileserve.com", "tianya.cn", "badoo.com",
"ehow.com", "photobucket.com", "imageshack.us", "xnxx.com", "deviantart.com",
"filestube.com", "addthis.com", "douban.com", "vimeo.com", "sogou.com",
"stackoverflow.com", "reddit.com", "dailymail.co.uk", "redtube.com", "megavideo.com",
"taringa.net", "pengyou.com", "amazon.co.uk", "fbcdn.net", "aweber.com", "spiegel.de",
"rapidshare.com", "mixi.jp", "360buy.com", "google.cn", "digg.com", "answers.com",
"bit.ly", "indiatimes.com", "skype.com", "yfrog.com", "optmd.com", "google.com.eg",
"google.com.pk", "58.com", "hotfile.com", "google.co.th",
"bankofamerica.com", "sourceforge.net", "maktoob.com", "warriorforum.com", "rediff.com",
"google.co.za", "56.com", "torrentz.eu", "clicksor.com", "avg.com",
"download.com", "ku6.com", "statcounter.com", "foxnews.com", "google.com.ar",
"nicovideo.jp", "reference.com", "liveinternet.ru", "ucoz.ru", "xinhuanet.com",
"xtendmedia.com", "naver.com", "youjizz.com", "domaintools.com", "sparkstudios.com",
"rambler.ru", "scribd.com", "kaixin001.com", "mashable.com", "adultfirendfinder.com",
"files.wordpress.com", "guardian.co.uk", "bild.de", "yelp.com", "wikimedia.org",
"chase.com", "onet.pl", "ameba.jp", "pconline.com.cn", "free.fr", "etsy.com",
"typepad.com", "youdao.com", "megaclick.com", "digitalpoint.com", "blogfa.com",
"salesforce.com", "adf.ly", "ganji.com", "wikia.com", "archive.org", "terra.com.br",
"w3schools.com", "ezinearticles.com", "wjs.com", "google.com.my", "clickbank.com",
"squidoo.com", "hulu.com", "repubblica.it", "google.be", "allegro.pl", "comcast.net",
"narod.ru", "zol.com.cn", "orange.fr", "soufun.com", "hatena.ne.jp", "google.gr",
"in.com", "techcrunch.com", "orkut.co.in", "xunlei.com",
"reuters.com", "google.com.vn", "hostgator.com", "kaskus.us", "espncricinfo.com",
"hootsuite.com", "qiyi.com", "gmx.net", "xing.com", "php.net", "soku.com", "web.de",
"libero.it", "groupon.com", "51.la", "slideshare.net", "booking.com", "seesaa.net",
"126.com", "telegraph.co.uk", "wretch.cc", "twimg.com", "rutracker.org", "angege.com",
"nba.com", "dell.com", "leboncoin.fr", "people.com", "google.com.tw", "walmart.com",
"daum.net", "2ch.net", "constantcontact.com", "nifty.com", "mywebsearch.com",
"tripadvisor.com", "google.se", "paipai.com", "google.com.ua", "ning.com", "hp.com",
"google.at", "joomla.org", "icio.us", "hudong.com", "csdn.net", "getfirebug.com",
"ups.com", "cj.com", "google.ch", "camzap.com", "wordreference.com", "tagged.com",
"wp.pl", "mozilla.com", "google.ru", "usps.com", "china.com", "themeforest.net",
"search-results.com", "tribalfusion.com", "thefreedictionary.com", "isohunt.com",
"linkwithin.com", "cam4.com", "plentyoffish.com", "wellsfargo.com", "metacafe.com",
"depositfiles.com", "freelancer.com", "opendns.com", "homeway.com", "engadget.com",
"10086.cn", "360.cn", "marca.com", "dropbox.com", "ign.com", "match.com", "google.pt",
"facemoods.com", "hardsextube.com", "google.com.ph", "lockerz.com", "istockphoto.com",
"partypoker.com", "netlog.com", "outbrain.com", "elpais.com", "fiverr.com",
"biglobe.ne.jp", "corriere.it", "love21cn.com", "yesky.com", "spankwire.com",
"ig.com.br", "imagevenue.com", "hubpages.com", "google.co.ve"};
// TODO - this works, but is slow and cts doesn't do anything with the result.
// Maybe require a min performance, a min cache size (detectable) and/or move
// to perf testing
private static final int LOOKUP_COUNT_GOAL = URLS.length;
public void skiptestDnsPerf() {
ArrayList<String> results = new ArrayList<String>();
int failures = 0;
try {
for (int numberOfUrls = URLS.length; numberOfUrls > 0; numberOfUrls--) {
failures = 0;
int iterationLimit = LOOKUP_COUNT_GOAL / numberOfUrls;
long startTime = SystemClock.elapsedRealtimeNanos();
for (int iteration = 0; iteration < iterationLimit; iteration++) {
for (int urlIndex = 0; urlIndex < numberOfUrls; urlIndex++) {
try {
InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
} catch (UnknownHostException e) {
Log.e(TAG, "failed first lookup of " + URLS[urlIndex]);
failures++;
try {
InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
} catch (UnknownHostException ee) {
failures++;
Log.e(TAG, "failed SECOND lookup of " + URLS[urlIndex]);
}
}
}
}
long endTime = SystemClock.elapsedRealtimeNanos();
float nsPer = ((float)(endTime-startTime) / iterationLimit) / numberOfUrls/ 1000;
String thisResult = new String("getByName for " + numberOfUrls + " took " +
(endTime - startTime)/1000 + "(" + nsPer + ") with " +
failures + " failures\n");
Log.d(TAG, thisResult);
results.add(thisResult);
}
// build up a list of addresses
ArrayList<byte[]> addressList = new ArrayList<byte[]>();
for (String url : URLS) {
try {
InetAddress addr = InetAddress.getByName(url);
addressList.add(addr.getAddress());
} catch (UnknownHostException e) {
Log.e(TAG, "Exception making reverseDNS list: " + e.toString());
}
}
for (int numberOfAddrs = addressList.size(); numberOfAddrs > 0; numberOfAddrs--) {
int iterationLimit = LOOKUP_COUNT_GOAL / numberOfAddrs;
failures = 0;
long startTime = SystemClock.elapsedRealtimeNanos();
for (int iteration = 0; iteration < iterationLimit; iteration++) {
for (int addrIndex = 0; addrIndex < numberOfAddrs; addrIndex++) {
try {
InetAddress addr = InetAddress.getByAddress(addressList.get(addrIndex));
String hostname = addr.getHostName();
} catch (UnknownHostException e) {
failures++;
Log.e(TAG, "Failure doing reverse DNS lookup: " + e.toString());
try {
InetAddress addr =
InetAddress.getByAddress(addressList.get(addrIndex));
String hostname = addr.getHostName();
} catch (UnknownHostException ee) {
failures++;
Log.e(TAG, "Failure doing SECOND reverse DNS lookup: " +
ee.toString());
}
}
}
}
long endTime = SystemClock.elapsedRealtimeNanos();
float nsPer = ((endTime-startTime) / iterationLimit) / numberOfAddrs / 1000;
String thisResult = new String("getHostName for " + numberOfAddrs + " took " +
(endTime - startTime)/1000 + "(" + nsPer + ") with " +
failures + " failures\n");
Log.d(TAG, thisResult);
results.add(thisResult);
}
for (String result : results) Log.d(TAG, result);
InetAddress exit = InetAddress.getByName("exitrightnow.com");
Log.e(TAG, " exit address= "+exit.toString());
} catch (Exception e) {
Log.e(TAG, "bad URL in testDnsPerf: " + e.toString());
}
}
private boolean activeNetworkInfoIsProxy() {
NetworkInfo info = mCm.getActiveNetworkInfo();
if (PROXY_NETWORK_TYPE.equals(info.getTypeName())) {
return true;
}
return false;
}
private void ensureIpv6Connectivity() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
final int TIMEOUT_MS = 5_000;
final NetworkCallback callback = new NetworkCallback() {
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
if (lp.hasGlobalIpv6Address()) {
latch.countDown();
}
}
};
mCm.registerDefaultNetworkCallback(callback);
String msg = "Default network did not provide IPv6 connectivity after " + TIMEOUT_MS
+ "ms. Please connect to an IPv6-capable network. lp="
+ mCm.getActiveLinkProperties();
try {
assertTrue(msg, latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
mCm.unregisterNetworkCallback(callback);
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright (C) 2020 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 android.net.cts;
import static android.net.cts.PacketUtils.BytePayload;
import static android.net.cts.PacketUtils.IP4_HDRLEN;
import static android.net.cts.PacketUtils.IP6_HDRLEN;
import static android.net.cts.PacketUtils.IpHeader;
import static android.net.cts.PacketUtils.UDP_HDRLEN;
import static android.net.cts.PacketUtils.UdpHeader;
import static android.net.cts.PacketUtils.getIpHeader;
import static android.system.OsConstants.IPPROTO_UDP;
import android.os.ParcelFileDescriptor;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.Arrays;
// TODO: Merge this with the version in the IPsec module (IKEv2 library) CTS tests.
/** An extension of the TunUtils class with IKE-specific packet handling. */
public class IkeTunUtils extends TunUtils {
private static final int PORT_LEN = 2;
private static final byte[] NON_ESP_MARKER = new byte[] {0, 0, 0, 0};
private static final int IKE_HEADER_LEN = 28;
private static final int IKE_SPI_LEN = 8;
private static final int IKE_IS_RESP_BYTE_OFFSET = 19;
private static final int IKE_MSG_ID_OFFSET = 20;
private static final int IKE_MSG_ID_LEN = 4;
public IkeTunUtils(ParcelFileDescriptor tunFd) {
super(tunFd);
}
/**
* Await an expected IKE request and inject an IKE response.
*
* @param respIkePkt IKE response packet without IP/UDP headers or NON ESP MARKER.
*/
public byte[] awaitReqAndInjectResp(long expectedInitIkeSpi, int expectedMsgId,
boolean encapExpected, byte[] respIkePkt) throws Exception {
final byte[] request = awaitIkePacket(expectedInitIkeSpi, expectedMsgId, encapExpected);
// Build response header by flipping address and port
final InetAddress srcAddr = getDstAddress(request);
final InetAddress dstAddr = getSrcAddress(request);
final int srcPort = getDstPort(request);
final int dstPort = getSrcPort(request);
final byte[] response =
buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, encapExpected, respIkePkt);
injectPacket(response);
return request;
}
private byte[] awaitIkePacket(long expectedInitIkeSpi, int expectedMsgId, boolean expectEncap)
throws Exception {
return super.awaitPacket(pkt -> isIke(pkt, expectedInitIkeSpi, expectedMsgId, expectEncap));
}
private static boolean isIke(
byte[] pkt, long expectedInitIkeSpi, int expectedMsgId, boolean encapExpected) {
final int ipProtocolOffset;
final int ikeOffset;
if (isIpv6(pkt)) {
ipProtocolOffset = IP6_PROTO_OFFSET;
ikeOffset = IP6_HDRLEN + UDP_HDRLEN;
} else {
if (encapExpected && !hasNonEspMarkerv4(pkt)) {
return false;
}
// Use default IPv4 header length (assuming no options)
final int encapMarkerLen = encapExpected ? NON_ESP_MARKER.length : 0;
ipProtocolOffset = IP4_PROTO_OFFSET;
ikeOffset = IP4_HDRLEN + UDP_HDRLEN + encapMarkerLen;
}
return pkt[ipProtocolOffset] == IPPROTO_UDP
&& areSpiAndMsgIdEqual(pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId);
}
/** Checks if the provided IPv4 packet has a UDP-encapsulation NON-ESP marker */
private static boolean hasNonEspMarkerv4(byte[] ipv4Pkt) {
final int nonEspMarkerOffset = IP4_HDRLEN + UDP_HDRLEN;
if (ipv4Pkt.length < nonEspMarkerOffset + NON_ESP_MARKER.length) {
return false;
}
final byte[] nonEspMarker = Arrays.copyOfRange(
ipv4Pkt, nonEspMarkerOffset, nonEspMarkerOffset + NON_ESP_MARKER.length);
return Arrays.equals(NON_ESP_MARKER, nonEspMarker);
}
private static boolean areSpiAndMsgIdEqual(
byte[] pkt, int ikeOffset, long expectedIkeInitSpi, int expectedMsgId) {
if (pkt.length <= ikeOffset + IKE_HEADER_LEN) {
return false;
}
final ByteBuffer buffer = ByteBuffer.wrap(pkt);
final long spi = buffer.getLong(ikeOffset);
final int msgId = buffer.getInt(ikeOffset + IKE_MSG_ID_OFFSET);
return expectedIkeInitSpi == spi && expectedMsgId == msgId;
}
private static InetAddress getSrcAddress(byte[] pkt) throws Exception {
return getAddress(pkt, true);
}
private static InetAddress getDstAddress(byte[] pkt) throws Exception {
return getAddress(pkt, false);
}
private static InetAddress getAddress(byte[] pkt, boolean getSrcAddr) throws Exception {
final int ipLen = isIpv6(pkt) ? IP6_ADDR_LEN : IP4_ADDR_LEN;
final int srcIpOffset = isIpv6(pkt) ? IP6_ADDR_OFFSET : IP4_ADDR_OFFSET;
final int ipOffset = getSrcAddr ? srcIpOffset : srcIpOffset + ipLen;
if (pkt.length < ipOffset + ipLen) {
// Should be impossible; getAddress() is only called with a full IKE request including
// the IP and UDP headers.
throw new IllegalArgumentException("Packet was too short to contain IP address");
}
return InetAddress.getByAddress(Arrays.copyOfRange(pkt, ipOffset, ipOffset + ipLen));
}
private static int getSrcPort(byte[] pkt) throws Exception {
return getPort(pkt, true);
}
private static int getDstPort(byte[] pkt) throws Exception {
return getPort(pkt, false);
}
private static int getPort(byte[] pkt, boolean getSrcPort) {
final int srcPortOffset = isIpv6(pkt) ? IP6_HDRLEN : IP4_HDRLEN;
final int portOffset = getSrcPort ? srcPortOffset : srcPortOffset + PORT_LEN;
if (pkt.length < portOffset + PORT_LEN) {
// Should be impossible; getPort() is only called with a full IKE request including the
// IP and UDP headers.
throw new IllegalArgumentException("Packet was too short to contain port");
}
final ByteBuffer buffer = ByteBuffer.wrap(pkt);
return Short.toUnsignedInt(buffer.getShort(portOffset));
}
private static byte[] buildIkePacket(
InetAddress srcAddr,
InetAddress dstAddr,
int srcPort,
int dstPort,
boolean useEncap,
byte[] payload)
throws Exception {
// Append non-ESP marker if encap is enabled
if (useEncap) {
final ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER.length + payload.length);
buffer.put(NON_ESP_MARKER);
buffer.put(payload);
payload = buffer.array();
}
final UdpHeader udpPkt = new UdpHeader(srcPort, dstPort, new BytePayload(payload));
final IpHeader ipPkt = getIpHeader(udpPkt.getProtocolId(), srcAddr, dstAddr, udpPkt);
return ipPkt.getPacketBytes();
}
}

View File

@@ -0,0 +1,535 @@
/*
* Copyright (C) 2020 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 android.net.cts;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.Manifest;
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Ikev2VpnProfile;
import android.net.IpSecAlgorithm;
import android.net.LinkAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.ProxyInfo;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.net.VpnManager;
import android.net.cts.util.CtsNetUtils;
import android.os.Build;
import android.os.Process;
import android.platform.test.annotations.AppModeFull;
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.HexDump;
import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.math.BigInteger;
import java.net.InetAddress;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.security.auth.x500.X500Principal;
@RunWith(DevSdkIgnoreRunner.class)
@IgnoreUpTo(Build.VERSION_CODES.Q)
@AppModeFull(reason = "Appops state changes disallowed for instant apps (OP_ACTIVATE_PLATFORM_VPN)")
public class Ikev2VpnTest {
private static final String TAG = Ikev2VpnTest.class.getSimpleName();
// Test vectors for IKE negotiation in test mode.
private static final String SUCCESSFUL_IKE_INIT_RESP_V4 =
"46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000"
+ "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800"
+ "100000b8070f159fe5141d8754ca86f72ecc28d66f514927e96cbe9eec0adb42bf2c276a0ab7"
+ "a97fa93555f4be9218c14e7f286bb28c6b4fb13825a420f2ffc165854f200bab37d69c8963d4"
+ "0acb831d983163aa50622fd35c182efe882cf54d6106222abcfaa597255d302f1b95ab71c142"
+ "c279ea5839a180070bff73f9d03fab815f0d5ee2adec7e409d1e35979f8bd92ffd8aab13d1a0"
+ "0657d816643ae767e9ae84d2ccfa2bcce1a50572be8d3748ae4863c41ae90da16271e014270f"
+ "77edd5cd2e3299f3ab27d7203f93d770bacf816041cdcecd0f9af249033979da4369cb242dd9"
+ "6d172e60513ff3db02de63e50eb7d7f596ada55d7946cad0af0669d1f3e2804846ab3f2a930d"
+ "df56f7f025f25c25ada694e6231abbb87ee8cfd072c8481dc0b0f6b083fdc3bd89b080e49feb"
+ "0288eef6fdf8a26ee2fc564a11e7385215cf2deaf2a9965638fc279c908ccdf04094988d91a2"
+ "464b4a8c0326533aff5119ed79ecbd9d99a218b44f506a5eb09351e67da86698b4c58718db25"
+ "d55f426fb4c76471b27a41fbce00777bc233c7f6e842e39146f466826de94f564cad8b92bfbe"
+ "87c99c4c7973ec5f1eea8795e7da82819753aa7c4fcfdab77066c56b939330c4b0d354c23f83"
+ "ea82fa7a64c4b108f1188379ea0eb4918ee009d804100e6bf118771b9058d42141c847d5ec37"
+ "6e5ec591c71fc9dac01063c2bd31f9c783b28bf1182900002430f3d5de3449462b31dd28bc27"
+ "297b6ad169bccce4f66c5399c6e0be9120166f2900001c0000400428b8df2e66f69c8584a186"
+ "c5eac66783551d49b72900001c000040054e7a622e802d5cbfb96d5f30a6e433994370173529"
+ "0000080000402e290000100000402f00020003000400050000000800004014";
private static final String SUCCESSFUL_IKE_INIT_RESP_V6 =
"46b8eca1e0d72a1800d9ea1babce26bf2120222000000000000002d0220000300000002c01010004030000"
+ "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800"
+ "100000ea0e6dd9ca5930a9a45c323a41f64bfd8cdef7730f5fbff37d7c377da427f489a42aa8"
+ "c89233380e6e925990d49de35c2cdcf63a61302c731a4b3569df1ee1bf2457e55a6751838ede"
+ "abb75cc63ba5c9e4355e8e784f383a5efe8a44727dc14aeaf8dacc2620fb1c8875416dc07739"
+ "7fe4decc1bd514a9c7d270cf21fd734c63a25c34b30b68686e54e8a198f37f27cb491fe27235"
+ "fab5476b036d875ccab9a68d65fbf3006197f9bebbf94de0d3802b4fafe1d48d931ce3a1a346"
+ "2d65bd639e9bd7fa46299650a9dbaf9b324e40b466942d91a59f41ef8042f8474c4850ed0f63"
+ "e9238949d41cd8bbaea9aefdb65443a6405792839563aa5dc5c36b5ce8326ccf8a94d9622b85"
+ "038d390d5fc0299e14e1f022966d4ac66515f6108ca04faec44821fe5bbf2ed4f84ff5671219"
+ "608cb4c36b44a31ba010c9088f8d5ff943bb9ff857f74be1755f57a5783874adc57f42bb174e"
+ "4ad3215de628707014dbcb1707bd214658118fdd7a42b3e1638b991ce5b812a667f1145be811"
+ "685e3cd3baf9b18d062657b64c206a4d19a531c252a6a51a04aeaf42c618620cdbab65baca23"
+ "82c57ed888422aeaacf7f1bc3fe2247ff7e7eaca218b74d7b31d02f2b0afa123f802529e7e6c"
+ "3259d418290740ddbf55686e26998d7edcbbf895664972fed666f2f20af40503aa2af436ec6d"
+ "4ec981ab19b9088755d94ae7a7c2066ea331d4e56e290000243fefe5555fce552d57a84e682c"
+ "d4a6dfb3f2f94a94464d5bec3d88b88e9559642900001c00004004eb4afff764e7b79bca78b1"
+ "3a89100d36d678ae982900001c00004005d177216a3c26f782076e12570d40bfaaa148822929"
+ "0000080000402e290000100000402f00020003000400050000000800004014";
private static final String SUCCESSFUL_IKE_AUTH_RESP_V4 =
"46b8eca1e0d72a18b2b5d9006d47a0022e20232000000001000000e0240000c420a2500a3da4c66fa6929e"
+ "600f36349ba0e38de14f78a3ad0416cba8c058735712a3d3f9a0a6ed36de09b5e9e02697e7c4"
+ "2d210ac86cfbd709503cfa51e2eab8cfdc6427d136313c072968f6506a546eb5927164200592"
+ "6e36a16ee994e63f029432a67bc7d37ca619e1bd6e1678df14853067ecf816b48b81e8746069"
+ "406363e5aa55f13cb2afda9dbebee94256c29d630b17dd7f1ee52351f92b6e1c3d8551c513f1"
+ "d74ac52a80b2041397e109fe0aeb3c105b0d4be0ae343a943398764281";
private static final String SUCCESSFUL_IKE_AUTH_RESP_V6 =
"46b8eca1e0d72a1800d9ea1babce26bf2e20232000000001000000f0240000d4aaf6eaa6c06b50447e6f54"
+ "827fd8a9d9d6ac8015c1ebb3e8cb03fc6e54b49a107441f50004027cc5021600828026367f03"
+ "bc425821cd7772ee98637361300c9b76056e874fea2bd4a17212370b291894264d8c023a01d1"
+ "c3b691fd4b7c0b534e8c95af4c4638e2d125cb21c6267e2507cd745d72e8da109c47b9259c6c"
+ "57a26f6bc5b337b9b9496d54bdde0333d7a32e6e1335c9ee730c3ecd607a8689aa7b0577b74f"
+ "3bf437696a9fd5fc0aee3ed346cd9e15d1dda293df89eb388a8719388a60ca7625754de12cdb"
+ "efe4c886c5c401";
private static final long IKE_INITIATOR_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
private static final InetAddress LOCAL_OUTER_6 =
InetAddress.parseNumericAddress("2001:db8::1");
private static final int IP4_PREFIX_LEN = 32;
private static final int IP6_PREFIX_LEN = 128;
// TODO: Use IPv6 address when we can generate test vectors (GCE does not allow IPv6 yet).
private static final String TEST_SERVER_ADDR_V4 = "192.0.2.2";
private static final String TEST_SERVER_ADDR_V6 = "2001:db8::2";
private static final String TEST_IDENTITY = "client.cts.android.com";
private static final List<String> TEST_ALLOWED_ALGORITHMS =
Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM);
private static final ProxyInfo TEST_PROXY_INFO =
ProxyInfo.buildDirectProxy("proxy.cts.android.com", 1234);
private static final int TEST_MTU = 1300;
private static final byte[] TEST_PSK = "ikeAndroidPsk".getBytes();
private static final String TEST_USER = "username";
private static final String TEST_PASSWORD = "pa55w0rd";
// Static state to reduce setup/teardown
private static final Context sContext = InstrumentationRegistry.getContext();
private static final ConnectivityManager sCM =
(ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
private static final VpnManager sVpnMgr =
(VpnManager) sContext.getSystemService(Context.VPN_MANAGEMENT_SERVICE);
private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
private final X509Certificate mServerRootCa;
private final CertificateAndKey mUserCertKey;
public Ikev2VpnTest() throws Exception {
// Build certificates
mServerRootCa = generateRandomCertAndKeyPair().cert;
mUserCertKey = generateRandomCertAndKeyPair();
}
@After
public void tearDown() {
setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
}
/**
* Sets the given appop using shell commands
*
* <p>This method must NEVER be called from within a shell permission, as it will attempt to
* acquire, and then drop the shell permission identity. This results in the caller losing the
* shell permission identity due to these calls not being reference counted.
*/
public void setAppop(int appop, boolean allow) {
// Requires shell permission to update appops.
runWithShellPermissionIdentity(() -> {
mCtsNetUtils.setAppopPrivileged(appop, allow);
}, Manifest.permission.MANAGE_TEST_NETWORKS);
}
private Ikev2VpnProfile buildIkev2VpnProfileCommon(
Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks) throws Exception {
if (isRestrictedToTestNetworks) {
builder.restrictToTestNetworks();
}
return builder.setBypassable(true)
.setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
.setProxy(TEST_PROXY_INFO)
.setMaxMtu(TEST_MTU)
.setMetered(false)
.build();
}
private Ikev2VpnProfile buildIkev2VpnProfilePsk(boolean isRestrictedToTestNetworks)
throws Exception {
return buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, isRestrictedToTestNetworks);
}
private Ikev2VpnProfile buildIkev2VpnProfilePsk(
String remote, boolean isRestrictedToTestNetworks) throws Exception {
final Ikev2VpnProfile.Builder builder =
new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
}
private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
throws Exception {
final Ikev2VpnProfile.Builder builder =
new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
}
private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
throws Exception {
final Ikev2VpnProfile.Builder builder =
new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthDigitalSignature(
mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
}
private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception {
assertEquals(TEST_SERVER_ADDR_V6, profile.getServerAddr());
assertEquals(TEST_IDENTITY, profile.getUserIdentity());
assertEquals(TEST_PROXY_INFO, profile.getProxyInfo());
assertEquals(TEST_ALLOWED_ALGORITHMS, profile.getAllowedAlgorithms());
assertTrue(profile.isBypassable());
assertFalse(profile.isMetered());
assertEquals(TEST_MTU, profile.getMaxMtu());
assertFalse(profile.isRestrictedToTestNetworks());
}
@Test
public void testBuildIkev2VpnProfilePsk() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
final Ikev2VpnProfile profile =
buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
checkBasicIkev2VpnProfile(profile);
assertArrayEquals(TEST_PSK, profile.getPresharedKey());
// Verify nothing else is set.
assertNull(profile.getUsername());
assertNull(profile.getPassword());
assertNull(profile.getServerRootCaCert());
assertNull(profile.getRsaPrivateKey());
assertNull(profile.getUserCert());
}
@Test
public void testBuildIkev2VpnProfileUsernamePassword() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
final Ikev2VpnProfile profile =
buildIkev2VpnProfileUsernamePassword(false /* isRestrictedToTestNetworks */);
checkBasicIkev2VpnProfile(profile);
assertEquals(TEST_USER, profile.getUsername());
assertEquals(TEST_PASSWORD, profile.getPassword());
assertEquals(mServerRootCa, profile.getServerRootCaCert());
// Verify nothing else is set.
assertNull(profile.getPresharedKey());
assertNull(profile.getRsaPrivateKey());
assertNull(profile.getUserCert());
}
@Test
public void testBuildIkev2VpnProfileDigitalSignature() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
final Ikev2VpnProfile profile =
buildIkev2VpnProfileDigitalSignature(false /* isRestrictedToTestNetworks */);
checkBasicIkev2VpnProfile(profile);
assertEquals(mUserCertKey.cert, profile.getUserCert());
assertEquals(mUserCertKey.key, profile.getRsaPrivateKey());
assertEquals(mServerRootCa, profile.getServerRootCaCert());
// Verify nothing else is set.
assertNull(profile.getUsername());
assertNull(profile.getPassword());
assertNull(profile.getPresharedKey());
}
private void verifyProvisionVpnProfile(
boolean hasActivateVpn, boolean hasActivatePlatformVpn, boolean expectIntent)
throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
setAppop(AppOpsManager.OP_ACTIVATE_VPN, hasActivateVpn);
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, hasActivatePlatformVpn);
final Ikev2VpnProfile profile =
buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
final Intent intent = sVpnMgr.provisionVpnProfile(profile);
assertEquals(expectIntent, intent != null);
}
@Test
public void testProvisionVpnProfileNoPreviousConsent() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
verifyProvisionVpnProfile(false /* hasActivateVpn */,
false /* hasActivatePlatformVpn */, true /* expectIntent */);
}
@Test
public void testProvisionVpnProfilePlatformVpnConsented() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
verifyProvisionVpnProfile(false /* hasActivateVpn */,
true /* hasActivatePlatformVpn */, false /* expectIntent */);
}
@Test
public void testProvisionVpnProfileVpnServiceConsented() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
verifyProvisionVpnProfile(true /* hasActivateVpn */,
false /* hasActivatePlatformVpn */, false /* expectIntent */);
}
@Test
public void testProvisionVpnProfileAllPreConsented() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
verifyProvisionVpnProfile(true /* hasActivateVpn */,
true /* hasActivatePlatformVpn */, false /* expectIntent */);
}
@Test
public void testDeleteVpnProfile() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
final Ikev2VpnProfile profile =
buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
assertNull(sVpnMgr.provisionVpnProfile(profile));
// Verify that deleting the profile works (even without the appop)
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
sVpnMgr.deleteProvisionedVpnProfile();
// Test that the profile was deleted - starting it should throw an IAE.
try {
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
sVpnMgr.startProvisionedVpnProfile();
fail("Expected IllegalArgumentException due to missing profile");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testStartVpnProfileNoPreviousConsent() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
// Make sure the VpnProfile is not provisioned already.
sVpnMgr.stopProvisionedVpnProfile();
try {
sVpnMgr.startProvisionedVpnProfile();
fail("Expected SecurityException for missing consent");
} catch (SecurityException expected) {
}
}
private void checkStartStopVpnProfileBuildsNetworks(IkeTunUtils tunUtils, boolean testIpv6)
throws Exception {
String serverAddr = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4;
String initResp = testIpv6 ? SUCCESSFUL_IKE_INIT_RESP_V6 : SUCCESSFUL_IKE_INIT_RESP_V4;
String authResp = testIpv6 ? SUCCESSFUL_IKE_AUTH_RESP_V6 : SUCCESSFUL_IKE_AUTH_RESP_V4;
boolean hasNat = !testIpv6;
// Requires MANAGE_TEST_NETWORKS to provision a test-mode profile.
mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
final Ikev2VpnProfile profile =
buildIkev2VpnProfilePsk(serverAddr, true /* isRestrictedToTestNetworks */);
assertNull(sVpnMgr.provisionVpnProfile(profile));
sVpnMgr.startProvisionedVpnProfile();
// Inject IKE negotiation
int expectedMsgId = 0;
tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, false /* isEncap */,
HexDump.hexStringToByteArray(initResp));
tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, hasNat /* isEncap */,
HexDump.hexStringToByteArray(authResp));
// Verify the VPN network came up
final NetworkRequest nr = new NetworkRequest.Builder()
.clearCapabilities().addTransportType(TRANSPORT_VPN).build();
final TestNetworkCallback cb = new TestNetworkCallback();
sCM.requestNetwork(nr, cb);
cb.waitForAvailable();
final Network vpnNetwork = cb.currentNetwork;
assertNotNull(vpnNetwork);
final NetworkCapabilities caps = sCM.getNetworkCapabilities(vpnNetwork);
assertTrue(caps.hasTransport(TRANSPORT_VPN));
assertTrue(caps.hasCapability(NET_CAPABILITY_INTERNET));
assertEquals(Process.myUid(), caps.getOwnerUid());
sVpnMgr.stopProvisionedVpnProfile();
cb.waitForLost();
assertEquals(vpnNetwork, cb.lastLostNetwork);
}
private void doTestStartStopVpnProfile(boolean testIpv6) throws Exception {
// Non-final; these variables ensure we clean up properly after our test if we have
// allocated test network resources
final TestNetworkManager tnm = sContext.getSystemService(TestNetworkManager.class);
TestNetworkInterface testIface = null;
TestNetworkCallback tunNetworkCallback = null;
try {
// Build underlying test network
testIface = tnm.createTunInterface(
new LinkAddress[] {
new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)});
// Hold on to this callback to ensure network does not get reaped.
tunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(
testIface.getInterfaceName());
final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor());
checkStartStopVpnProfileBuildsNetworks(tunUtils, testIpv6);
} finally {
// Make sure to stop the VPN profile. This is safe to call multiple times.
sVpnMgr.stopProvisionedVpnProfile();
if (testIface != null) {
testIface.getFileDescriptor().close();
}
if (tunNetworkCallback != null) {
sCM.unregisterNetworkCallback(tunNetworkCallback);
}
final Network testNetwork = tunNetworkCallback.currentNetwork;
if (testNetwork != null) {
tnm.teardownTestNetwork(testNetwork);
}
}
}
@Test
public void testStartStopVpnProfileV4() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
// Requires shell permission to update appops.
runWithShellPermissionIdentity(() -> {
doTestStartStopVpnProfile(false);
});
}
@Test
public void testStartStopVpnProfileV6() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
// Requires shell permission to update appops.
runWithShellPermissionIdentity(() -> {
doTestStartStopVpnProfile(true);
});
}
private static class CertificateAndKey {
public final X509Certificate cert;
public final PrivateKey key;
CertificateAndKey(X509Certificate cert, PrivateKey key) {
this.cert = cert;
this.key = key;
}
}
private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
final Date validityBeginDate =
new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
final Date validityEndDate =
new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
// Generate a keypair
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(512);
final KeyPair keyPair = keyPairGenerator.generateKeyPair();
final X500Principal dnName = new X500Principal("CN=test.android.com");
final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
certGen.setSubjectDN(dnName);
certGen.setIssuerDN(dnName);
certGen.setNotBefore(validityBeginDate);
certGen.setNotAfter(validityEndDate);
certGen.setPublicKey(keyPair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
return new CertificateAndKey(cert, keyPair.getPrivate());
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2018 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 android.net.cts;
import android.net.InetAddresses;
import java.net.InetAddress;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Test;
import org.junit.runner.RunWith;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(JUnitParamsRunner.class)
public class InetAddressesTest {
public static String[][] validNumericAddressesAndStringRepresentation() {
return new String[][] {
// Regular IPv4.
{ "1.2.3.4", "1.2.3.4" },
// Regular IPv6.
{ "2001:4860:800d::68", "2001:4860:800d::68" },
{ "1234:5678::9ABC:DEF0", "1234:5678::9abc:def0" },
{ "2001:cdba:9abc:5678::", "2001:cdba:9abc:5678::" },
{ "::2001:cdba:9abc:5678", "::2001:cdba:9abc:5678" },
{ "64:ff9b::1.2.3.4", "64:ff9b::102:304" },
{ "::9abc:5678", "::154.188.86.120" },
// Mapped IPv4
{ "::ffff:127.0.0.1", "127.0.0.1" },
// Android does not recognize Octal (leading 0) cases: they are treated as decimal.
{ "0177.00.00.01", "177.0.0.1" },
// Verify that examples from JavaDoc work correctly.
{ "192.0.2.1", "192.0.2.1" },
{ "2001:db8::1:2", "2001:db8::1:2" },
};
}
public static String[] invalidNumericAddresses() {
return new String[] {
"",
" ",
"\t",
"\n",
"1.2.3.4.",
"1.2.3",
"1.2",
"1",
"1234",
"0",
"0x1.0x2.0x3.0x4",
"0x7f.0x00.0x00.0x01",
"0256.00.00.01",
"fred",
"www.google.com",
// IPv6 encoded for use in URL as defined in RFC 2732
"[fe80::6:2222]",
};
}
@Parameters(method = "validNumericAddressesAndStringRepresentation")
@Test
public void parseNumericAddress(String address, String expectedString) {
InetAddress inetAddress = InetAddresses.parseNumericAddress(address);
assertEquals(expectedString, inetAddress.getHostAddress());
}
@Parameters(method = "invalidNumericAddresses")
@Test
public void test_parseNonNumericAddress(String address) {
try {
InetAddress inetAddress = InetAddresses.parseNumericAddress(address);
fail(String.format(
"Address %s is not numeric but was parsed as %s", address, inetAddress));
} catch (IllegalArgumentException e) {
assertThat(e.getMessage()).contains(address);
}
}
@Test
public void test_parseNumericAddress_null() {
try {
InetAddress inetAddress = InetAddresses.parseNumericAddress(null);
fail(String.format("null is not numeric but was parsed as %s", inetAddress));
} catch (NullPointerException e) {
// expected
}
}
@Parameters(method = "validNumericAddressesAndStringRepresentation")
@Test
public void test_isNumericAddress(String address, String unused) {
assertTrue("expected '" + address + "' to be treated as numeric",
InetAddresses.isNumericAddress(address));
}
@Parameters(method = "invalidNumericAddresses")
@Test
public void test_isNotNumericAddress(String address) {
assertFalse("expected '" + address + "' to be treated as non-numeric",
InetAddresses.isNumericAddress(address));
}
@Test
public void test_isNumericAddress_null() {
try {
InetAddresses.isNumericAddress(null);
fail("expected null to throw a NullPointerException");
} catch (NullPointerException e) {
// expected
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2019 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 android.net.cts;
import static com.android.testutils.ParcelUtils.assertParcelSane;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import android.net.IpConfiguration;
import android.net.LinkAddress;
import android.net.ProxyInfo;
import android.net.StaticIpConfiguration;
import androidx.test.runner.AndroidJUnit4;
import libcore.net.InetAddressUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.InetAddress;
import java.util.ArrayList;
@RunWith(AndroidJUnit4.class)
public final class IpConfigurationTest {
private static final LinkAddress LINKADDR = new LinkAddress("192.0.2.2/25");
private static final InetAddress GATEWAY = InetAddressUtils.parseNumericAddress("192.0.2.1");
private static final InetAddress DNS1 = InetAddressUtils.parseNumericAddress("8.8.8.8");
private static final InetAddress DNS2 = InetAddressUtils.parseNumericAddress("8.8.4.4");
private static final String DOMAINS = "example.com";
private static final ArrayList<InetAddress> dnsServers = new ArrayList<>();
private StaticIpConfiguration mStaticIpConfig;
private ProxyInfo mProxy;
@Before
public void setUp() {
dnsServers.add(DNS1);
dnsServers.add(DNS2);
mStaticIpConfig = new StaticIpConfiguration.Builder()
.setIpAddress(LINKADDR)
.setGateway(GATEWAY)
.setDnsServers(dnsServers)
.setDomains(DOMAINS)
.build();
mProxy = ProxyInfo.buildDirectProxy("test", 8888);
}
@Test
public void testConstructor() {
IpConfiguration ipConfig = new IpConfiguration();
checkEmpty(ipConfig);
assertIpConfigurationEqual(ipConfig, new IpConfiguration());
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
ipConfig.setStaticIpConfiguration(mStaticIpConfig);
ipConfig.setHttpProxy(mProxy);
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.STATIC);
ipConfig.setProxySettings(IpConfiguration.ProxySettings.PAC);
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.STATIC);
ipConfig.setProxySettings(IpConfiguration.ProxySettings.STATIC);
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
ipConfig.setProxySettings(IpConfiguration.ProxySettings.PAC);
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
ipConfig.setProxySettings(IpConfiguration.ProxySettings.PAC);
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
ipConfig.setProxySettings(IpConfiguration.ProxySettings.STATIC);
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
ipConfig.setProxySettings(IpConfiguration.ProxySettings.NONE);
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
}
private void checkEmpty(IpConfiguration config) {
assertEquals(IpConfiguration.IpAssignment.UNASSIGNED,
config.getIpAssignment().UNASSIGNED);
assertEquals(IpConfiguration.ProxySettings.UNASSIGNED,
config.getProxySettings().UNASSIGNED);
assertNull(config.getStaticIpConfiguration());
assertNull(config.getHttpProxy());
}
private void assertIpConfigurationEqual(IpConfiguration source, IpConfiguration target) {
assertEquals(source.getIpAssignment(), target.getIpAssignment());
assertEquals(source.getProxySettings(), target.getProxySettings());
assertEquals(source.getHttpProxy(), target.getHttpProxy());
assertEquals(source.getStaticIpConfiguration(), target.getStaticIpConfiguration());
}
@Test
public void testParcel() {
final IpConfiguration config = new IpConfiguration();
assertParcelSane(config, 4);
}
}

View File

@@ -0,0 +1,556 @@
/*
* Copyright (C) 2018 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 android.net.cts;
import static org.junit.Assert.assertArrayEquals;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
import android.net.IpSecTransform;
import android.platform.test.annotations.AppModeFull;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class IpSecBaseTest {
private static final String TAG = IpSecBaseTest.class.getSimpleName();
protected static final String IPV4_LOOPBACK = "127.0.0.1";
protected static final String IPV6_LOOPBACK = "::1";
protected static final String[] LOOPBACK_ADDRS = new String[] {IPV4_LOOPBACK, IPV6_LOOPBACK};
protected static final int[] DIRECTIONS =
new int[] {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT};
protected static final byte[] TEST_DATA = "Best test data ever!".getBytes();
protected static final int DATA_BUFFER_LEN = 4096;
protected static final int SOCK_TIMEOUT = 500;
private static final byte[] KEY_DATA = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23
};
protected static final byte[] AUTH_KEY = getKey(256);
protected static final byte[] CRYPT_KEY = getKey(256);
protected ConnectivityManager mCM;
protected IpSecManager mISM;
@Before
public void setUp() throws Exception {
mISM =
(IpSecManager)
InstrumentationRegistry.getContext()
.getSystemService(Context.IPSEC_SERVICE);
mCM =
(ConnectivityManager)
InstrumentationRegistry.getContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
}
protected static byte[] getKey(int bitLength) {
return Arrays.copyOf(KEY_DATA, bitLength / 8);
}
protected static int getDomain(InetAddress address) {
int domain;
if (address instanceof Inet6Address) {
domain = OsConstants.AF_INET6;
} else {
domain = OsConstants.AF_INET;
}
return domain;
}
protected static int getPort(FileDescriptor sock) throws Exception {
return ((InetSocketAddress) Os.getsockname(sock)).getPort();
}
public static interface GenericSocket extends AutoCloseable {
void send(byte[] data) throws Exception;
byte[] receive() throws Exception;
int getPort() throws Exception;
void close() throws Exception;
void applyTransportModeTransform(
IpSecManager ism, int direction, IpSecTransform transform) throws Exception;
void removeTransportModeTransforms(IpSecManager ism) throws Exception;
}
public static interface GenericTcpSocket extends GenericSocket {}
public static interface GenericUdpSocket extends GenericSocket {
void sendTo(byte[] data, InetAddress dstAddr, int port) throws Exception;
}
public abstract static class NativeSocket implements GenericSocket {
public FileDescriptor mFd;
public NativeSocket(FileDescriptor fd) {
mFd = fd;
}
@Override
public void send(byte[] data) throws Exception {
Os.write(mFd, data, 0, data.length);
}
@Override
public byte[] receive() throws Exception {
byte[] in = new byte[DATA_BUFFER_LEN];
AtomicInteger bytesRead = new AtomicInteger(-1);
Thread readSockThread = new Thread(() -> {
long startTime = System.currentTimeMillis();
while (bytesRead.get() < 0 && System.currentTimeMillis() < startTime + SOCK_TIMEOUT) {
try {
bytesRead.set(Os.recvfrom(mFd, in, 0, DATA_BUFFER_LEN, 0, null));
} catch (Exception e) {
Log.e(TAG, "Error encountered reading from socket", e);
}
}
});
readSockThread.start();
readSockThread.join(SOCK_TIMEOUT);
if (bytesRead.get() < 0) {
throw new IOException("No data received from socket");
}
return Arrays.copyOfRange(in, 0, bytesRead.get());
}
@Override
public int getPort() throws Exception {
return IpSecBaseTest.getPort(mFd);
}
@Override
public void close() throws Exception {
Os.close(mFd);
}
@Override
public void applyTransportModeTransform(
IpSecManager ism, int direction, IpSecTransform transform) throws Exception {
ism.applyTransportModeTransform(mFd, direction, transform);
}
@Override
public void removeTransportModeTransforms(IpSecManager ism) throws Exception {
ism.removeTransportModeTransforms(mFd);
}
}
public static class NativeTcpSocket extends NativeSocket implements GenericTcpSocket {
public NativeTcpSocket(FileDescriptor fd) {
super(fd);
}
}
public static class NativeUdpSocket extends NativeSocket implements GenericUdpSocket {
public NativeUdpSocket(FileDescriptor fd) {
super(fd);
}
@Override
public void sendTo(byte[] data, InetAddress dstAddr, int port) throws Exception {
Os.sendto(mFd, data, 0, data.length, 0, dstAddr, port);
}
}
public static class JavaUdpSocket implements GenericUdpSocket {
public final DatagramSocket mSocket;
public JavaUdpSocket(InetAddress localAddr, int port) {
try {
mSocket = new DatagramSocket(port, localAddr);
mSocket.setSoTimeout(SOCK_TIMEOUT);
} catch (SocketException e) {
// Fail loudly if we can't set up sockets properly. And without the timeout, we
// could easily end up in an endless wait.
throw new RuntimeException(e);
}
}
public JavaUdpSocket(InetAddress localAddr) {
try {
mSocket = new DatagramSocket(0, localAddr);
mSocket.setSoTimeout(SOCK_TIMEOUT);
} catch (SocketException e) {
// Fail loudly if we can't set up sockets properly. And without the timeout, we
// could easily end up in an endless wait.
throw new RuntimeException(e);
}
}
@Override
public void send(byte[] data) throws Exception {
mSocket.send(new DatagramPacket(data, data.length));
}
@Override
public void sendTo(byte[] data, InetAddress dstAddr, int port) throws Exception {
mSocket.send(new DatagramPacket(data, data.length, dstAddr, port));
}
@Override
public int getPort() throws Exception {
return mSocket.getLocalPort();
}
@Override
public void close() throws Exception {
mSocket.close();
}
@Override
public byte[] receive() throws Exception {
DatagramPacket data = new DatagramPacket(new byte[DATA_BUFFER_LEN], DATA_BUFFER_LEN);
mSocket.receive(data);
return Arrays.copyOfRange(data.getData(), 0, data.getLength());
}
@Override
public void applyTransportModeTransform(
IpSecManager ism, int direction, IpSecTransform transform) throws Exception {
ism.applyTransportModeTransform(mSocket, direction, transform);
}
@Override
public void removeTransportModeTransforms(IpSecManager ism) throws Exception {
ism.removeTransportModeTransforms(mSocket);
}
}
public static class JavaTcpSocket implements GenericTcpSocket {
public final Socket mSocket;
public JavaTcpSocket(Socket socket) {
mSocket = socket;
try {
mSocket.setSoTimeout(SOCK_TIMEOUT);
} catch (SocketException e) {
// Fail loudly if we can't set up sockets properly. And without the timeout, we
// could easily end up in an endless wait.
throw new RuntimeException(e);
}
}
@Override
public void send(byte[] data) throws Exception {
mSocket.getOutputStream().write(data);
}
@Override
public byte[] receive() throws Exception {
byte[] in = new byte[DATA_BUFFER_LEN];
int bytesRead = mSocket.getInputStream().read(in);
return Arrays.copyOfRange(in, 0, bytesRead);
}
@Override
public int getPort() throws Exception {
return mSocket.getLocalPort();
}
@Override
public void close() throws Exception {
mSocket.close();
}
@Override
public void applyTransportModeTransform(
IpSecManager ism, int direction, IpSecTransform transform) throws Exception {
ism.applyTransportModeTransform(mSocket, direction, transform);
}
@Override
public void removeTransportModeTransforms(IpSecManager ism) throws Exception {
ism.removeTransportModeTransforms(mSocket);
}
}
public static class SocketPair<T> {
public final T mLeftSock;
public final T mRightSock;
public SocketPair(T leftSock, T rightSock) {
mLeftSock = leftSock;
mRightSock = rightSock;
}
}
protected static void applyTransformBidirectionally(
IpSecManager ism, IpSecTransform transform, GenericSocket socket) throws Exception {
for (int direction : DIRECTIONS) {
socket.applyTransportModeTransform(ism, direction, transform);
}
}
public static SocketPair<NativeUdpSocket> getNativeUdpSocketPair(
InetAddress localAddr, IpSecManager ism, IpSecTransform transform, boolean connected)
throws Exception {
int domain = getDomain(localAddr);
NativeUdpSocket leftSock = new NativeUdpSocket(
Os.socket(domain, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP));
NativeUdpSocket rightSock = new NativeUdpSocket(
Os.socket(domain, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP));
for (NativeUdpSocket sock : new NativeUdpSocket[] {leftSock, rightSock}) {
applyTransformBidirectionally(ism, transform, sock);
Os.bind(sock.mFd, localAddr, 0);
}
if (connected) {
Os.connect(leftSock.mFd, localAddr, rightSock.getPort());
Os.connect(rightSock.mFd, localAddr, leftSock.getPort());
}
return new SocketPair<>(leftSock, rightSock);
}
public static SocketPair<NativeTcpSocket> getNativeTcpSocketPair(
InetAddress localAddr, IpSecManager ism, IpSecTransform transform) throws Exception {
int domain = getDomain(localAddr);
NativeTcpSocket server = new NativeTcpSocket(
Os.socket(domain, OsConstants.SOCK_STREAM, OsConstants.IPPROTO_TCP));
NativeTcpSocket client = new NativeTcpSocket(
Os.socket(domain, OsConstants.SOCK_STREAM, OsConstants.IPPROTO_TCP));
Os.bind(server.mFd, localAddr, 0);
applyTransformBidirectionally(ism, transform, server);
applyTransformBidirectionally(ism, transform, client);
Os.listen(server.mFd, 10);
Os.connect(client.mFd, localAddr, server.getPort());
NativeTcpSocket accepted = new NativeTcpSocket(Os.accept(server.mFd, null));
applyTransformBidirectionally(ism, transform, accepted);
server.close();
return new SocketPair<>(client, accepted);
}
public static SocketPair<JavaUdpSocket> getJavaUdpSocketPair(
InetAddress localAddr, IpSecManager ism, IpSecTransform transform, boolean connected)
throws Exception {
JavaUdpSocket leftSock = new JavaUdpSocket(localAddr);
JavaUdpSocket rightSock = new JavaUdpSocket(localAddr);
applyTransformBidirectionally(ism, transform, leftSock);
applyTransformBidirectionally(ism, transform, rightSock);
if (connected) {
leftSock.mSocket.connect(localAddr, rightSock.mSocket.getLocalPort());
rightSock.mSocket.connect(localAddr, leftSock.mSocket.getLocalPort());
}
return new SocketPair<>(leftSock, rightSock);
}
public static SocketPair<JavaTcpSocket> getJavaTcpSocketPair(
InetAddress localAddr, IpSecManager ism, IpSecTransform transform) throws Exception {
JavaTcpSocket clientSock = new JavaTcpSocket(new Socket());
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(localAddr, 0));
// While technically the client socket does not need to be bound, the OpenJDK implementation
// of Socket only allocates an FD when bind() or connect() or other similar methods are
// called. So we call bind to force the FD creation, so that we can apply a transform to it
// prior to socket connect.
clientSock.mSocket.bind(new InetSocketAddress(localAddr, 0));
// IpSecService doesn't support serverSockets at the moment; workaround using FD
FileDescriptor serverFd = serverSocket.getImpl().getFD$();
applyTransformBidirectionally(ism, transform, new NativeTcpSocket(serverFd));
applyTransformBidirectionally(ism, transform, clientSock);
clientSock.mSocket.connect(new InetSocketAddress(localAddr, serverSocket.getLocalPort()));
JavaTcpSocket acceptedSock = new JavaTcpSocket(serverSocket.accept());
applyTransformBidirectionally(ism, transform, acceptedSock);
serverSocket.close();
return new SocketPair<>(clientSock, acceptedSock);
}
private void checkSocketPair(GenericSocket left, GenericSocket right) throws Exception {
left.send(TEST_DATA);
assertArrayEquals(TEST_DATA, right.receive());
right.send(TEST_DATA);
assertArrayEquals(TEST_DATA, left.receive());
left.close();
right.close();
}
private void checkUnconnectedUdpSocketPair(
GenericUdpSocket left, GenericUdpSocket right, InetAddress localAddr) throws Exception {
left.sendTo(TEST_DATA, localAddr, right.getPort());
assertArrayEquals(TEST_DATA, right.receive());
right.sendTo(TEST_DATA, localAddr, left.getPort());
assertArrayEquals(TEST_DATA, left.receive());
left.close();
right.close();
}
protected static IpSecTransform buildIpSecTransform(
Context context,
IpSecManager.SecurityParameterIndex spi,
IpSecManager.UdpEncapsulationSocket encapSocket,
InetAddress remoteAddr)
throws Exception {
IpSecTransform.Builder builder =
new IpSecTransform.Builder(context)
.setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
.setAuthentication(
new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256,
AUTH_KEY,
AUTH_KEY.length * 4));
if (encapSocket != null) {
builder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
}
return builder.buildTransportModeTransform(remoteAddr, spi);
}
private IpSecTransform buildDefaultTransform(InetAddress localAddr) throws Exception {
try (IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(localAddr)) {
return buildIpSecTransform(InstrumentationRegistry.getContext(), spi, null, localAddr);
}
}
@Test
@AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testJavaTcpSocketPair() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
try (IpSecTransform transform = buildDefaultTransform(local)) {
SocketPair<JavaTcpSocket> sockets = getJavaTcpSocketPair(local, mISM, transform);
checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
}
}
}
@Test
@AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testJavaUdpSocketPair() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
try (IpSecTransform transform = buildDefaultTransform(local)) {
SocketPair<JavaUdpSocket> sockets =
getJavaUdpSocketPair(local, mISM, transform, true);
checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
}
}
}
@Test
@AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testJavaUdpSocketPairUnconnected() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
try (IpSecTransform transform = buildDefaultTransform(local)) {
SocketPair<JavaUdpSocket> sockets =
getJavaUdpSocketPair(local, mISM, transform, false);
checkUnconnectedUdpSocketPair(sockets.mLeftSock, sockets.mRightSock, local);
}
}
}
@Test
@AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testNativeTcpSocketPair() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
try (IpSecTransform transform = buildDefaultTransform(local)) {
SocketPair<NativeTcpSocket> sockets =
getNativeTcpSocketPair(local, mISM, transform);
checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
}
}
}
@Test
@AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testNativeUdpSocketPair() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
try (IpSecTransform transform = buildDefaultTransform(local)) {
SocketPair<NativeUdpSocket> sockets =
getNativeUdpSocketPair(local, mISM, transform, true);
checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
}
}
}
@Test
@AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testNativeUdpSocketPairUnconnected() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
try (IpSecTransform transform = buildDefaultTransform(local)) {
SocketPair<NativeUdpSocket> sockets =
getNativeUdpSocketPair(local, mISM, transform, false);
checkUnconnectedUdpSocketPair(sockets.mLeftSock, sockets.mRightSock, local);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,899 @@
/*
* Copyright (C) 2018 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 android.net.cts;
import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
import static android.net.IpSecManager.UdpEncapsulationSocket;
import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
import static android.net.cts.PacketUtils.BytePayload;
import static android.net.cts.PacketUtils.EspHeader;
import static android.net.cts.PacketUtils.IP4_HDRLEN;
import static android.net.cts.PacketUtils.IP6_HDRLEN;
import static android.net.cts.PacketUtils.IpHeader;
import static android.net.cts.PacketUtils.UDP_HDRLEN;
import static android.net.cts.PacketUtils.UdpHeader;
import static android.net.cts.PacketUtils.getIpHeader;
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
import android.net.IpSecTransform;
import android.net.LinkAddress;
import android.net.Network;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.net.cts.PacketUtils.Payload;
import android.net.cts.util.CtsNetUtils;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
public class IpSecManagerTunnelTest extends IpSecBaseTest {
private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName();
private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
private static final InetAddress REMOTE_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.2");
private static final InetAddress LOCAL_OUTER_6 =
InetAddress.parseNumericAddress("2001:db8:1::1");
private static final InetAddress REMOTE_OUTER_6 =
InetAddress.parseNumericAddress("2001:db8:1::2");
private static final InetAddress LOCAL_INNER_4 =
InetAddress.parseNumericAddress("198.51.100.1");
private static final InetAddress REMOTE_INNER_4 =
InetAddress.parseNumericAddress("198.51.100.2");
private static final InetAddress LOCAL_INNER_6 =
InetAddress.parseNumericAddress("2001:db8:2::1");
private static final InetAddress REMOTE_INNER_6 =
InetAddress.parseNumericAddress("2001:db8:2::2");
private static final int IP4_PREFIX_LEN = 32;
private static final int IP6_PREFIX_LEN = 128;
private static final int TIMEOUT_MS = 500;
// Static state to reduce setup/teardown
private static ConnectivityManager sCM;
private static TestNetworkManager sTNM;
private static ParcelFileDescriptor sTunFd;
private static TestNetworkCallback sTunNetworkCallback;
private static Network sTunNetwork;
private static TunUtils sTunUtils;
private static Context sContext = InstrumentationRegistry.getContext();
private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
@BeforeClass
public static void setUpBeforeClass() throws Exception {
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.adoptShellPermissionIdentity();
sCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
// Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
// a standard permission is insufficient. So we shell out the appop, to give us the
// right appop permissions.
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
TestNetworkInterface testIface =
sTNM.createTunInterface(
new LinkAddress[] {
new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)
});
sTunFd = testIface.getFileDescriptor();
sTunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName());
sTunNetworkCallback.waitForAvailable();
sTunNetwork = sTunNetworkCallback.currentNetwork;
sTunUtils = new TunUtils(sTunFd);
}
@Before
@Override
public void setUp() throws Exception {
super.setUp();
// Set to true before every run; some tests flip this.
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
// Clear sTunUtils state
sTunUtils.reset();
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
sCM.unregisterNetworkCallback(sTunNetworkCallback);
sTNM.teardownTestNetwork(sTunNetwork);
sTunFd.close();
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.dropShellPermissionIdentity();
}
@Test
public void testSecurityExceptionCreateTunnelInterfaceWithoutAppop() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
// Ensure we don't have the appop. Permission is not requested in the Manifest
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
// Security exceptions are thrown regardless of IPv4/IPv6. Just test one
try {
mISM.createIpSecTunnelInterface(LOCAL_INNER_6, REMOTE_INNER_6, sTunNetwork);
fail("Did not throw SecurityException for Tunnel creation without appop");
} catch (SecurityException expected) {
}
}
@Test
public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
// Ensure we don't have the appop. Permission is not requested in the Manifest
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
// Security exceptions are thrown regardless of IPv4/IPv6. Just test one
try (IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(LOCAL_INNER_4);
IpSecTransform transform =
new IpSecTransform.Builder(sContext)
.buildTunnelModeTransform(REMOTE_INNER_4, spi)) {
fail("Did not throw SecurityException for Transform creation without appop");
} catch (SecurityException expected) {
}
}
/* Test runnables for callbacks after IPsec tunnels are set up. */
private abstract class IpSecTunnelTestRunnable {
/**
* Runs the test code, and returns the inner socket port, if any.
*
* @param ipsecNetwork The IPsec Interface based Network for binding sockets on
* @return the integer port of the inner socket if outbound, or 0 if inbound
* IpSecTunnelTestRunnable
* @throws Exception if any part of the test failed.
*/
public abstract int run(Network ipsecNetwork) throws Exception;
}
private int getPacketSize(
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN;
// Inner Transport mode packet size
if (transportInTunnelMode) {
expectedPacketSize =
PacketUtils.calculateEspPacketSize(
expectedPacketSize,
AES_CBC_IV_LEN,
AES_CBC_BLK_SIZE,
AUTH_KEY.length * 4);
}
// Inner IP Header
expectedPacketSize += innerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
// Tunnel mode transform size
expectedPacketSize =
PacketUtils.calculateEspPacketSize(
expectedPacketSize, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, AUTH_KEY.length * 4);
// UDP encap size
expectedPacketSize += useEncap ? UDP_HDRLEN : 0;
// Outer IP Header
expectedPacketSize += outerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
return expectedPacketSize;
}
private interface IpSecTunnelTestRunnableFactory {
IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
boolean transportInTunnelMode,
int spi,
InetAddress localInner,
InetAddress remoteInner,
InetAddress localOuter,
InetAddress remoteOuter,
IpSecTransform inTransportTransform,
IpSecTransform outTransportTransform,
int encapPort,
int innerSocketPort,
int expectedPacketSize)
throws Exception;
}
private class OutputIpSecTunnelTestRunnableFactory implements IpSecTunnelTestRunnableFactory {
public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
boolean transportInTunnelMode,
int spi,
InetAddress localInner,
InetAddress remoteInner,
InetAddress localOuter,
InetAddress remoteOuter,
IpSecTransform inTransportTransform,
IpSecTransform outTransportTransform,
int encapPort,
int unusedInnerSocketPort,
int expectedPacketSize) {
return new IpSecTunnelTestRunnable() {
@Override
public int run(Network ipsecNetwork) throws Exception {
// Build a socket and send traffic
JavaUdpSocket socket = new JavaUdpSocket(localInner);
ipsecNetwork.bindSocket(socket.mSocket);
int innerSocketPort = socket.getPort();
// For Transport-In-Tunnel mode, apply transform to socket
if (transportInTunnelMode) {
mISM.applyTransportModeTransform(
socket.mSocket, IpSecManager.DIRECTION_IN, inTransportTransform);
mISM.applyTransportModeTransform(
socket.mSocket, IpSecManager.DIRECTION_OUT, outTransportTransform);
}
socket.sendTo(TEST_DATA, remoteInner, socket.getPort());
// Verify that an encrypted packet is sent. As of right now, checking encrypted
// body is not possible, due to the test not knowing some of the fields of the
// inner IP header (flow label, flags, etc)
sTunUtils.awaitEspPacketNoPlaintext(
spi, TEST_DATA, encapPort != 0, expectedPacketSize);
socket.close();
return innerSocketPort;
}
};
}
}
private class InputReflectedIpSecTunnelTestRunnableFactory
implements IpSecTunnelTestRunnableFactory {
public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
boolean transportInTunnelMode,
int spi,
InetAddress localInner,
InetAddress remoteInner,
InetAddress localOuter,
InetAddress remoteOuter,
IpSecTransform inTransportTransform,
IpSecTransform outTransportTransform,
int encapPort,
int innerSocketPort,
int expectedPacketSize)
throws Exception {
return new IpSecTunnelTestRunnable() {
@Override
public int run(Network ipsecNetwork) throws Exception {
// Build a socket and receive traffic
JavaUdpSocket socket = new JavaUdpSocket(localInner, innerSocketPort);
ipsecNetwork.bindSocket(socket.mSocket);
// For Transport-In-Tunnel mode, apply transform to socket
if (transportInTunnelMode) {
mISM.applyTransportModeTransform(
socket.mSocket, IpSecManager.DIRECTION_IN, outTransportTransform);
mISM.applyTransportModeTransform(
socket.mSocket, IpSecManager.DIRECTION_OUT, inTransportTransform);
}
sTunUtils.reflectPackets();
// Receive packet from socket, and validate that the payload is correct
receiveAndValidatePacket(socket);
socket.close();
return 0;
}
};
}
}
private class InputPacketGeneratorIpSecTunnelTestRunnableFactory
implements IpSecTunnelTestRunnableFactory {
public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
boolean transportInTunnelMode,
int spi,
InetAddress localInner,
InetAddress remoteInner,
InetAddress localOuter,
InetAddress remoteOuter,
IpSecTransform inTransportTransform,
IpSecTransform outTransportTransform,
int encapPort,
int innerSocketPort,
int expectedPacketSize)
throws Exception {
return new IpSecTunnelTestRunnable() {
@Override
public int run(Network ipsecNetwork) throws Exception {
// Build a socket and receive traffic
JavaUdpSocket socket = new JavaUdpSocket(localInner);
ipsecNetwork.bindSocket(socket.mSocket);
// For Transport-In-Tunnel mode, apply transform to socket
if (transportInTunnelMode) {
mISM.applyTransportModeTransform(
socket.mSocket, IpSecManager.DIRECTION_IN, outTransportTransform);
mISM.applyTransportModeTransform(
socket.mSocket, IpSecManager.DIRECTION_OUT, inTransportTransform);
}
byte[] pkt;
if (transportInTunnelMode) {
pkt =
getTransportInTunnelModePacket(
spi,
spi,
remoteInner,
localInner,
remoteOuter,
localOuter,
socket.getPort(),
encapPort);
} else {
pkt =
getTunnelModePacket(
spi,
remoteInner,
localInner,
remoteOuter,
localOuter,
socket.getPort(),
encapPort);
}
sTunUtils.injectPacket(pkt);
// Receive packet from socket, and validate
receiveAndValidatePacket(socket);
socket.close();
return 0;
}
};
}
}
private void checkTunnelOutput(
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
throws Exception {
checkTunnel(
innerFamily,
outerFamily,
useEncap,
transportInTunnelMode,
new OutputIpSecTunnelTestRunnableFactory());
}
private void checkTunnelInput(
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
throws Exception {
checkTunnel(
innerFamily,
outerFamily,
useEncap,
transportInTunnelMode,
new InputPacketGeneratorIpSecTunnelTestRunnableFactory());
}
/**
* Validates that the kernel can talk to itself.
*
* <p>This test takes an outbound IPsec packet, reflects it (by flipping IP src/dst), and
* injects it back into the TUN. This test then verifies that a packet with the correct payload
* is found on the specified socket/port.
*/
public void checkTunnelReflected(
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
throws Exception {
InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
InetAddress localOuter = outerFamily == AF_INET ? LOCAL_OUTER_4 : LOCAL_OUTER_6;
InetAddress remoteOuter = outerFamily == AF_INET ? REMOTE_OUTER_4 : REMOTE_OUTER_6;
// Preselect both SPI and encap port, to be used for both inbound and outbound tunnels.
int spi = getRandomSpi(localOuter, remoteOuter);
int expectedPacketSize =
getPacketSize(innerFamily, outerFamily, useEncap, transportInTunnelMode);
try (IpSecManager.SecurityParameterIndex inTransportSpi =
mISM.allocateSecurityParameterIndex(localInner, spi);
IpSecManager.SecurityParameterIndex outTransportSpi =
mISM.allocateSecurityParameterIndex(remoteInner, spi);
IpSecTransform inTransportTransform =
buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
IpSecTransform outTransportTransform =
buildIpSecTransform(sContext, outTransportSpi, null, localInner);
UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
// Run output direction tests
IpSecTunnelTestRunnable outputIpSecTunnelTestRunnable =
new OutputIpSecTunnelTestRunnableFactory()
.getIpSecTunnelTestRunnable(
transportInTunnelMode,
spi,
localInner,
remoteInner,
localOuter,
remoteOuter,
inTransportTransform,
outTransportTransform,
useEncap ? encapSocket.getPort() : 0,
0,
expectedPacketSize);
int innerSocketPort =
buildTunnelNetworkAndRunTests(
localInner,
remoteInner,
localOuter,
remoteOuter,
spi,
useEncap ? encapSocket : null,
outputIpSecTunnelTestRunnable);
// Input direction tests, with matching inner socket ports.
IpSecTunnelTestRunnable inputIpSecTunnelTestRunnable =
new InputReflectedIpSecTunnelTestRunnableFactory()
.getIpSecTunnelTestRunnable(
transportInTunnelMode,
spi,
remoteInner,
localInner,
localOuter,
remoteOuter,
inTransportTransform,
outTransportTransform,
useEncap ? encapSocket.getPort() : 0,
innerSocketPort,
expectedPacketSize);
buildTunnelNetworkAndRunTests(
remoteInner,
localInner,
localOuter,
remoteOuter,
spi,
useEncap ? encapSocket : null,
inputIpSecTunnelTestRunnable);
}
}
public void checkTunnel(
int innerFamily,
int outerFamily,
boolean useEncap,
boolean transportInTunnelMode,
IpSecTunnelTestRunnableFactory factory)
throws Exception {
InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
InetAddress localOuter = outerFamily == AF_INET ? LOCAL_OUTER_4 : LOCAL_OUTER_6;
InetAddress remoteOuter = outerFamily == AF_INET ? REMOTE_OUTER_4 : REMOTE_OUTER_6;
// Preselect both SPI and encap port, to be used for both inbound and outbound tunnels.
// Re-uses the same SPI to ensure that even in cases of symmetric SPIs shared across tunnel
// and transport mode, packets are encrypted/decrypted properly based on the src/dst.
int spi = getRandomSpi(localOuter, remoteOuter);
int expectedPacketSize =
getPacketSize(innerFamily, outerFamily, useEncap, transportInTunnelMode);
try (IpSecManager.SecurityParameterIndex inTransportSpi =
mISM.allocateSecurityParameterIndex(localInner, spi);
IpSecManager.SecurityParameterIndex outTransportSpi =
mISM.allocateSecurityParameterIndex(remoteInner, spi);
IpSecTransform inTransportTransform =
buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
IpSecTransform outTransportTransform =
buildIpSecTransform(sContext, outTransportSpi, null, localInner);
UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
buildTunnelNetworkAndRunTests(
localInner,
remoteInner,
localOuter,
remoteOuter,
spi,
useEncap ? encapSocket : null,
factory.getIpSecTunnelTestRunnable(
transportInTunnelMode,
spi,
localInner,
remoteInner,
localOuter,
remoteOuter,
inTransportTransform,
outTransportTransform,
useEncap ? encapSocket.getPort() : 0,
0,
expectedPacketSize));
}
}
private int buildTunnelNetworkAndRunTests(
InetAddress localInner,
InetAddress remoteInner,
InetAddress localOuter,
InetAddress remoteOuter,
int spi,
UdpEncapsulationSocket encapSocket,
IpSecTunnelTestRunnable test)
throws Exception {
int innerPrefixLen = localInner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN;
TestNetworkCallback testNetworkCb = null;
int innerSocketPort;
try (IpSecManager.SecurityParameterIndex inSpi =
mISM.allocateSecurityParameterIndex(localOuter, spi);
IpSecManager.SecurityParameterIndex outSpi =
mISM.allocateSecurityParameterIndex(remoteOuter, spi);
IpSecManager.IpSecTunnelInterface tunnelIface =
mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) {
// Build the test network
tunnelIface.addAddress(localInner, innerPrefixLen);
testNetworkCb = mCtsNetUtils.setupAndGetTestNetwork(tunnelIface.getInterfaceName());
testNetworkCb.waitForAvailable();
Network testNetwork = testNetworkCb.currentNetwork;
// Check interface was created
assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
// Verify address was added
final NetworkInterface netIface = NetworkInterface.getByInetAddress(localInner);
assertNotNull(netIface);
assertEquals(tunnelIface.getInterfaceName(), netIface.getDisplayName());
// Configure Transform parameters
IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(sContext);
transformBuilder.setEncryption(
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY));
transformBuilder.setAuthentication(
new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
if (encapSocket != null) {
transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
}
// Apply transform and check that traffic is properly encrypted
try (IpSecTransform inTransform =
transformBuilder.buildTunnelModeTransform(remoteOuter, inSpi);
IpSecTransform outTransform =
transformBuilder.buildTunnelModeTransform(localOuter, outSpi)) {
mISM.applyTunnelModeTransform(tunnelIface, IpSecManager.DIRECTION_IN, inTransform);
mISM.applyTunnelModeTransform(
tunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
innerSocketPort = test.run(testNetwork);
}
// Teardown the test network
sTNM.teardownTestNetwork(testNetwork);
// Remove addresses and check that interface is still present, but fails lookup-by-addr
tunnelIface.removeAddress(localInner, innerPrefixLen);
assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
assertNull(NetworkInterface.getByInetAddress(localInner));
// Check interface was cleaned up
tunnelIface.close();
assertNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
} finally {
if (testNetworkCb != null) {
sCM.unregisterNetworkCallback(testNetworkCb);
}
}
return innerSocketPort;
}
private static void receiveAndValidatePacket(JavaUdpSocket socket) throws Exception {
byte[] socketResponseBytes = socket.receive();
assertArrayEquals(TEST_DATA, socketResponseBytes);
}
private int getRandomSpi(InetAddress localOuter, InetAddress remoteOuter) throws Exception {
// Try to allocate both in and out SPIs using the same requested SPI value.
try (IpSecManager.SecurityParameterIndex inSpi =
mISM.allocateSecurityParameterIndex(localOuter);
IpSecManager.SecurityParameterIndex outSpi =
mISM.allocateSecurityParameterIndex(remoteOuter, inSpi.getSpi()); ) {
return inSpi.getSpi();
}
}
private EspHeader buildTransportModeEspPacket(
int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception {
IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload);
return new EspHeader(
payload.getProtocolId(),
spi,
1, // sequence number
CRYPT_KEY, // Same key for auth and crypt
payload.getPacketBytes(preEspIpHeader));
}
private EspHeader buildTunnelModeEspPacket(
int spi,
InetAddress srcInner,
InetAddress dstInner,
InetAddress srcOuter,
InetAddress dstOuter,
int port,
int encapPort,
Payload payload)
throws Exception {
IpHeader innerIp = getIpHeader(payload.getProtocolId(), srcInner, dstInner, payload);
return new EspHeader(
innerIp.getProtocolId(),
spi,
1, // sequence number
CRYPT_KEY, // Same key for auth and crypt
innerIp.getPacketBytes());
}
private IpHeader maybeEncapPacket(
InetAddress src, InetAddress dst, int encapPort, EspHeader espPayload)
throws Exception {
Payload payload = espPayload;
if (encapPort != 0) {
payload = new UdpHeader(encapPort, encapPort, espPayload);
}
return getIpHeader(payload.getProtocolId(), src, dst, payload);
}
private byte[] getTunnelModePacket(
int spi,
InetAddress srcInner,
InetAddress dstInner,
InetAddress srcOuter,
InetAddress dstOuter,
int port,
int encapPort)
throws Exception {
UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
EspHeader espPayload =
buildTunnelModeEspPacket(
spi, srcInner, dstInner, srcOuter, dstOuter, port, encapPort, udp);
return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
}
private byte[] getTransportInTunnelModePacket(
int spiInner,
int spiOuter,
InetAddress srcInner,
InetAddress dstInner,
InetAddress srcOuter,
InetAddress dstOuter,
int port,
int encapPort)
throws Exception {
UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
EspHeader espPayload = buildTransportModeEspPacket(spiInner, srcInner, dstInner, port, udp);
espPayload =
buildTunnelModeEspPacket(
spiOuter,
srcInner,
dstInner,
srcOuter,
dstOuter,
port,
encapPort,
espPayload);
return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
}
// Transport-in-Tunnel mode tests
@Test
public void testTransportInTunnelModeV4InV4() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET, false, true);
checkTunnelInput(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV4InV4Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV4InV4UdpEncap() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET, true, true);
checkTunnelInput(AF_INET, AF_INET, true, true);
}
@Test
public void testTransportInTunnelModeV4InV4UdpEncapReflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV4InV6() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET6, false, true);
checkTunnelInput(AF_INET, AF_INET6, false, true);
}
@Test
public void testTransportInTunnelModeV4InV6Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV6InV4() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET, false, true);
checkTunnelInput(AF_INET6, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV6InV4Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV6InV4UdpEncap() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET, true, true);
checkTunnelInput(AF_INET6, AF_INET, true, true);
}
@Test
public void testTransportInTunnelModeV6InV4UdpEncapReflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV6InV6() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET6, false, true);
checkTunnelInput(AF_INET, AF_INET6, false, true);
}
@Test
public void testTransportInTunnelModeV6InV6Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
// Tunnel mode tests
@Test
public void testTunnelV4InV4() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET, false, false);
checkTunnelInput(AF_INET, AF_INET, false, false);
}
@Test
public void testTunnelV4InV4Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, false);
}
@Test
public void testTunnelV4InV4UdpEncap() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET, true, false);
checkTunnelInput(AF_INET, AF_INET, true, false);
}
@Test
public void testTunnelV4InV4UdpEncapReflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, true, false);
}
@Test
public void testTunnelV4InV6() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET6, false, false);
checkTunnelInput(AF_INET, AF_INET6, false, false);
}
@Test
public void testTunnelV4InV6Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET6, false, false);
}
@Test
public void testTunnelV6InV4() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET, false, false);
checkTunnelInput(AF_INET6, AF_INET, false, false);
}
@Test
public void testTunnelV6InV4Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET6, AF_INET, false, false);
}
@Test
public void testTunnelV6InV4UdpEncap() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET, true, false);
checkTunnelInput(AF_INET6, AF_INET, true, false);
}
@Test
public void testTunnelV6InV4UdpEncapReflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET6, AF_INET, true, false);
}
@Test
public void testTunnelV6InV6() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET6, false, false);
checkTunnelInput(AF_INET6, AF_INET6, false, false);
}
@Test
public void testTunnelV6InV6Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET6, AF_INET6, false, false);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2009 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 android.net.cts;
import junit.framework.TestCase;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
public class LocalServerSocketTest extends TestCase {
public void testLocalServerSocket() throws IOException {
String address = "com.android.net.LocalServerSocketTest_testLocalServerSocket";
LocalServerSocket localServerSocket = new LocalServerSocket(address);
assertNotNull(localServerSocket.getLocalSocketAddress());
// create client socket
LocalSocket clientSocket = new LocalSocket();
// establish connection between client and server
clientSocket.connect(new LocalSocketAddress(address));
LocalSocket serverSocket = localServerSocket.accept();
assertTrue(serverSocket.isConnected());
assertTrue(serverSocket.isBound());
// send data from client to server
OutputStream clientOutStream = clientSocket.getOutputStream();
clientOutStream.write(12);
InputStream serverInStream = serverSocket.getInputStream();
assertEquals(12, serverInStream.read());
// send data from server to client
OutputStream serverOutStream = serverSocket.getOutputStream();
serverOutStream.write(3);
InputStream clientInStream = clientSocket.getInputStream();
assertEquals(3, clientInStream.read());
// close server socket
assertNotNull(localServerSocket.getFileDescriptor());
localServerSocket.close();
assertNull(localServerSocket.getFileDescriptor());
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2008 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 android.net.cts;
import android.net.LocalSocketAddress;
import android.net.LocalSocketAddress.Namespace;
import android.test.AndroidTestCase;
public class LocalSocketAddressTest extends AndroidTestCase {
public void testNewLocalSocketAddressWithDefaultNamespace() {
// default namespace
LocalSocketAddress localSocketAddress = new LocalSocketAddress("name");
assertEquals("name", localSocketAddress.getName());
assertEquals(Namespace.ABSTRACT, localSocketAddress.getNamespace());
// specify the namespace
LocalSocketAddress localSocketAddress2 =
new LocalSocketAddress("name2", Namespace.ABSTRACT);
assertEquals("name2", localSocketAddress2.getName());
assertEquals(Namespace.ABSTRACT, localSocketAddress2.getNamespace());
LocalSocketAddress localSocketAddress3 =
new LocalSocketAddress("name3", Namespace.FILESYSTEM);
assertEquals("name3", localSocketAddress3.getName());
assertEquals(Namespace.FILESYSTEM, localSocketAddress3.getNamespace());
LocalSocketAddress localSocketAddress4 =
new LocalSocketAddress("name4", Namespace.RESERVED);
assertEquals("name4", localSocketAddress4.getName());
assertEquals(Namespace.RESERVED, localSocketAddress4.getNamespace());
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2009 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 android.net.cts;
import android.net.LocalSocketAddress.Namespace;
import android.test.AndroidTestCase;
public class LocalSocketAddress_NamespaceTest extends AndroidTestCase {
public void testValueOf() {
assertEquals(Namespace.ABSTRACT, Namespace.valueOf("ABSTRACT"));
assertEquals(Namespace.RESERVED, Namespace.valueOf("RESERVED"));
assertEquals(Namespace.FILESYSTEM, Namespace.valueOf("FILESYSTEM"));
}
public void testValues() {
Namespace[] expected = Namespace.values();
assertEquals(Namespace.ABSTRACT, expected[0]);
assertEquals(Namespace.RESERVED, expected[1]);
assertEquals(Namespace.FILESYSTEM, expected[2]);
}
}

View File

@@ -0,0 +1,470 @@
/*
* Copyright (C) 2008 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 android.net.cts;
import junit.framework.TestCase;
import android.net.Credentials;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.system.Os;
import android.system.OsConstants;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class LocalSocketTest extends TestCase {
private final static String ADDRESS_PREFIX = "com.android.net.LocalSocketTest";
public void testLocalConnections() throws IOException {
String address = ADDRESS_PREFIX + "_testLocalConnections";
// create client and server socket
LocalServerSocket localServerSocket = new LocalServerSocket(address);
LocalSocket clientSocket = new LocalSocket();
// establish connection between client and server
LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
assertFalse(clientSocket.isConnected());
clientSocket.connect(locSockAddr);
assertTrue(clientSocket.isConnected());
LocalSocket serverSocket = localServerSocket.accept();
assertTrue(serverSocket.isConnected());
assertTrue(serverSocket.isBound());
try {
serverSocket.bind(localServerSocket.getLocalSocketAddress());
fail("Cannot bind a LocalSocket from accept()");
} catch (IOException expected) {
}
try {
serverSocket.connect(locSockAddr);
fail("Cannot connect a LocalSocket from accept()");
} catch (IOException expected) {
}
Credentials credent = clientSocket.getPeerCredentials();
assertTrue(0 != credent.getPid());
// send data from client to server
OutputStream clientOutStream = clientSocket.getOutputStream();
clientOutStream.write(12);
InputStream serverInStream = serverSocket.getInputStream();
assertEquals(12, serverInStream.read());
//send data from server to client
OutputStream serverOutStream = serverSocket.getOutputStream();
serverOutStream.write(3);
InputStream clientInStream = clientSocket.getInputStream();
assertEquals(3, clientInStream.read());
// Test sending and receiving file descriptors
clientSocket.setFileDescriptorsForSend(new FileDescriptor[]{FileDescriptor.in});
clientOutStream.write(32);
assertEquals(32, serverInStream.read());
FileDescriptor[] out = serverSocket.getAncillaryFileDescriptors();
assertEquals(1, out.length);
FileDescriptor fd = clientSocket.getFileDescriptor();
assertTrue(fd.valid());
//shutdown input stream of client
clientSocket.shutdownInput();
assertEquals(-1, clientInStream.read());
//shutdown output stream of client
clientSocket.shutdownOutput();
try {
clientOutStream.write(10);
fail("testLocalSocket shouldn't come to here");
} catch (IOException e) {
// expected
}
//shutdown input stream of server
serverSocket.shutdownInput();
assertEquals(-1, serverInStream.read());
//shutdown output stream of server
serverSocket.shutdownOutput();
try {
serverOutStream.write(10);
fail("testLocalSocket shouldn't come to here");
} catch (IOException e) {
// expected
}
//close client socket
clientSocket.close();
try {
clientInStream.read();
fail("testLocalSocket shouldn't come to here");
} catch (IOException e) {
// expected
}
//close server socket
serverSocket.close();
try {
serverInStream.read();
fail("testLocalSocket shouldn't come to here");
} catch (IOException e) {
// expected
}
}
public void testAccessors() throws IOException {
String address = ADDRESS_PREFIX + "_testAccessors";
LocalSocket socket = new LocalSocket();
LocalSocketAddress addr = new LocalSocketAddress(address);
assertFalse(socket.isBound());
socket.bind(addr);
assertTrue(socket.isBound());
assertEquals(addr, socket.getLocalSocketAddress());
String str = socket.toString();
assertTrue(str.contains("impl:android.net.LocalSocketImpl"));
socket.setReceiveBufferSize(1999);
assertEquals(1999 << 1, socket.getReceiveBufferSize());
socket.setSendBufferSize(3998);
assertEquals(3998 << 1, socket.getSendBufferSize());
assertEquals(0, socket.getSoTimeout());
socket.setSoTimeout(1996);
assertTrue(socket.getSoTimeout() > 0);
try {
socket.getRemoteSocketAddress();
fail("testLocalSocketSecondary shouldn't come to here");
} catch (UnsupportedOperationException e) {
// expected
}
try {
socket.isClosed();
fail("testLocalSocketSecondary shouldn't come to here");
} catch (UnsupportedOperationException e) {
// expected
}
try {
socket.isInputShutdown();
fail("testLocalSocketSecondary shouldn't come to here");
} catch (UnsupportedOperationException e) {
// expected
}
try {
socket.isOutputShutdown();
fail("testLocalSocketSecondary shouldn't come to here");
} catch (UnsupportedOperationException e) {
// expected
}
try {
socket.connect(addr, 2005);
fail("testLocalSocketSecondary shouldn't come to here");
} catch (UnsupportedOperationException e) {
// expected
}
socket.close();
}
// http://b/31205169
public void testSetSoTimeout_readTimeout() throws Exception {
String address = ADDRESS_PREFIX + "_testSetSoTimeout_readTimeout";
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
final LocalSocket clientSocket = socketPair.clientSocket;
// Set the timeout in millis.
int timeoutMillis = 1000;
clientSocket.setSoTimeout(timeoutMillis);
// Avoid blocking the test run if timeout doesn't happen by using a separate thread.
Callable<Result> reader = () -> {
try {
clientSocket.getInputStream().read();
return Result.noException("Did not block");
} catch (IOException e) {
return Result.exception(e);
}
};
// Allow the configured timeout, plus some slop.
int allowedTime = timeoutMillis + 2000;
Result result = runInSeparateThread(allowedTime, reader);
// Check the message was a timeout, it's all we have to go on.
String expectedMessage = Os.strerror(OsConstants.EAGAIN);
result.assertThrewIOException(expectedMessage);
}
}
// http://b/31205169
public void testSetSoTimeout_writeTimeout() throws Exception {
String address = ADDRESS_PREFIX + "_testSetSoTimeout_writeTimeout";
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
final LocalSocket clientSocket = socketPair.clientSocket;
// Set the timeout in millis.
int timeoutMillis = 1000;
clientSocket.setSoTimeout(timeoutMillis);
// Set a small buffer size so we know we can flood it.
clientSocket.setSendBufferSize(100);
final int bufferSize = clientSocket.getSendBufferSize();
// Avoid blocking the test run if timeout doesn't happen by using a separate thread.
Callable<Result> writer = () -> {
try {
byte[] toWrite = new byte[bufferSize * 2];
clientSocket.getOutputStream().write(toWrite);
return Result.noException("Did not block");
} catch (IOException e) {
return Result.exception(e);
}
};
// Allow the configured timeout, plus some slop.
int allowedTime = timeoutMillis + 2000;
Result result = runInSeparateThread(allowedTime, writer);
// Check the message was a timeout, it's all we have to go on.
String expectedMessage = Os.strerror(OsConstants.EAGAIN);
result.assertThrewIOException(expectedMessage);
}
}
public void testAvailable() throws Exception {
String address = ADDRESS_PREFIX + "_testAvailable";
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
LocalSocket clientSocket = socketPair.clientSocket;
LocalSocket serverSocket = socketPair.serverSocket.accept();
OutputStream clientOutputStream = clientSocket.getOutputStream();
InputStream serverInputStream = serverSocket.getInputStream();
assertEquals(0, serverInputStream.available());
byte[] buffer = new byte[50];
clientOutputStream.write(buffer);
assertEquals(50, serverInputStream.available());
InputStream clientInputStream = clientSocket.getInputStream();
OutputStream serverOutputStream = serverSocket.getOutputStream();
assertEquals(0, clientInputStream.available());
serverOutputStream.write(buffer);
assertEquals(50, serverInputStream.available());
serverSocket.close();
}
}
// http://b/34095140
public void testLocalSocketCreatedFromFileDescriptor() throws Exception {
String address = ADDRESS_PREFIX + "_testLocalSocketCreatedFromFileDescriptor";
// Establish connection between a local client and server to get a valid client socket file
// descriptor.
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
// Extract the client FileDescriptor we can use.
FileDescriptor fileDescriptor = socketPair.clientSocket.getFileDescriptor();
assertTrue(fileDescriptor.valid());
// Create the LocalSocket we want to test.
LocalSocket clientSocketCreatedFromFileDescriptor =
LocalSocket.createConnectedLocalSocket(fileDescriptor);
assertTrue(clientSocketCreatedFromFileDescriptor.isConnected());
assertTrue(clientSocketCreatedFromFileDescriptor.isBound());
// Test the LocalSocket can be used for communication.
LocalSocket serverSocket = socketPair.serverSocket.accept();
OutputStream clientOutputStream =
clientSocketCreatedFromFileDescriptor.getOutputStream();
InputStream serverInputStream = serverSocket.getInputStream();
clientOutputStream.write(12);
assertEquals(12, serverInputStream.read());
// Closing clientSocketCreatedFromFileDescriptor does not close the file descriptor.
clientSocketCreatedFromFileDescriptor.close();
assertTrue(fileDescriptor.valid());
// .. while closing the LocalSocket that owned the file descriptor does.
socketPair.clientSocket.close();
assertFalse(fileDescriptor.valid());
}
}
public void testFlush() throws Exception {
String address = ADDRESS_PREFIX + "_testFlush";
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
LocalSocket clientSocket = socketPair.clientSocket;
LocalSocket serverSocket = socketPair.serverSocket.accept();
OutputStream clientOutputStream = clientSocket.getOutputStream();
InputStream serverInputStream = serverSocket.getInputStream();
testFlushWorks(clientOutputStream, serverInputStream);
OutputStream serverOutputStream = serverSocket.getOutputStream();
InputStream clientInputStream = clientSocket.getInputStream();
testFlushWorks(serverOutputStream, clientInputStream);
serverSocket.close();
}
}
private void testFlushWorks(OutputStream outputStream, InputStream inputStream)
throws Exception {
final int bytesToTransfer = 50;
StreamReader inputStreamReader = new StreamReader(inputStream, bytesToTransfer);
byte[] buffer = new byte[bytesToTransfer];
outputStream.write(buffer);
assertEquals(bytesToTransfer, inputStream.available());
// Start consuming the data.
inputStreamReader.start();
// This doesn't actually flush any buffers, it just polls until the reader has read all the
// bytes.
outputStream.flush();
inputStreamReader.waitForCompletion(5000);
inputStreamReader.assertBytesRead(bytesToTransfer);
assertEquals(0, inputStream.available());
}
private static class StreamReader extends Thread {
private final InputStream is;
private final int expectedByteCount;
private final CountDownLatch completeLatch = new CountDownLatch(1);
private volatile Exception exception;
private int bytesRead;
private StreamReader(InputStream is, int expectedByteCount) {
this.is = is;
this.expectedByteCount = expectedByteCount;
}
@Override
public void run() {
try {
byte[] buffer = new byte[10];
int readCount;
while ((readCount = is.read(buffer)) >= 0) {
bytesRead += readCount;
if (bytesRead >= expectedByteCount) {
break;
}
}
} catch (IOException e) {
exception = e;
} finally {
completeLatch.countDown();
}
}
public void waitForCompletion(long waitMillis) throws Exception {
if (!completeLatch.await(waitMillis, TimeUnit.MILLISECONDS)) {
fail("Timeout waiting for completion");
}
if (exception != null) {
throw new Exception("Read failed", exception);
}
}
public void assertBytesRead(int expected) {
assertEquals(expected, bytesRead);
}
}
private static class Result {
private final String type;
private final Exception e;
private Result(String type, Exception e) {
this.type = type;
this.e = e;
}
static Result noException(String description) {
return new Result(description, null);
}
static Result exception(Exception e) {
return new Result(e.getClass().getName(), e);
}
void assertThrewIOException(String expectedMessage) {
assertEquals("Unexpected result type", IOException.class.getName(), type);
assertEquals("Unexpected exception message", expectedMessage, e.getMessage());
}
}
private static Result runInSeparateThread(int allowedTime, final Callable<Result> callable)
throws Exception {
ExecutorService service = Executors.newSingleThreadScheduledExecutor();
Future<Result> future = service.submit(callable);
Result result = future.get(allowedTime, TimeUnit.MILLISECONDS);
if (!future.isDone()) {
fail("Worker thread appears blocked");
}
return result;
}
private static class LocalSocketPair implements AutoCloseable {
static LocalSocketPair createConnectedSocketPair(String address) throws Exception {
LocalServerSocket localServerSocket = new LocalServerSocket(address);
final LocalSocket clientSocket = new LocalSocket();
// Establish connection between client and server
LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
clientSocket.connect(locSockAddr);
assertTrue(clientSocket.isConnected());
return new LocalSocketPair(localServerSocket, clientSocket);
}
final LocalServerSocket serverSocket;
final LocalSocket clientSocket;
LocalSocketPair(LocalServerSocket serverSocket, LocalSocket clientSocket) {
this.serverSocket = serverSocket;
this.clientSocket = clientSocket;
}
public void close() throws Exception {
serverSocket.close();
clientSocket.close();
}
}
}

Some files were not shown because too many files have changed in this diff Show More