Merge "Changing target_sdk for CtsNetTestCasesLatestSdk"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 95f854b..c4c79c6 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -22,6 +22,18 @@
}
]
},
+ // Also run CtsNetTestCasesLatestSdk to ensure tests using older shims pass.
+ {
+ "name": "CtsNetTestCasesLatestSdk",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
{
"name": "bpf_existence_test"
},
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 29f6e12..026ed54 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -33,6 +33,7 @@
":framework-connectivity-shared-srcs",
":tethering-module-utils-srcs",
":services-tethering-shared-srcs",
+ ":statslog-tethering-java-gen",
],
static_libs: [
"androidx.annotation_annotation",
@@ -223,3 +224,11 @@
apex_available: ["com.android.tethering"],
min_sdk_version: "30",
}
+
+genrule {
+ name: "statslog-tethering-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module network_tethering" +
+ " --javaPackage com.android.networkstack.tethering.metrics --javaClass TetheringStatsLog",
+ out: ["com/android/networkstack/tethering/metrics/TetheringStatsLog.java"],
+}
\ No newline at end of file
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index bd8fe7c..76c5d5c 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -70,9 +70,9 @@
canned_fs_config: "canned_fs_config",
bpfs: [
"block.o",
- "clatd.o_mainline",
+ "clatd.o",
"dscp_policy.o",
- "netd.o_mainline",
+ "netd.o",
"offload.o",
"test.o",
],
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index c718f4c..437ed71 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -69,6 +69,7 @@
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
@@ -282,13 +283,15 @@
private LinkAddress mIpv4Address;
+ private final TetheringMetrics mTetheringMetrics;
+
// TODO: Add a dependency object to pass the data members or variables from the tethering
// object. It helps to reduce the arguments of the constructor.
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
INetd netd, @NonNull BpfCoordinator coordinator, Callback callback,
TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
- Dependencies deps) {
+ TetheringMetrics tetheringMetrics, Dependencies deps) {
super(ifaceName, looper);
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
@@ -303,6 +306,7 @@
mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength();
mPrivateAddressCoordinator = addressCoordinator;
mDeps = deps;
+ mTetheringMetrics = tetheringMetrics;
resetLinkProperties();
mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
@@ -1201,6 +1205,9 @@
stopConntrackMonitoring();
resetLinkProperties();
+
+ mTetheringMetrics.updateErrorCode(mInterfaceType, mLastError);
+ mTetheringMetrics.sendReport(mInterfaceType);
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index ecb6478..c403548 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -77,6 +77,7 @@
import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim;
import com.android.networkstack.tethering.util.TetheringUtils.ForwardedStats;
+import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -1024,7 +1025,7 @@
map.forEach((k, v) -> {
pw.println(String.format("%s: %s", k, v));
});
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping BPF stats map: " + e);
}
}
@@ -1072,7 +1073,7 @@
return;
}
map.forEach((k, v) -> pw.println(ipv6UpstreamRuletoString(k, v)));
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping IPv6 upstream map: " + e);
}
}
@@ -1116,7 +1117,7 @@
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_STATS)) {
try (BpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
dumpRawMap(statsMap, pw);
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping stats map: " + e);
}
return;
@@ -1124,7 +1125,7 @@
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
dumpRawMap(upstreamMap, pw);
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping IPv4 map: " + e);
}
return;
@@ -1195,7 +1196,7 @@
pw.increaseIndent();
dumpIpv4ForwardingRuleMap(now, DOWNSTREAM, downstreamMap, pw);
pw.decreaseIndent();
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping IPv4 map: " + e);
}
}
@@ -1220,7 +1221,7 @@
}
if (v.val > 0) pw.println(String.format("%s: %d", counterName, v.val));
});
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping counter map: " + e);
}
}
@@ -1244,7 +1245,7 @@
pw.println(String.format("%d (%s) -> %d (%s)", k.ifIndex, getIfName(k.ifIndex),
v.ifIndex, getIfName(v.ifIndex)));
});
- } catch (ErrnoException e) {
+ } catch (ErrnoException | IOException e) {
pw.println("Error dumping dev map: " + e);
}
pw.decreaseIndent();
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index cc2422f..41a10ae 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -172,6 +172,9 @@
return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
}
+ // This ensures that tethering isn't started on 2 different interfaces with the same type.
+ // Once tethering could support multiple interface with the same type,
+ // TetheringSoftApCallback would need to handle it among others.
final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType());
if (useLastAddress && cachedAddress != null
&& !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 44935fc..af017f3 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -140,6 +140,7 @@
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.TetheringUtils;
@@ -254,6 +255,7 @@
private final UserManager mUserManager;
private final BpfCoordinator mBpfCoordinator;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
+ private final TetheringMetrics mTetheringMetrics;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
private volatile TetheringConfiguration mConfig;
@@ -292,6 +294,7 @@
mNetd = mDeps.getINetd(mContext);
mLooper = mDeps.getTetheringLooper();
mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper);
+ mTetheringMetrics = mDeps.getTetheringMetrics();
// This is intended to ensrure that if something calls startTethering(bluetooth) just after
// bluetooth is enabled. Before onServiceConnected is called, store the calls into this
@@ -445,8 +448,22 @@
mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler);
final WifiManager wifiManager = getWifiManager();
+ TetheringSoftApCallback softApCallback = new TetheringSoftApCallback();
if (wifiManager != null) {
- wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback());
+ wifiManager.registerSoftApCallback(mExecutor, softApCallback);
+ }
+ if (SdkLevel.isAtLeastT() && wifiManager != null) {
+ try {
+ // Although WifiManager#registerLocalOnlyHotspotSoftApCallback document that it need
+ // NEARBY_WIFI_DEVICES permission, but actually a caller who have NETWORK_STACK
+ // or MAINLINE_NETWORK_STACK permission would also able to use this API.
+ wifiManager.registerLocalOnlyHotspotSoftApCallback(mExecutor, softApCallback);
+ } catch (UnsupportedOperationException e) {
+ // Since wifi module development in internal branch,
+ // #registerLocalOnlyHotspotSoftApCallback currently doesn't supported in AOSP
+ // before AOSP switch to Android T + 1.
+ Log.wtf(TAG, "registerLocalOnlyHotspotSoftApCallback API is not supported");
+ }
}
startTrackDefaultNetwork();
@@ -542,6 +559,13 @@
}
// Called by wifi when the number of soft AP clients changed.
+ // Currently multiple softAp would not behave well in PrivateAddressCoordinator
+ // (where it gets the address from cache), it ensure tethering only support one ipServer for
+ // TETHERING_WIFI. Once tethering support multiple softAp enabled simultaneously,
+ // onConnectedClientsChanged should also be updated to support tracking different softAp's
+ // clients individually.
+ // TODO: Add wtf log and have check to reject request duplicated type with different
+ // interface.
@Override
public void onConnectedClientsChanged(final List<WifiClient> clients) {
updateConnectedClients(clients);
@@ -616,7 +640,8 @@
processInterfaceStateChange(iface, false /* enabled */);
}
- void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) {
+ void startTethering(final TetheringRequestParcel request, final String callerPkg,
+ final IIntResultListener listener) {
mHandler.post(() -> {
final TetheringRequestParcel unfinishedRequest = mActiveTetheringRequests.get(
request.tetheringType);
@@ -636,6 +661,7 @@
request.showProvisioningUi);
}
enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
+ mTetheringMetrics.createBuilder(request.tetheringType, callerPkg);
});
}
@@ -695,7 +721,11 @@
// If changing tethering fail, remove corresponding request
// no matter who trigger the start/stop.
- if (result != TETHER_ERROR_NO_ERROR) mActiveTetheringRequests.remove(type);
+ if (result != TETHER_ERROR_NO_ERROR) {
+ mActiveTetheringRequests.remove(type);
+ mTetheringMetrics.updateErrorCode(type, result);
+ mTetheringMetrics.sendReport(type);
+ }
}
private int setWifiTethering(final boolean enable) {
@@ -1288,7 +1318,7 @@
// Finally bring up serving on the new interface
mWifiP2pTetherInterface = group.getInterface();
- enableWifiIpServing(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
+ enableWifiP2pIpServing(mWifiP2pTetherInterface);
}
private void handleUserRestrictionAction() {
@@ -1379,20 +1409,22 @@
changeInterfaceState(ifname, ipServingMode);
}
- private void disableWifiIpServingCommon(int tetheringType, String ifname, int apState) {
- mLog.log("Canceling WiFi tethering request -"
- + " type=" + tetheringType
- + " interface=" + ifname
- + " state=" + apState);
-
- if (!TextUtils.isEmpty(ifname)) {
- final TetherState ts = mTetherStates.get(ifname);
- if (ts != null) {
- ts.ipServer.unwanted();
- return;
- }
+ private void disableWifiIpServingCommon(int tetheringType, String ifname) {
+ if (!TextUtils.isEmpty(ifname) && mTetherStates.containsKey(ifname)) {
+ mTetherStates.get(ifname).ipServer.unwanted();
+ return;
}
+ if (SdkLevel.isAtLeastT()) {
+ mLog.e("Tethering no longer handle untracked interface after T: " + ifname);
+ return;
+ }
+
+ // Attempt to guess the interface name before T. Pure AOSP code should never enter here
+ // because WIFI_AP_STATE_CHANGED intent always include ifname and it should be tracked
+ // by mTetherStates. In case OEMs have some modification in wifi side which pass null
+ // or empty ifname. Before T, tethering allow to disable the first wifi ipServer if
+ // given ifname don't match any tracking ipServer.
for (int i = 0; i < mTetherStates.size(); i++) {
final IpServer ipServer = mTetherStates.valueAt(i).ipServer;
if (ipServer.interfaceType() == tetheringType) {
@@ -1400,7 +1432,6 @@
return;
}
}
-
mLog.log("Error disabling Wi-Fi IP serving; "
+ (TextUtils.isEmpty(ifname) ? "no interface name specified"
: "specified interface: " + ifname));
@@ -1409,20 +1440,39 @@
private void disableWifiIpServing(String ifname, int apState) {
// Regardless of whether we requested this transition, the AP has gone
// down. Don't try to tether again unless we're requested to do so.
- // TODO: Remove this altogether, once Wi-Fi reliably gives us an
- // interface name with every broadcast.
mWifiTetherRequested = false;
- disableWifiIpServingCommon(TETHERING_WIFI, ifname, apState);
+ mLog.log("Canceling WiFi tethering request - interface=" + ifname + " state=" + apState);
+
+ disableWifiIpServingCommon(TETHERING_WIFI, ifname);
+ }
+
+ private void enableWifiP2pIpServing(String ifname) {
+ if (TextUtils.isEmpty(ifname)) {
+ mLog.e("Cannot enable P2P IP serving with invalid interface");
+ return;
+ }
+
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable p2p regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI_P2P : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+ enableIpServing(type, ifname, IpServer.STATE_LOCAL_ONLY);
}
private void disableWifiP2pIpServingIfNeeded(String ifname) {
if (TextUtils.isEmpty(ifname)) return;
- disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname, /* fake */ 0);
+ mLog.log("Canceling P2P tethering request - interface=" + ifname);
+ disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname);
}
private void enableWifiIpServing(String ifname, int wifiIpMode) {
+ mLog.log("request WiFi tethering - interface=" + ifname + " state=" + wifiIpMode);
+
// Map wifiIpMode values to IpServer.Callback serving states, inferring
// from mWifiTetherRequested as a final "best guess".
final int ipServingMode;
@@ -1438,13 +1488,18 @@
return;
}
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable wifi regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+
if (!TextUtils.isEmpty(ifname)) {
- ensureIpServerStarted(ifname);
- changeInterfaceState(ifname, ipServingMode);
+ enableIpServing(type, ifname, ipServingMode);
} else {
- mLog.e(String.format(
- "Cannot enable IP serving in mode %s on missing interface name",
- ipServingMode));
+ mLog.e("Cannot enable IP serving on missing interface name");
}
}
@@ -2715,23 +2770,28 @@
mTetherMainSM.sendMessage(which, state, 0, newLp);
}
+ private boolean hasSystemFeature(final String feature) {
+ return mContext.getPackageManager().hasSystemFeature(feature);
+ }
+
+ private boolean checkTetherableType(int type) {
+ if ((type == TETHERING_WIFI || type == TETHERING_WIGIG)
+ && !hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ return false;
+ }
+
+ if (type == TETHERING_WIFI_P2P && !hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
+ return false;
+ }
+
+ return type != TETHERING_INVALID;
+ }
+
private void ensureIpServerStarted(final String iface) {
// If we don't care about this type of interface, ignore.
final int interfaceType = ifaceNameToType(iface);
- if (interfaceType == TETHERING_INVALID) {
- mLog.log(iface + " is not a tetherable iface, ignoring");
- return;
- }
-
- final PackageManager pm = mContext.getPackageManager();
- if ((interfaceType == TETHERING_WIFI || interfaceType == TETHERING_WIGIG)
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- mLog.log(iface + " is not tetherable, because WiFi feature is disabled");
- return;
- }
- if (interfaceType == TETHERING_WIFI_P2P
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
- mLog.log(iface + " is not tetherable, because WiFi Direct feature is disabled");
+ if (!checkTetherableType(interfaceType)) {
+ mLog.log(iface + " is used for " + interfaceType + " which is not tetherable");
return;
}
@@ -2749,7 +2809,7 @@
final TetherState tetherState = new TetherState(
new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
makeControlCallback(), mConfig, mPrivateAddressCoordinator,
- mDeps.getIpServerDependencies()), isNcm);
+ mTetheringMetrics, mDeps.getIpServerDependencies()), isNcm);
mTetherStates.put(iface, tetherState);
tetherState.ipServer.start();
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 9224213..8e0354d 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -34,6 +34,7 @@
import com.android.internal.util.StateMachine;
import com.android.networkstack.apishim.BluetoothPanShimImpl;
import com.android.networkstack.apishim.common.BluetoothPanShim;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import java.util.ArrayList;
@@ -163,4 +164,11 @@
public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
return BluetoothPanShimImpl.newInstance(pan);
}
+
+ /**
+ * Get a reference to the TetheringMetrics to be used by tethering.
+ */
+ public TetheringMetrics getTetheringMetrics() {
+ return new TetheringMetrics();
+ }
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 9fb61fe..f147e10 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -137,7 +137,7 @@
return;
}
- mTethering.startTethering(request, listener);
+ mTethering.startTethering(request, callerPkg, listener);
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
new file mode 100644
index 0000000..e25f2ae
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2022 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.networkstack.tethering.metrics;
+
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
+import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+
+import android.stats.connectivity.DownstreamType;
+import android.stats.connectivity.ErrorCode;
+import android.stats.connectivity.UpstreamType;
+import android.stats.connectivity.UserType;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * Collection of utilities for tethering metrics.
+ *
+ * To see if the logs are properly sent to statsd, execute following commands
+ *
+ * $ adb shell cmd stats print-logs
+ * $ adb logcat | grep statsd OR $ adb logcat -b stats
+ *
+ * @hide
+ */
+public class TetheringMetrics {
+ private static final String TAG = TetheringMetrics.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final String SETTINGS_PKG_NAME = "com.android.settings";
+ private static final String SYSTEMUI_PKG_NAME = "com.android.systemui";
+ private static final String GMS_PKG_NAME = "com.google.android.gms";
+ private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
+
+ /** Update Tethering stats about caller's package name and downstream type. */
+ public void createBuilder(final int downstreamType, final String callerPkg) {
+ mBuilderMap.clear();
+ NetworkTetheringReported.Builder statsBuilder =
+ NetworkTetheringReported.newBuilder();
+ statsBuilder.setDownstreamType(downstreamTypeToEnum(downstreamType))
+ .setUserType(userTypeToEnum(callerPkg))
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .build();
+ mBuilderMap.put(downstreamType, statsBuilder);
+ }
+
+ /** Update error code of given downstreamType. */
+ public void updateErrorCode(final int downstreamType, final int errCode) {
+ NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType);
+ if (statsBuilder == null) {
+ Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
+ return;
+ }
+ statsBuilder.setErrorCode(errorCodeToEnum(errCode));
+ }
+
+ /** Remove Tethering stats.
+ * If Tethering stats is ready to write then write it before removing.
+ */
+ public void sendReport(final int downstreamType) {
+ final NetworkTetheringReported.Builder statsBuilder =
+ mBuilderMap.get(downstreamType);
+ if (statsBuilder == null) {
+ Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
+ return;
+ }
+ write(statsBuilder.build());
+ mBuilderMap.remove(downstreamType);
+ }
+
+ /** Collect Tethering stats and write metrics data to statsd pipeline. */
+ @VisibleForTesting
+ public void write(@NonNull final NetworkTetheringReported reported) {
+ TetheringStatsLog.write(TetheringStatsLog.NETWORK_TETHERING_REPORTED,
+ reported.getErrorCode().getNumber(),
+ reported.getDownstreamType().getNumber(),
+ reported.getUpstreamType().getNumber(),
+ reported.getUserType().getNumber());
+ if (DBG) {
+ Log.d(TAG, "Write errorCode: " + reported.getErrorCode().getNumber()
+ + ", downstreamType: " + reported.getDownstreamType().getNumber()
+ + ", upstreamType: " + reported.getUpstreamType().getNumber()
+ + ", userType: " + reported.getUserType().getNumber());
+ }
+ }
+
+ /** Map {@link TetheringType} to {@link DownstreamType} */
+ private DownstreamType downstreamTypeToEnum(final int ifaceType) {
+ switch(ifaceType) {
+ case TETHERING_WIFI:
+ return DownstreamType.DS_TETHERING_WIFI;
+ case TETHERING_WIFI_P2P:
+ return DownstreamType.DS_TETHERING_WIFI_P2P;
+ case TETHERING_USB:
+ return DownstreamType.DS_TETHERING_USB;
+ case TETHERING_BLUETOOTH:
+ return DownstreamType.DS_TETHERING_BLUETOOTH;
+ case TETHERING_NCM:
+ return DownstreamType.DS_TETHERING_NCM;
+ case TETHERING_ETHERNET:
+ return DownstreamType.DS_TETHERING_ETHERNET;
+ default:
+ return DownstreamType.DS_UNSPECIFIED;
+ }
+ }
+
+ /** Map {@link StartTetheringError} to {@link ErrorCode} */
+ private ErrorCode errorCodeToEnum(final int lastError) {
+ switch(lastError) {
+ case TETHER_ERROR_NO_ERROR:
+ return ErrorCode.EC_NO_ERROR;
+ case TETHER_ERROR_UNKNOWN_IFACE:
+ return ErrorCode.EC_UNKNOWN_IFACE;
+ case TETHER_ERROR_SERVICE_UNAVAIL:
+ return ErrorCode.EC_SERVICE_UNAVAIL;
+ case TETHER_ERROR_UNSUPPORTED:
+ return ErrorCode.EC_UNSUPPORTED;
+ case TETHER_ERROR_UNAVAIL_IFACE:
+ return ErrorCode.EC_UNAVAIL_IFACE;
+ case TETHER_ERROR_INTERNAL_ERROR:
+ return ErrorCode.EC_INTERNAL_ERROR;
+ case TETHER_ERROR_TETHER_IFACE_ERROR:
+ return ErrorCode.EC_TETHER_IFACE_ERROR;
+ case TETHER_ERROR_UNTETHER_IFACE_ERROR:
+ return ErrorCode.EC_UNTETHER_IFACE_ERROR;
+ case TETHER_ERROR_ENABLE_FORWARDING_ERROR:
+ return ErrorCode.EC_ENABLE_FORWARDING_ERROR;
+ case TETHER_ERROR_DISABLE_FORWARDING_ERROR:
+ return ErrorCode.EC_DISABLE_FORWARDING_ERROR;
+ case TETHER_ERROR_IFACE_CFG_ERROR:
+ return ErrorCode.EC_IFACE_CFG_ERROR;
+ case TETHER_ERROR_PROVISIONING_FAILED:
+ return ErrorCode.EC_PROVISIONING_FAILED;
+ case TETHER_ERROR_DHCPSERVER_ERROR:
+ return ErrorCode.EC_DHCPSERVER_ERROR;
+ case TETHER_ERROR_ENTITLEMENT_UNKNOWN:
+ return ErrorCode.EC_ENTITLEMENT_UNKNOWN;
+ case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION:
+ return ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION;
+ case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION:
+ return ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION;
+ default:
+ return ErrorCode.EC_UNKNOWN_TYPE;
+ }
+ }
+
+ /** Map callerPkg to {@link UserType} */
+ private UserType userTypeToEnum(final String callerPkg) {
+ if (callerPkg.equals(SETTINGS_PKG_NAME)) {
+ return UserType.USER_SETTINGS;
+ } else if (callerPkg.equals(SYSTEMUI_PKG_NAME)) {
+ return UserType.USER_SYSTEMUI;
+ } else if (callerPkg.equals(GMS_PKG_NAME)) {
+ return UserType.USER_GMS;
+ } else {
+ return UserType.USER_UNKNOWN;
+ }
+ }
+}
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index e73b7d5..3699f7a 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -59,6 +59,7 @@
import android.os.HandlerThread;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
@@ -84,6 +85,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.HandlerUtils;
import com.android.testutils.TapPacketReader;
@@ -1058,19 +1060,33 @@
}
@Test
- @IgnoreAfter(Build.VERSION_CODES.Q)
- public void testTetherUdpV4WithoutBpf() throws Exception {
+ @IgnoreAfter(Build.VERSION_CODES.R)
+ public void testTetherUdpV4UpToR() throws Exception {
initializeTethering();
runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
false /* usingBpf */);
}
+ private static boolean isUdpOffloadSupportedByKernel() {
+ final String kVersionString = VintfRuntimeInfo.getKernelVersion();
+ // Kernel version which is older than 4.14 doesn't support UDP offload absolutely. Kernel
+ // version which is between 4.14 and 5.8 support UDP offload probably. Simply apply kernel
+ // 4.14 to be threshold first and monitor on what devices tests fail for improving the
+ // offload support checking.
+ return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.14") >= 0;
+ }
+
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
- public void testTetherUdpV4WithBpf() throws Exception {
+ public void testTetherUdpV4AfterR() throws Exception {
initializeTethering();
+ boolean usingBpf = isUdpOffloadSupportedByKernel();
+ if (!usingBpf) {
+ Log.i(TAG, "testTetherUdpV4AfterR will skip BPF offload test for kernel "
+ + VintfRuntimeInfo.getKernelVersion());
+ }
runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
- true /* usingBpf */);
+ usingBpf);
}
@Nullable
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index ad2faa0..68c1c57 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -352,15 +352,6 @@
assertFalse(mTestMap.isEmpty());
mTestMap.clear();
assertTrue(mTestMap.isEmpty());
-
- // Clearing an already-closed map throws.
- mTestMap.close();
- try {
- mTestMap.clear();
- fail("clearing already-closed map should throw");
- } catch (ErrnoException expected) {
- assertEquals(OsConstants.EBADF, expected.errno);
- }
}
@Test
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index aac531a..bf7e887 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -116,6 +116,7 @@
import com.android.networkstack.tethering.TetherLimitValue;
import com.android.networkstack.tethering.TetherUpstream6Key;
import com.android.networkstack.tethering.TetheringConfiguration;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.testutils.DevSdkIgnoreRule;
@@ -186,6 +187,7 @@
@Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
+ @Mock private TetheringMetrics mTetheringMetrics;
@Mock private BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map;
@Mock private BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
@Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
@@ -235,7 +237,7 @@
when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
mIpServer = new IpServer(
IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
- mCallback, mTetherConfig, mAddressCoordinator, mDependencies);
+ mCallback, mTetherConfig, mAddressCoordinator, mTetheringMetrics, mDependencies);
mIpServer.start();
mNeighborEventConsumer = neighborCaptor.getValue();
@@ -367,7 +369,7 @@
.thenReturn(mIpNeighborMonitor);
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
mNetd, mBpfCoordinator, mCallback, mTetherConfig, mAddressCoordinator,
- mDependencies);
+ mTetheringMetrics, mDependencies);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
@@ -451,6 +453,9 @@
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_BLUETOOTH),
+ eq(TETHER_ERROR_NO_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_BLUETOOTH));
verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
}
@@ -658,6 +663,9 @@
usbTeardownOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB),
+ eq(TETHER_ERROR_TETHER_IFACE_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_USB));
}
@Test
@@ -676,6 +684,9 @@
usbTeardownOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB),
+ eq(TETHER_ERROR_ENABLE_FORWARDING_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_USB));
}
@Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index e9716b3..8ef0c76 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -26,6 +26,7 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
@@ -668,7 +669,7 @@
if (isAtLeastT()) {
mTetherStatsProviderCb.expectNotifyLimitReached();
- } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.S) {
+ } else if (isAtLeastS()) {
mTetherStatsProviderCb.expectNotifyWarningOrLimitReached();
} else {
mTetherStatsProviderCb.expectNotifyLimitReached();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index f664d5d..9db8f16 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -275,7 +275,7 @@
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringSupported();
- verify(mTethering).startTethering(eq(request), eq(result));
+ verify(mTethering).startTethering(eq(request), eq(TEST_CALLER_PKG), eq(result));
}
@Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 2fd7f48..773cae3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -45,6 +45,7 @@
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
@@ -57,6 +58,7 @@
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -192,11 +194,15 @@
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.MiscAsserts;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -219,6 +225,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TetheringTest {
+ @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
private static final int IFINDEX_OFFSET = 100;
private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
@@ -239,6 +247,7 @@
private static final String TEST_WIFI_REGEX = "test_wlan\\d";
private static final String TEST_P2P_REGEX = "test_p2p-p2p\\d-.*";
private static final String TEST_BT_REGEX = "test_pan\\d";
+ private static final String TEST_CALLER_PKG = "com.test.tethering";
private static final int CELLULAR_NETID = 100;
private static final int WIFI_NETID = 101;
@@ -273,6 +282,7 @@
@Mock private BluetoothPan mBluetoothPan;
@Mock private BluetoothPanShim mBluetoothPanShim;
@Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
+ @Mock private TetheringMetrics mTetheringMetrics;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -296,6 +306,7 @@
private OffloadController mOffloadCtrl;
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private SoftApCallback mSoftApCallback;
+ private SoftApCallback mLocalOnlyHotspotCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
@@ -497,6 +508,11 @@
}
@Override
+ public TetheringMetrics getTetheringMetrics() {
+ return mTetheringMetrics;
+ }
+
+ @Override
public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
TetheringConfiguration cfg) {
mPrivateAddressCoordinator = super.getPrivateAddressCoordinator(ctx, cfg);
@@ -661,6 +677,14 @@
verify(mWifiManager).registerSoftApCallback(any(), softApCallbackCaptor.capture());
mSoftApCallback = softApCallbackCaptor.getValue();
+ if (isAtLeastT()) {
+ final ArgumentCaptor<SoftApCallback> localOnlyCallbackCaptor =
+ ArgumentCaptor.forClass(SoftApCallback.class);
+ verify(mWifiManager).registerLocalOnlyHotspotSoftApCallback(any(),
+ localOnlyCallbackCaptor.capture());
+ mLocalOnlyHotspotCallback = localOnlyCallbackCaptor.getValue();
+ }
+
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
}
@@ -855,7 +879,8 @@
private void prepareNcmTethering() {
// Emulate startTethering(TETHERING_NCM) called
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
}
@@ -863,7 +888,7 @@
private void prepareUsbTethering() {
// Emulate pressing the USB tethering button in Settings UI.
final TetheringRequestParcel request = createTetheringRequestParcel(TETHERING_USB);
- mTethering.startTethering(request, null);
+ mTethering.startTethering(request, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
assertEquals(1, mTethering.getActiveTetheringRequests().size());
@@ -936,7 +961,7 @@
// Emulate externally-visible WifiManager effects, when hotspot mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1433,7 +1458,8 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
@@ -1460,7 +1486,8 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
@@ -1509,7 +1536,7 @@
// Emulate externally-visible WifiManager effects, when tethering mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1536,11 +1563,13 @@
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
+ verify(mTetheringMetrics).createBuilder(eq(TETHERING_WIFI), anyString());
// Emulate externally-visible WifiManager effects, causing the
// per-interface state machine to start up, and telling us that
@@ -1579,6 +1608,10 @@
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
+ verify(mTetheringMetrics, times(2)).updateErrorCode(eq(TETHERING_WIFI),
+ eq(TETHER_ERROR_INTERNAL_ERROR));
+ verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_WIFI));
+
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
}
@@ -1881,7 +1914,8 @@
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIface});
@@ -1903,7 +1937,13 @@
mTethering.unregisterTetheringEventCallback(callback);
mLooper.dispatchAll();
mTethering.stopTethering(TETHERING_WIFI);
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED);
+ if (isAtLeastT()) {
+ // After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name.
+ callback2.assertNoStateChangeCallback();
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
+ IFACE_IP_MODE_TETHERED);
+ }
tetherState = callback2.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
mLooper.dispatchAll();
@@ -1978,10 +2018,12 @@
public void testNoDuplicatedEthernetRequest() throws Exception {
final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class);
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mEm, times(1)).requestTetheredInterface(any(), any());
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verifyNoMoreInteractions(mEm);
mTethering.stopTethering(TETHERING_ETHERNET);
@@ -2185,14 +2227,16 @@
final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR);
// Enable USB tethering and check that Tethering starts USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), firstResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ firstResult);
mLooper.dispatchAll();
firstResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
verifyNoMoreInteractions(mUsbManager);
// Enable USB tethering again with the same request and expect no change to USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), secondResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ secondResult);
mLooper.dispatchAll();
secondResult.assertHasResult();
verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -2201,7 +2245,8 @@
// Enable USB tethering with a different request and expect that USB is stopped and
// started.
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), thirdResult);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
+ TEST_CALLER_PKG, thirdResult);
mLooper.dispatchAll();
thirdResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -2230,7 +2275,8 @@
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), null);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
+ TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
mTethering.interfaceStatusChanged(TEST_NCM_IFNAME, true);
@@ -2298,7 +2344,7 @@
final TetheringRequestParcel wifiNotExemptRequest =
createTetheringRequestParcel(TETHERING_WIFI, null, null, false,
CONNECTIVITY_SCOPE_GLOBAL);
- mTethering.startTethering(wifiNotExemptRequest, null);
+ mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
@@ -2312,7 +2358,7 @@
final TetheringRequestParcel wifiExemptRequest =
createTetheringRequestParcel(TETHERING_WIFI, null, null, true,
CONNECTIVITY_SCOPE_GLOBAL);
- mTethering.startTethering(wifiExemptRequest, null);
+ mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
@@ -2325,14 +2371,14 @@
// If one app enables tethering without provisioning check first, then another app enables
// tethering of the same type but does not disable the provisioning check.
setupForRequiredProvisioning();
- mTethering.startTethering(wifiExemptRequest, null);
+ mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
assertTrue(mEntitleMgr.isCellularUpstreamPermitted());
reset(mEntitleMgr);
setupForRequiredProvisioning();
- mTethering.startTethering(wifiNotExemptRequest, null);
+ mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
@@ -2422,7 +2468,8 @@
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
final ArgumentCaptor<TetheredInterfaceCallback> callbackCaptor =
ArgumentCaptor.forClass(TetheredInterfaceCallback.class);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET),
+ TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEm).requestTetheredInterface(any(), callbackCaptor.capture());
TetheredInterfaceCallback ethCallback = callbackCaptor.getValue();
@@ -2504,12 +2551,11 @@
eventCallbacks = dhcpEventCbsCaptor.getValue();
// Update lease for local only tethering.
final MacAddress testMac1 = MacAddress.fromString("11:11:11:11:11:11");
- final ArrayList<DhcpLeaseParcelable> p2pLeases = new ArrayList<>();
- p2pLeases.add(createDhcpLeaseParcelable("clientId1", testMac1, "192.168.50.24", 24,
- Long.MAX_VALUE, "test1"));
- notifyDhcpLeasesChanged(p2pLeases, eventCallbacks);
- final List<TetheredClient> clients = toTetheredClients(p2pLeases, TETHERING_WIFI_P2P);
- callback.expectTetheredClientChanged(clients);
+ final DhcpLeaseParcelable p2pLease = createDhcpLeaseParcelable("clientId1", testMac1,
+ "192.168.50.24", 24, Long.MAX_VALUE, "test1");
+ final List<TetheredClient> p2pClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P,
+ eventCallbacks, p2pLease);
+ callback.expectTetheredClientChanged(p2pClients);
reset(mDhcpServer);
// Run wifi tethering.
@@ -2519,25 +2565,20 @@
any(), dhcpEventCbsCaptor.capture());
eventCallbacks = dhcpEventCbsCaptor.getValue();
// Update mac address from softAp callback before getting dhcp lease.
- final ArrayList<WifiClient> wifiClients = new ArrayList<>();
final MacAddress testMac2 = MacAddress.fromString("22:22:22:22:22:22");
- final WifiClient testClient = mock(WifiClient.class);
- when(testClient.getMacAddress()).thenReturn(testMac2);
- wifiClients.add(testClient);
- mSoftApCallback.onConnectedClientsChanged(wifiClients);
- final TetheredClient noAddrClient = new TetheredClient(testMac2,
- Collections.emptyList() /* addresses */, TETHERING_WIFI);
- clients.add(noAddrClient);
- callback.expectTetheredClientChanged(clients);
+ final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac2,
+ false /* isLocalOnly */);
+ final List<TetheredClient> p2pAndNoAddrClients = new ArrayList<>(p2pClients);
+ p2pAndNoAddrClients.add(noAddrClient);
+ callback.expectTetheredClientChanged(p2pAndNoAddrClients);
// Update dhcp lease for wifi tethering.
- clients.remove(noAddrClient);
- final ArrayList<DhcpLeaseParcelable> wifiLeases = new ArrayList<>();
- wifiLeases.add(createDhcpLeaseParcelable("clientId2", testMac2, "192.168.43.24", 24,
- Long.MAX_VALUE, "test2"));
- notifyDhcpLeasesChanged(wifiLeases, eventCallbacks);
- clients.addAll(toTetheredClients(wifiLeases, TETHERING_WIFI));
- callback.expectTetheredClientChanged(clients);
+ final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId2", testMac2,
+ "192.168.43.24", 24, Long.MAX_VALUE, "test2");
+ final List<TetheredClient> p2pAndWifiClients = new ArrayList<>(p2pClients);
+ p2pAndWifiClients.addAll(notifyDhcpLeasesChanged(TETHERING_WIFI,
+ eventCallbacks, wifiLease));
+ callback.expectTetheredClientChanged(p2pAndWifiClients);
// Test onStarted callback that register second callback when tethering is running.
TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
@@ -2545,18 +2586,74 @@
mTethering.registerTetheringEventCallback(callback2);
mLooper.dispatchAll();
});
- callback2.expectTetheredClientChanged(clients);
+ callback2.expectTetheredClientChanged(p2pAndWifiClients);
}
- private void notifyDhcpLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables,
- IDhcpEventCallbacks callback) throws Exception {
- callback.onLeasesChanged(leaseParcelables);
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testUpdateConnectedClientsForLocalOnlyHotspot() throws Exception {
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ });
+ callback.expectTetheredClientChanged(Collections.emptyList());
+
+ // Run local only hotspot.
+ mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+ final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor =
+ ArgumentCaptor.forClass(IDhcpEventCallbacks.class);
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+ any(), dhcpEventCbsCaptor.capture());
+ final IDhcpEventCallbacks eventCallbacks = dhcpEventCbsCaptor.getValue();
+ // Update mac address from softAp callback before getting dhcp lease.
+ final MacAddress testMac = MacAddress.fromString("22:22:22:22:22:22");
+ final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac,
+ true /* isLocalOnly */);
+ final List<TetheredClient> noAddrLocalOnlyClients = new ArrayList<>();
+ noAddrLocalOnlyClients.add(noAddrClient);
+ callback.expectTetheredClientChanged(noAddrLocalOnlyClients);
+
+ // Update dhcp lease for local only hotspot.
+ final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId", testMac,
+ "192.168.43.24", 24, Long.MAX_VALUE, "test");
+ final List<TetheredClient> localOnlyClients = notifyDhcpLeasesChanged(TETHERING_WIFI,
+ eventCallbacks, wifiLease);
+ callback.expectTetheredClientChanged(localOnlyClients);
+
+ // Client disconnect from local only hotspot.
+ mLocalOnlyHotspotCallback.onConnectedClientsChanged(Collections.emptyList());
+ callback.expectTetheredClientChanged(Collections.emptyList());
+ }
+
+ private TetheredClient notifyConnectedWifiClientsChanged(final MacAddress mac,
+ boolean isLocalOnly) throws Exception {
+ final ArrayList<WifiClient> wifiClients = new ArrayList<>();
+ final WifiClient testClient = mock(WifiClient.class);
+ when(testClient.getMacAddress()).thenReturn(mac);
+ wifiClients.add(testClient);
+ if (isLocalOnly) {
+ mLocalOnlyHotspotCallback.onConnectedClientsChanged(wifiClients);
+ } else {
+ mSoftApCallback.onConnectedClientsChanged(wifiClients);
+ }
+ return new TetheredClient(mac, Collections.emptyList() /* addresses */, TETHERING_WIFI);
+ }
+
+ private List<TetheredClient> notifyDhcpLeasesChanged(int type, IDhcpEventCallbacks callback,
+ DhcpLeaseParcelable... leases) throws Exception {
+ final List<DhcpLeaseParcelable> dhcpLeases = Arrays.asList(leases);
+ callback.onLeasesChanged(dhcpLeases);
mLooper.dispatchAll();
+
+ return toTetheredClients(dhcpLeases, type);
}
private List<TetheredClient> toTetheredClients(List<DhcpLeaseParcelable> leaseParcelables,
int type) throws Exception {
- final ArrayList<TetheredClient> leases = new ArrayList<>();
+ final ArrayList<TetheredClient> clients = new ArrayList<>();
for (DhcpLeaseParcelable lease : leaseParcelables) {
final LinkAddress address = new LinkAddress(
intToInet4AddressHTH(lease.netAddr), lease.prefixLength,
@@ -2566,13 +2663,13 @@
final MacAddress macAddress = MacAddress.fromBytes(lease.hwAddr);
final AddressInfo addressInfo = new TetheredClient.AddressInfo(address, lease.hostname);
- leases.add(new TetheredClient(
+ clients.add(new TetheredClient(
macAddress,
Collections.singletonList(addressInfo),
type));
}
- return leases;
+ return clients;
}
private DhcpLeaseParcelable createDhcpLeaseParcelable(final String clientId,
@@ -2597,7 +2694,8 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
result.assertHasResult();
@@ -2632,7 +2730,8 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
result.assertHasResult();
@@ -2653,7 +2752,8 @@
// already bound.
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), secondResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, secondResult);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, false /* bindToPanService */);
secondResult.assertHasResult();
@@ -2674,7 +2774,8 @@
public void testBluetoothServiceDisconnects() throws Exception {
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, result);
mLooper.dispatchAll();
ServiceListener panListener = verifySetBluetoothTethering(true /* enable */,
true /* bindToPanService */);
@@ -2825,18 +2926,26 @@
runNcmTethering();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
+ verify(mTetheringMetrics).createBuilder(eq(TETHERING_NCM), anyString());
// Change the USB tethering function to NCM. Because the USB tethering function was set to
// RNDIS (the default), tethering is stopped.
forceUsbTetheringUse(TETHER_USB_NCM_FUNCTION);
verifyUsbTetheringStopDueToSettingChange(TEST_NCM_IFNAME);
+ verify(mTetheringMetrics).updateErrorCode(anyInt(), eq(TETHER_ERROR_NO_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_NCM));
// If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
// available.
final ResultListener ncmResult = new ResultListener(TETHER_ERROR_SERVICE_UNAVAIL);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), ncmResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ ncmResult);
mLooper.dispatchAll();
ncmResult.assertHasResult();
+ verify(mTetheringMetrics, times(2)).createBuilder(eq(TETHERING_NCM), anyString());
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_NCM),
+ eq(TETHER_ERROR_SERVICE_UNAVAIL));
+ verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_NCM));
// Run TETHERING_USB with ncm configuration.
runDualStackUsbTethering(TEST_NCM_IFNAME);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
new file mode 100644
index 0000000..c34cf5f
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 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.networkstack.tethering.metrics;
+
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_TYPE;
+import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
+import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.stats.connectivity.DownstreamType;
+import android.stats.connectivity.ErrorCode;
+import android.stats.connectivity.UpstreamType;
+import android.stats.connectivity.UserType;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class TetheringMetricsTest {
+ private static final String TEST_CALLER_PKG = "com.test.caller.pkg";
+ private static final String SETTINGS_PKG = "com.android.settings";
+ private static final String SYSTEMUI_PKG = "com.android.systemui";
+ private static final String GMS_PKG = "com.google.android.gms";
+ private TetheringMetrics mTetheringMetrics;
+
+ private final NetworkTetheringReported.Builder mStatsBuilder =
+ NetworkTetheringReported.newBuilder();
+
+ private class MockTetheringMetrics extends TetheringMetrics {
+ @Override
+ public void write(final NetworkTetheringReported reported) { }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTetheringMetrics = spy(new MockTetheringMetrics());
+ }
+
+ private void runDownstreamTypesTest(final Pair<Integer, DownstreamType>... testPairs)
+ throws Exception {
+ for (Pair<Integer, DownstreamType> testPair : testPairs) {
+ final int type = testPair.first;
+ final DownstreamType expectedResult = testPair.second;
+
+ mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
+ mTetheringMetrics.updateErrorCode(type, TETHER_ERROR_NO_ERROR);
+ mTetheringMetrics.sendReport(type);
+ NetworkTetheringReported expectedReport =
+ mStatsBuilder.setDownstreamType(expectedResult)
+ .setUserType(UserType.USER_UNKNOWN)
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .build();
+ verify(mTetheringMetrics).write(expectedReport);
+ reset(mTetheringMetrics);
+ }
+ }
+
+ @Test
+ public void testDownstreamTypes() throws Exception {
+ runDownstreamTypesTest(new Pair<>(TETHERING_WIFI, DownstreamType.DS_TETHERING_WIFI),
+ new Pair<>(TETHERING_WIFI_P2P, DownstreamType.DS_TETHERING_WIFI_P2P),
+ new Pair<>(TETHERING_BLUETOOTH, DownstreamType.DS_TETHERING_BLUETOOTH),
+ new Pair<>(TETHERING_USB, DownstreamType.DS_TETHERING_USB),
+ new Pair<>(TETHERING_NCM, DownstreamType.DS_TETHERING_NCM),
+ new Pair<>(TETHERING_ETHERNET, DownstreamType.DS_TETHERING_ETHERNET));
+ }
+
+ private void runErrorCodesTest(final Pair<Integer, ErrorCode>... testPairs)
+ throws Exception {
+ for (Pair<Integer, ErrorCode> testPair : testPairs) {
+ final int errorCode = testPair.first;
+ final ErrorCode expectedResult = testPair.second;
+
+ mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
+ mTetheringMetrics.updateErrorCode(TETHERING_WIFI, errorCode);
+ mTetheringMetrics.sendReport(TETHERING_WIFI);
+ NetworkTetheringReported expectedReport =
+ mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI)
+ .setUserType(UserType.USER_UNKNOWN)
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(expectedResult)
+ .build();
+ verify(mTetheringMetrics).write(expectedReport);
+ reset(mTetheringMetrics);
+ }
+ }
+
+ @Test
+ public void testErrorCodes() throws Exception {
+ runErrorCodesTest(new Pair<>(TETHER_ERROR_NO_ERROR, ErrorCode.EC_NO_ERROR),
+ new Pair<>(TETHER_ERROR_UNKNOWN_IFACE, ErrorCode.EC_UNKNOWN_IFACE),
+ new Pair<>(TETHER_ERROR_SERVICE_UNAVAIL, ErrorCode.EC_SERVICE_UNAVAIL),
+ new Pair<>(TETHER_ERROR_UNSUPPORTED, ErrorCode.EC_UNSUPPORTED),
+ new Pair<>(TETHER_ERROR_UNAVAIL_IFACE, ErrorCode.EC_UNAVAIL_IFACE),
+ new Pair<>(TETHER_ERROR_INTERNAL_ERROR, ErrorCode.EC_INTERNAL_ERROR),
+ new Pair<>(TETHER_ERROR_TETHER_IFACE_ERROR, ErrorCode.EC_TETHER_IFACE_ERROR),
+ new Pair<>(TETHER_ERROR_UNTETHER_IFACE_ERROR, ErrorCode.EC_UNTETHER_IFACE_ERROR),
+ new Pair<>(TETHER_ERROR_ENABLE_FORWARDING_ERROR,
+ ErrorCode.EC_ENABLE_FORWARDING_ERROR),
+ new Pair<>(TETHER_ERROR_DISABLE_FORWARDING_ERROR,
+ ErrorCode.EC_DISABLE_FORWARDING_ERROR),
+ new Pair<>(TETHER_ERROR_IFACE_CFG_ERROR, ErrorCode.EC_IFACE_CFG_ERROR),
+ new Pair<>(TETHER_ERROR_PROVISIONING_FAILED, ErrorCode.EC_PROVISIONING_FAILED),
+ new Pair<>(TETHER_ERROR_DHCPSERVER_ERROR, ErrorCode.EC_DHCPSERVER_ERROR),
+ new Pair<>(TETHER_ERROR_ENTITLEMENT_UNKNOWN, ErrorCode.EC_ENTITLEMENT_UNKNOWN),
+ new Pair<>(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION,
+ ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION),
+ new Pair<>(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION,
+ ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION),
+ new Pair<>(TETHER_ERROR_UNKNOWN_TYPE, ErrorCode.EC_UNKNOWN_TYPE));
+ }
+
+ private void runUserTypesTest(final Pair<String, UserType>... testPairs)
+ throws Exception {
+ for (Pair<String, UserType> testPair : testPairs) {
+ final String callerPkg = testPair.first;
+ final UserType expectedResult = testPair.second;
+
+ mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
+ mTetheringMetrics.updateErrorCode(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+ mTetheringMetrics.sendReport(TETHERING_WIFI);
+ NetworkTetheringReported expectedReport =
+ mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI)
+ .setUserType(expectedResult)
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .build();
+ verify(mTetheringMetrics).write(expectedReport);
+ reset(mTetheringMetrics);
+ }
+ }
+
+ @Test
+ public void testUserTypes() throws Exception {
+ runUserTypesTest(new Pair<>(TEST_CALLER_PKG, UserType.USER_UNKNOWN),
+ new Pair<>(SETTINGS_PKG, UserType.USER_SETTINGS),
+ new Pair<>(SYSTEMUI_PKG, UserType.USER_SYSTEMUI),
+ new Pair<>(GMS_PKG, UserType.USER_GMS));
+ }
+}
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 1fe0e9a..23af3e3 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -61,6 +61,7 @@
bpf {
name: "block.o",
srcs: ["block.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -71,6 +72,7 @@
bpf {
name: "dscp_policy.o",
srcs: ["dscp_policy.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -97,8 +99,9 @@
}
bpf {
- name: "clatd.o_mainline",
+ name: "clatd.o",
srcs: ["clatd.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -110,8 +113,9 @@
}
bpf {
- name: "netd.o_mainline",
+ name: "netd.o",
srcs: ["netd.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -119,5 +123,5 @@
include_dirs: [
"frameworks/libs/net/common/netd/libnetdutils/include",
],
- sub_dir: "net_shared",
+ sub_dir: "netd_shared",
}
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index ddd9a1c..f2a3e62 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -19,6 +19,9 @@
#include <netinet/in.h>
#include <stdint.h>
+// The resulting .o needs to load on the Android T beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include "bpf_helpers.h"
#define ALLOW 1
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index a6e78b6..14fcdd6 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -98,29 +98,29 @@
static const int CONFIGURATION_MAP_SIZE = 2;
static const int UID_OWNER_MAP_SIZE = 2000;
-#define BPF_PATH "/sys/fs/bpf/net_shared/"
+#define BPF_NETD_PATH "/sys/fs/bpf/netd_shared/"
-#define BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_egress_stats"
-#define BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_ingress_stats"
-#define XT_BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_ingress_xtbpf"
-#define XT_BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_egress_xtbpf"
-#define XT_BPF_ALLOWLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_allowlist_xtbpf"
-#define XT_BPF_DENYLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_denylist_xtbpf"
-#define CGROUP_SOCKET_PROG_PATH BPF_PATH "prog_netd_cgroupsock_inet_create"
+#define BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_egress_stats"
+#define BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_ingress_stats"
+#define XT_BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_ingress_xtbpf"
+#define XT_BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_egress_xtbpf"
+#define XT_BPF_ALLOWLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_allowlist_xtbpf"
+#define XT_BPF_DENYLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_denylist_xtbpf"
+#define CGROUP_SOCKET_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
#define TC_BPF_INGRESS_ACCOUNT_PROG_NAME "prog_netd_schedact_ingress_account"
-#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
+#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_NETD_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
-#define COOKIE_TAG_MAP_PATH BPF_PATH "map_netd_cookie_tag_map"
-#define UID_COUNTERSET_MAP_PATH BPF_PATH "map_netd_uid_counterset_map"
-#define APP_UID_STATS_MAP_PATH BPF_PATH "map_netd_app_uid_stats_map"
-#define STATS_MAP_A_PATH BPF_PATH "map_netd_stats_map_A"
-#define STATS_MAP_B_PATH BPF_PATH "map_netd_stats_map_B"
-#define IFACE_INDEX_NAME_MAP_PATH BPF_PATH "map_netd_iface_index_name_map"
-#define IFACE_STATS_MAP_PATH BPF_PATH "map_netd_iface_stats_map"
-#define CONFIGURATION_MAP_PATH BPF_PATH "map_netd_configuration_map"
-#define UID_OWNER_MAP_PATH BPF_PATH "map_netd_uid_owner_map"
-#define UID_PERMISSION_MAP_PATH BPF_PATH "map_netd_uid_permission_map"
+#define COOKIE_TAG_MAP_PATH BPF_NETD_PATH "map_netd_cookie_tag_map"
+#define UID_COUNTERSET_MAP_PATH BPF_NETD_PATH "map_netd_uid_counterset_map"
+#define APP_UID_STATS_MAP_PATH BPF_NETD_PATH "map_netd_app_uid_stats_map"
+#define STATS_MAP_A_PATH BPF_NETD_PATH "map_netd_stats_map_A"
+#define STATS_MAP_B_PATH BPF_NETD_PATH "map_netd_stats_map_B"
+#define IFACE_INDEX_NAME_MAP_PATH BPF_NETD_PATH "map_netd_iface_index_name_map"
+#define IFACE_STATS_MAP_PATH BPF_NETD_PATH "map_netd_iface_stats_map"
+#define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map"
+#define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
+#define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
enum UidOwnerMatchType {
NO_MATCH = 0,
@@ -132,6 +132,7 @@
RESTRICTED_MATCH = (1 << 5),
LOW_POWER_STANDBY_MATCH = (1 << 6),
IIF_MATCH = (1 << 7),
+ LOCKDOWN_VPN_MATCH = (1 << 8),
};
enum BpfPermissionMatch {
@@ -162,13 +163,15 @@
#define UID_RULES_CONFIGURATION_KEY 1
#define CURRENT_STATS_MAP_CONFIGURATION_KEY 2
+#define BPF_CLATD_PATH "/sys/fs/bpf/net_shared/"
+
#define CLAT_INGRESS6_PROG_RAWIP_NAME "prog_clatd_schedcls_ingress6_clat_rawip"
#define CLAT_INGRESS6_PROG_ETHER_NAME "prog_clatd_schedcls_ingress6_clat_ether"
-#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
-#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_PATH CLAT_INGRESS6_PROG_ETHER_NAME
+#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
+#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_ETHER_NAME
-#define CLAT_INGRESS6_MAP_PATH BPF_PATH "map_clatd_clat_ingress6_map"
+#define CLAT_INGRESS6_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_ingress6_map"
typedef struct {
uint32_t iif; // The input interface index
@@ -186,10 +189,10 @@
#define CLAT_EGRESS4_PROG_RAWIP_NAME "prog_clatd_schedcls_egress4_clat_rawip"
#define CLAT_EGRESS4_PROG_ETHER_NAME "prog_clatd_schedcls_egress4_clat_ether"
-#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
-#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_PATH CLAT_EGRESS4_PROG_ETHER_NAME
+#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
+#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_ETHER_NAME
-#define CLAT_EGRESS4_MAP_PATH BPF_PATH "map_clatd_clat_egress4_map"
+#define CLAT_EGRESS4_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_egress4_map"
typedef struct {
uint32_t iif; // The input interface index
diff --git a/bpf_progs/bpf_tethering.h b/bpf_progs/bpf_tethering.h
index b0ec8f6..f9ef6ef 100644
--- a/bpf_progs/bpf_tethering.h
+++ b/bpf_progs/bpf_tethering.h
@@ -73,10 +73,6 @@
#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
-#define BPF_PATH_TETHER BPF_PATH "tethering/"
-
-#define TETHER_STATS_MAP_PATH BPF_PATH_TETHER "map_offload_tether_stats_map"
-
typedef uint32_t TetherStatsKey; // upstream ifindex
typedef struct {
@@ -89,19 +85,9 @@
} TetherStatsValue;
STRUCT_SIZE(TetherStatsValue, 6 * 8); // 48
-#define TETHER_LIMIT_MAP_PATH BPF_PATH_TETHER "map_offload_tether_limit_map"
-
typedef uint32_t TetherLimitKey; // upstream ifindex
typedef uint64_t TetherLimitValue; // in bytes
-#define TETHER_DOWNSTREAM6_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_downstream6_rawip"
-#define TETHER_DOWNSTREAM6_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_downstream6_ether"
-
-#define TETHER_DOWNSTREAM6_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM6_TC_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM6_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM6_TC_PROG_ETHER_NAME
-
-#define TETHER_DOWNSTREAM6_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream6_map"
-
// For now tethering offload only needs to support downstreams that use 6-byte MAC addresses,
// because all downstream types that are currently supported (WiFi, USB, Bluetooth and
// Ethernet) have 6-byte MAC addresses.
@@ -121,8 +107,6 @@
} Tether6Value;
STRUCT_SIZE(Tether6Value, 4 + 14 + 2); // 20
-#define TETHER_DOWNSTREAM64_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream64_map"
-
typedef struct {
uint32_t iif; // The input interface index
uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress)
@@ -146,14 +130,6 @@
} TetherDownstream64Value;
STRUCT_SIZE(TetherDownstream64Value, 4 + 14 + 2 + 4 + 4 + 2 + 2 + 8); // 40
-#define TETHER_UPSTREAM6_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_upstream6_rawip"
-#define TETHER_UPSTREAM6_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_upstream6_ether"
-
-#define TETHER_UPSTREAM6_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM6_TC_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM6_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM6_TC_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM6_MAP_PATH BPF_PATH_TETHER "map_offload_tether_upstream6_map"
-
typedef struct {
uint32_t iif; // The input interface index
uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress)
@@ -162,23 +138,6 @@
} TetherUpstream6Key;
STRUCT_SIZE(TetherUpstream6Key, 12);
-#define TETHER_DOWNSTREAM4_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_downstream4_rawip"
-#define TETHER_DOWNSTREAM4_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_downstream4_ether"
-
-#define TETHER_DOWNSTREAM4_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM4_TC_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM4_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM4_TC_PROG_ETHER_NAME
-
-#define TETHER_DOWNSTREAM4_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream4_map"
-
-
-#define TETHER_UPSTREAM4_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_upstream4_rawip"
-#define TETHER_UPSTREAM4_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_upstream4_ether"
-
-#define TETHER_UPSTREAM4_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM4_TC_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM4_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM4_TC_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM4_MAP_PATH BPF_PATH_TETHER "map_offload_tether_upstream4_map"
-
typedef struct {
uint32_t iif; // The input interface index
uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress)
@@ -202,16 +161,4 @@
} Tether4Value;
STRUCT_SIZE(Tether4Value, 4 + 14 + 2 + 16 + 16 + 2 + 2 + 8); // 64
-#define TETHER_DOWNSTREAM_XDP_PROG_RAWIP_NAME "prog_offload_xdp_tether_downstream_rawip"
-#define TETHER_DOWNSTREAM_XDP_PROG_ETHER_NAME "prog_offload_xdp_tether_downstream_ether"
-
-#define TETHER_DOWNSTREAM_XDP_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM_XDP_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM_XDP_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM_XDP_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM_XDP_PROG_RAWIP_NAME "prog_offload_xdp_tether_upstream_rawip"
-#define TETHER_UPSTREAM_XDP_PROG_ETHER_NAME "prog_offload_xdp_tether_upstream_ether"
-
-#define TETHER_UPSTREAM_XDP_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM_XDP_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM_XDP_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM_XDP_PROG_ETHER_NAME
-
#undef STRUCT_SIZE
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 9a9d337..c5b8555 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -30,6 +30,9 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
+// The resulting .o needs to load on the Android T beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
#include "bpf_shared.h"
diff --git a/bpf_progs/dscp_policy.c b/bpf_progs/dscp_policy.c
index d5df7ef..538a9e4 100644
--- a/bpf_progs/dscp_policy.c
+++ b/bpf_progs/dscp_policy.c
@@ -27,6 +27,9 @@
#include <netinet/udp.h>
#include <string.h>
+// The resulting .o needs to load on the Android T beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include "bpf_helpers.h"
#include "dscp_policy.h"
diff --git a/bpf_progs/dscp_policy.h b/bpf_progs/dscp_policy.h
index 777c4ff..1637f7a 100644
--- a/bpf_progs/dscp_policy.h
+++ b/bpf_progs/dscp_policy.h
@@ -26,12 +26,11 @@
#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
-#ifndef v6_equal
-#define v6_equal(a, b) (a.s6_addr32[0] == b.s6_addr32[0] && \
- a.s6_addr32[1] == b.s6_addr32[1] && \
- a.s6_addr32[2] == b.s6_addr32[2] && \
- a.s6_addr32[3] == b.s6_addr32[3])
-#endif
+#define v6_equal(a, b) \
+ (((a.s6_addr32[0] ^ b.s6_addr32[0]) | \
+ (a.s6_addr32[1] ^ b.s6_addr32[1]) | \
+ (a.s6_addr32[2] ^ b.s6_addr32[2]) | \
+ (a.s6_addr32[3] ^ b.s6_addr32[3])) == 0)
// TODO: these are already defined in packages/modules/Connectivity/bpf_progs/bpf_net_helpers.h.
// smove to common location in future.
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index fe9a871..ae92686 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+// The resulting .o needs to load on the Android T Beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include <bpf_helpers.h>
#include <linux/bpf.h>
#include <linux/if.h>
@@ -194,7 +197,7 @@
BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
- uint8_t uidRules = uidEntry ? uidEntry->rule : 0;
+ uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
if (enabledRules) {
@@ -214,9 +217,16 @@
return BPF_DROP;
}
}
- if (direction == BPF_INGRESS && (uidRules & IIF_MATCH)) {
- // Drops packets not coming from lo nor the allowlisted interface
- if (allowed_iif && skb->ifindex != 1 && skb->ifindex != allowed_iif) {
+ if (direction == BPF_INGRESS && skb->ifindex != 1) {
+ if (uidRules & IIF_MATCH) {
+ if (allowed_iif && skb->ifindex != allowed_iif) {
+ // Drops packets not coming from lo nor the allowed interface
+ // allowed interface=0 is a wildcard and does not drop packets
+ return BPF_DROP_UNLESS_DNS;
+ }
+ } else if (uidRules & LOCKDOWN_VPN_MATCH) {
+ // Drops packets not coming from lo and rule does not have IIF_MATCH but has
+ // LOCKDOWN_VPN_MATCH
return BPF_DROP_UNLESS_DNS;
}
}
@@ -373,8 +383,7 @@
return BPF_NOMATCH;
}
-DEFINE_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
- KVER(4, 14, 0))
+DEFINE_BPF_PROG("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create)
(struct bpf_sock* sk) {
uint64_t gid_uid = bpf_get_current_uid_gid();
/*
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 92a774c..2ec0792 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -24,8 +24,8 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
-// The resulting .o needs to load on the Android S bpfloader v0.2
-#define BPFLOADER_MIN_VER 2u
+// The resulting .o needs to load on the Android S bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
@@ -355,88 +355,10 @@
DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 1024, AID_NETWORK_STACK)
-static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
- const bool downstream, const bool updatetime) {
- // Require ethernet dst mac address to be our unicast address.
- if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
-
- // Must be meta-ethernet IPv4 frame
- if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
-
- const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
-
- // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does
- // not trigger and thus we need to manually make sure we can read packet headers via DPA.
- // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
- // It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
- try_make_writable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
-
- void* data = (void*)(long)skb->data;
- const void* data_end = (void*)(long)skb->data_end;
- struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
- struct iphdr* ip = is_ethernet ? (void*)(eth + 1) : data;
-
- // Must have (ethernet and) ipv4 header
- if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_PIPE;
-
- // Ethertype - if present - must be IPv4
- if (is_ethernet && (eth->h_proto != htons(ETH_P_IP))) return TC_ACT_PIPE;
-
- // IP version must be 4
- if (ip->version != 4) TC_PUNT(INVALID_IP_VERSION);
-
- // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
- if (ip->ihl != 5) TC_PUNT(HAS_IP_OPTIONS);
-
- // Calculate the IPv4 one's complement checksum of the IPv4 header.
- __wsum sum4 = 0;
- for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
- sum4 += ((__u16*)ip)[i];
- }
- // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
- // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
- if (sum4 != 0xFFFF) TC_PUNT(CHECKSUM);
-
- // Minimum IPv4 total length is the size of the header
- if (ntohs(ip->tot_len) < sizeof(*ip)) TC_PUNT(TRUNCATED_IPV4);
-
- // We are incapable of dealing with IPv4 fragments
- if (ip->frag_off & ~htons(IP_DF)) TC_PUNT(IS_IP_FRAG);
-
- // Cannot decrement during forward if already zero or would be zero,
- // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
- if (ip->ttl <= 1) TC_PUNT(LOW_TTL);
-
- // If we cannot update the 'last_used' field due to lack of bpf_ktime_get_boot_ns() helper,
- // then it is not safe to offload UDP due to the small conntrack timeouts, as such,
- // in such a situation we can only support TCP. This also has the added nice benefit of
- // using a separate error counter, and thus making it obvious which version of the program
- // is loaded.
- if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
-
- // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
- // but no need to check this if !updatetime due to check immediately above.
- if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
- TC_PUNT(NON_TCP_UDP);
-
- // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
- // out all the non-tcp logic. Also note that at this point is_udp === !is_tcp.
- const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
-
- // This is a bit of a hack to make things easier on the bpf verifier.
- // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about
- // what offsets into the packet are valid and can spuriously reject the program, this is
- // because it fails to realize that is_tcp && !is_tcp is impossible)
- //
- // For both TCP & UDP we'll need to read and modify the src/dst ports, which so happen to
- // always be in the first 4 bytes of the L4 header. Additionally for UDP we'll need access
- // to the checksum field which is in bytes 7 and 8. While for TCP we'll need to read the
- // TCP flags (at offset 13) and access to the checksum field (2 bytes at offset 16).
- // As such we *always* need access to at least 8 bytes.
- if (data + l2_header_size + sizeof(*ip) + 8 > data_end) TC_PUNT(SHORT_L4_HEADER);
-
+static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb,
+ const int l2_header_size, void* data, const void* data_end,
+ struct ethhdr* eth, struct iphdr* ip, const bool is_ethernet,
+ const bool downstream, const bool updatetime, const bool is_tcp) {
struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL;
struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1);
@@ -625,6 +547,102 @@
return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
}
+static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
+ const bool downstream, const bool updatetime) {
+ // Require ethernet dst mac address to be our unicast address.
+ if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
+
+ // Must be meta-ethernet IPv4 frame
+ if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
+
+ const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+
+ // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does
+ // not trigger and thus we need to manually make sure we can read packet headers via DPA.
+ // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
+ // It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
+ try_make_writable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
+
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ struct iphdr* ip = is_ethernet ? (void*)(eth + 1) : data;
+
+ // Must have (ethernet and) ipv4 header
+ if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_PIPE;
+
+ // Ethertype - if present - must be IPv4
+ if (is_ethernet && (eth->h_proto != htons(ETH_P_IP))) return TC_ACT_PIPE;
+
+ // IP version must be 4
+ if (ip->version != 4) TC_PUNT(INVALID_IP_VERSION);
+
+ // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+ if (ip->ihl != 5) TC_PUNT(HAS_IP_OPTIONS);
+
+ // Calculate the IPv4 one's complement checksum of the IPv4 header.
+ __wsum sum4 = 0;
+ for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
+ sum4 += ((__u16*)ip)[i];
+ }
+ // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
+ // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
+ if (sum4 != 0xFFFF) TC_PUNT(CHECKSUM);
+
+ // Minimum IPv4 total length is the size of the header
+ if (ntohs(ip->tot_len) < sizeof(*ip)) TC_PUNT(TRUNCATED_IPV4);
+
+ // We are incapable of dealing with IPv4 fragments
+ if (ip->frag_off & ~htons(IP_DF)) TC_PUNT(IS_IP_FRAG);
+
+ // Cannot decrement during forward if already zero or would be zero,
+ // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
+ if (ip->ttl <= 1) TC_PUNT(LOW_TTL);
+
+ // If we cannot update the 'last_used' field due to lack of bpf_ktime_get_boot_ns() helper,
+ // then it is not safe to offload UDP due to the small conntrack timeouts, as such,
+ // in such a situation we can only support TCP. This also has the added nice benefit of
+ // using a separate error counter, and thus making it obvious which version of the program
+ // is loaded.
+ if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
+
+ // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
+ // but no need to check this if !updatetime due to check immediately above.
+ if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
+ TC_PUNT(NON_TCP_UDP);
+
+ // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
+ // out all the non-tcp logic. Also note that at this point is_udp === !is_tcp.
+ const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
+
+ // This is a bit of a hack to make things easier on the bpf verifier.
+ // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about
+ // what offsets into the packet are valid and can spuriously reject the program, this is
+ // because it fails to realize that is_tcp && !is_tcp is impossible)
+ //
+ // For both TCP & UDP we'll need to read and modify the src/dst ports, which so happen to
+ // always be in the first 4 bytes of the L4 header. Additionally for UDP we'll need access
+ // to the checksum field which is in bytes 7 and 8. While for TCP we'll need to read the
+ // TCP flags (at offset 13) and access to the checksum field (2 bytes at offset 16).
+ // As such we *always* need access to at least 8 bytes.
+ if (data + l2_header_size + sizeof(*ip) + 8 > data_end) TC_PUNT(SHORT_L4_HEADER);
+
+ // We're forcing the compiler to emit two copies of the following code, optimized
+ // separately for is_tcp being true or false. This simplifies the resulting bpf
+ // byte code sufficiently that the 4.14 bpf verifier is able to keep track of things.
+ // Without this (updatetime == true) case would fail to bpf verify on 4.14 even
+ // if the underlying requisite kernel support (bpf_ktime_get_boot_ns) was backported.
+ if (is_tcp) {
+ return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
+ is_ethernet, downstream, updatetime, /* is_tcp */ true);
+ } else {
+ return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
+ is_ethernet, downstream, updatetime, /* is_tcp */ false);
+ }
+}
+
// Full featured (required) implementations for 5.8+ kernels (these are S+ by definition)
DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK,
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index c9c73f1..f2fcc8c 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -18,8 +18,8 @@
#include <linux/in.h>
#include <linux/ip.h>
-// The resulting .o needs to load on the Android S bpfloader v0.2
-#define BPFLOADER_MIN_VER 2u
+// The resulting .o needs to load on the Android S bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
diff --git a/framework-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java
index 886d194..b8070f0 100644
--- a/framework-t/src/android/net/EthernetManager.java
+++ b/framework-t/src/android/net/EthernetManager.java
@@ -22,13 +22,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Build;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
@@ -573,7 +571,6 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
- @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
public void enableInterface(
@NonNull String iface,
@Nullable @CallbackExecutor Executor executor,
@@ -582,7 +579,7 @@
final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
executor, callback);
try {
- mService.connectNetwork(iface, proxy);
+ mService.enableInterface(iface, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -610,7 +607,6 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
- @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
public void disableInterface(
@NonNull String iface,
@Nullable @CallbackExecutor Executor executor,
@@ -619,7 +615,7 @@
final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
executor, callback);
try {
- mService.disconnectNetwork(iface, proxy);
+ mService.disableInterface(iface, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/framework-t/src/android/net/IEthernetManager.aidl b/framework-t/src/android/net/IEthernetManager.aidl
index 42e4c1a..c1efc29 100644
--- a/framework-t/src/android/net/IEthernetManager.aidl
+++ b/framework-t/src/android/net/IEthernetManager.aidl
@@ -43,8 +43,8 @@
void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request,
in INetworkInterfaceOutcomeReceiver listener);
- void connectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
- void disconnectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
+ void enableInterface(String iface, in INetworkInterfaceOutcomeReceiver listener);
+ void disableInterface(String iface, in INetworkInterfaceOutcomeReceiver listener);
void setEthernetEnabled(boolean enabled);
List<String> getInterfaceList();
}
diff --git a/framework-t/src/android/net/IpSecManager.java b/framework-t/src/android/net/IpSecManager.java
index 9cb0947..9cceac2 100644
--- a/framework-t/src/android/net/IpSecManager.java
+++ b/framework-t/src/android/net/IpSecManager.java
@@ -817,10 +817,10 @@
* </ol>
*
* @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
- * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise
- * this method will throw an {@link IllegalArgumentException}. If the
- * IpSecTunnelInterface is later added to this network, all outbound traffic will be
- * blackholed.
+ * This network MUST be a functional {@link Network} with valid {@link LinkProperties},
+ * and MUST never be the network exposing this IpSecTunnelInterface, otherwise this
+ * method will throw an {@link IllegalArgumentException}. If the IpSecTunnelInterface is
+ * later added to this network, all outbound traffic will be blackholed.
*/
// TODO: b/169171001 Update the documentation when transform migration is supported.
// The purpose of making updating network and applying transforms separate is to leave open
@@ -962,7 +962,6 @@
* IP header and IPsec Header on all inbound traffic).
* <p>Applications should probably not use this API directly.
*
- *
* @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied
* transform.
* @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which
diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java
index b59a890..29ea772 100644
--- a/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/framework-t/src/android/net/NetworkStatsCollection.java
@@ -694,6 +694,26 @@
}
}
+ /**
+ * Remove histories which contains or is before the cutoff timestamp.
+ * @hide
+ */
+ public void removeHistoryBefore(long cutoffMillis) {
+ final ArrayList<Key> knownKeys = new ArrayList<>();
+ knownKeys.addAll(mStats.keySet());
+
+ for (Key key : knownKeys) {
+ final NetworkStatsHistory history = mStats.get(key);
+ if (history.getStart() > cutoffMillis) continue;
+
+ history.removeBucketsStartingBefore(cutoffMillis);
+ if (history.size() == 0) {
+ mStats.remove(key);
+ }
+ mDirty = true;
+ }
+ }
+
private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
if (startMillis < mStartMillis) mStartMillis = startMillis;
if (endMillis > mEndMillis) mEndMillis = endMillis;
diff --git a/framework-t/src/android/net/NetworkStatsHistory.java b/framework-t/src/android/net/NetworkStatsHistory.java
index 301fef9..b45d44d 100644
--- a/framework-t/src/android/net/NetworkStatsHistory.java
+++ b/framework-t/src/android/net/NetworkStatsHistory.java
@@ -680,19 +680,21 @@
}
/**
- * Remove buckets older than requested cutoff.
+ * Remove buckets that start older than requested cutoff.
+ *
+ * This method will remove any bucket that contains any data older than the requested
+ * cutoff, even if that same bucket includes some data from after the cutoff.
+ *
* @hide
*/
- public void removeBucketsBefore(long cutoff) {
+ public void removeBucketsStartingBefore(final long cutoff) {
// TODO: Consider use getIndexBefore.
int i;
for (i = 0; i < bucketCount; i++) {
final long curStart = bucketStart[i];
- final long curEnd = curStart + bucketDuration;
- // cutoff happens before or during this bucket; everything before
- // this bucket should be removed.
- if (curEnd > cutoff) break;
+ // This bucket starts after or at the cutoff, so it should be kept.
+ if (curStart >= cutoff) break;
}
if (i > 0) {
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index a174fe3..9f9ee95 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -982,6 +982,16 @@
@SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5;
+ /**
+ * Firewall chain used for lockdown VPN.
+ * Denylist of apps that cannot receive incoming packets except on loopback because they are
+ * subject to an always-on VPN which is not currently connected.
+ *
+ * @see #BLOCKED_REASON_LOCKDOWN_VPN
+ * @hide
+ */
+ public static final int FIREWALL_CHAIN_LOCKDOWN_VPN = 6;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
@@ -989,7 +999,8 @@
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
- FIREWALL_CHAIN_LOW_POWER_STANDBY
+ FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ FIREWALL_CHAIN_LOCKDOWN_VPN
})
public @interface FirewallChain {}
// LINT.ThenChange(packages/modules/Connectivity/service/native/include/Common.h)
@@ -2589,9 +2600,24 @@
* {@hide}
*/
public ConnectivityManager(Context context, IConnectivityManager service) {
+ this(context, service, true /* newStatic */);
+ }
+
+ private ConnectivityManager(Context context, IConnectivityManager service, boolean newStatic) {
mContext = Objects.requireNonNull(context, "missing context");
mService = Objects.requireNonNull(service, "missing IConnectivityManager");
- sInstance = this;
+ // sInstance is accessed without a lock, so it may actually be reassigned several times with
+ // different ConnectivityManager, but that's still OK considering its usage.
+ if (sInstance == null && newStatic) {
+ final Context appContext = mContext.getApplicationContext();
+ // Don't create static ConnectivityManager instance again to prevent infinite loop.
+ // If the application context is null, we're either in the system process or
+ // it's the application context very early in app initialization. In both these
+ // cases, the passed-in Context will not be freed, so it's safe to pass it to the
+ // service. http://b/27532714 .
+ sInstance = new ConnectivityManager(appContext != null ? appContext : context, service,
+ false /* newStatic */);
+ }
}
/** {@hide} */
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index 126a5ff..a8f707e 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -1366,6 +1366,21 @@
}
/**
+ * Returns true if this link has a throw route.
+ *
+ * @return {@code true} if there is an exclude route, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasExcludeRoute() {
+ for (RouteInfo r : mRoutes) {
+ if (r.getType() == RouteInfo.RTN_THROW) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Compares this {@code LinkProperties} interface name against the target
*
* @param target LinkProperties to compare.
diff --git a/framework/src/android/net/ProfileNetworkPreference.java b/framework/src/android/net/ProfileNetworkPreference.java
index fb271e3..fdcab02 100644
--- a/framework/src/android/net/ProfileNetworkPreference.java
+++ b/framework/src/android/net/ProfileNetworkPreference.java
@@ -120,8 +120,8 @@
public String toString() {
return "ProfileNetworkPreference{"
+ "mPreference=" + getPreference()
- + "mIncludedUids=" + mIncludedUids.toString()
- + "mExcludedUids=" + mExcludedUids.toString()
+ + "mIncludedUids=" + Arrays.toString(mIncludedUids)
+ + "mExcludedUids=" + Arrays.toString(mExcludedUids)
+ "mPreferenceEnterpriseId=" + mPreferenceEnterpriseId
+ '}';
}
diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java
index ed6eb15..b80cff4 100644
--- a/framework/src/android/net/QosCallbackException.java
+++ b/framework/src/android/net/QosCallbackException.java
@@ -46,8 +46,10 @@
EX_TYPE_FILTER_NONE,
EX_TYPE_FILTER_NETWORK_RELEASED,
EX_TYPE_FILTER_SOCKET_NOT_BOUND,
+ EX_TYPE_FILTER_SOCKET_NOT_CONNECTED,
EX_TYPE_FILTER_NOT_SUPPORTED,
EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED,
+ EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ExceptionType {}
@@ -65,10 +67,16 @@
public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2;
/** {@hide} */
- public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3;
+ public static final int EX_TYPE_FILTER_SOCKET_NOT_CONNECTED = 3;
/** {@hide} */
- public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4;
+ public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 4;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 5;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED = 6;
/**
* Creates exception based off of a type and message. Not all types of exceptions accept a
@@ -83,12 +91,17 @@
return new QosCallbackException(new NetworkReleasedException());
case EX_TYPE_FILTER_SOCKET_NOT_BOUND:
return new QosCallbackException(new SocketNotBoundException());
+ case EX_TYPE_FILTER_SOCKET_NOT_CONNECTED:
+ return new QosCallbackException(new SocketNotConnectedException());
case EX_TYPE_FILTER_NOT_SUPPORTED:
return new QosCallbackException(new UnsupportedOperationException(
"This device does not support the specified filter"));
case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED:
return new QosCallbackException(
new SocketLocalAddressChangedException());
+ case EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED:
+ return new QosCallbackException(
+ new SocketRemoteAddressChangedException());
default:
Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'");
return new QosCallbackException(
diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java
index 5c1c3cc..b432644 100644
--- a/framework/src/android/net/QosFilter.java
+++ b/framework/src/android/net/QosFilter.java
@@ -90,5 +90,15 @@
*/
public abstract boolean matchesRemoteAddress(@NonNull InetAddress address,
int startPort, int endPort);
+
+ /**
+ * Determines whether or not the parameter will be matched with this filter.
+ *
+ * @param protocol the protocol such as TCP or UDP included in IP packet filter set of a QoS
+ * flow assigned on {@link Network}.
+ * @return whether the parameters match the socket type of the filter
+ * @hide
+ */
+ public abstract boolean matchesProtocol(int protocol);
}
diff --git a/framework/src/android/net/QosFilterParcelable.java b/framework/src/android/net/QosFilterParcelable.java
index da3b2cf..6e71fa3 100644
--- a/framework/src/android/net/QosFilterParcelable.java
+++ b/framework/src/android/net/QosFilterParcelable.java
@@ -104,7 +104,7 @@
if (mQosFilter instanceof QosSocketFilter) {
dest.writeInt(QOS_SOCKET_FILTER);
final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter;
- qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0);
+ qosSocketFilter.getQosSocketInfo().writeToParcelWithoutFd(dest, 0);
return;
}
dest.writeInt(NO_FILTER_PRESENT);
diff --git a/framework/src/android/net/QosSocketFilter.java b/framework/src/android/net/QosSocketFilter.java
index 69da7f4..5ceeb67 100644
--- a/framework/src/android/net/QosSocketFilter.java
+++ b/framework/src/android/net/QosSocketFilter.java
@@ -18,6 +18,13 @@
import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -74,19 +81,34 @@
* 2. In the scenario that the socket is now bound to a different local address, which can
* happen in the case of UDP, then
* {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned.
+ * 3. In the scenario that the UDP socket changed remote address, then
+ * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED} is returned.
+ *
* @return validation error code
*/
@Override
public int validate() {
- final InetSocketAddress sa = getAddressFromFileDescriptor();
- if (sa == null) {
- return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+ final InetSocketAddress sa = getLocalAddressFromFileDescriptor();
+
+ if (sa == null || (sa.getAddress().isAnyLocalAddress() && sa.getPort() == 0)) {
+ return EX_TYPE_FILTER_SOCKET_NOT_BOUND;
}
if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) {
return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
}
+ if (mQosSocketInfo.getRemoteSocketAddress() != null) {
+ final InetSocketAddress da = getRemoteAddressFromFileDescriptor();
+ if (da == null) {
+ return EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+ }
+
+ if (!da.equals(mQosSocketInfo.getRemoteSocketAddress())) {
+ return EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+ }
+ }
+
return EX_TYPE_FILTER_NONE;
}
@@ -98,17 +120,14 @@
* @return the local address
*/
@Nullable
- private InetSocketAddress getAddressFromFileDescriptor() {
+ private InetSocketAddress getLocalAddressFromFileDescriptor() {
final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
- if (parcelFileDescriptor == null) return null;
-
final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
- if (fd == null) return null;
final SocketAddress address;
try {
address = Os.getsockname(fd);
- } catch (final ErrnoException e) {
+ } catch (ErrnoException e) {
Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e);
return null;
}
@@ -119,6 +138,31 @@
}
/**
+ * The remote address of the socket's connected.
+ *
+ * <p>Note: If the socket is no longer connected, null is returned.
+ *
+ * @return the remote address
+ */
+ @Nullable
+ private InetSocketAddress getRemoteAddressFromFileDescriptor() {
+ final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
+ final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
+
+ final SocketAddress address;
+ try {
+ address = Os.getpeername(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "getAddressFromFileDescriptor: getRemoteAddress exception", e);
+ return null;
+ }
+ if (address instanceof InetSocketAddress) {
+ return (InetSocketAddress) address;
+ }
+ return null;
+ }
+
+ /**
* The network used with this filter.
*
* @return the registered {@link Network}
@@ -156,6 +200,18 @@
}
/**
+ * @inheritDoc
+ */
+ @Override
+ public boolean matchesProtocol(final int protocol) {
+ if ((mQosSocketInfo.getSocketType() == SOCK_STREAM && protocol == IPPROTO_TCP)
+ || (mQosSocketInfo.getSocketType() == SOCK_DGRAM && protocol == IPPROTO_UDP)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)}
* and {@link QosSocketFilter#matchesRemoteAddress(InetAddress, int, int)} with the
* filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}.
@@ -174,6 +230,7 @@
final int startPort, final int endPort) {
return startPort <= filterSocketAddress.getPort()
&& endPort >= filterSocketAddress.getPort()
- && filterSocketAddress.getAddress().equals(address);
+ && (address.isAnyLocalAddress()
+ || filterSocketAddress.getAddress().equals(address));
}
}
diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java
index 39c2f33..49ac22b 100644
--- a/framework/src/android/net/QosSocketInfo.java
+++ b/framework/src/android/net/QosSocketInfo.java
@@ -165,25 +165,28 @@
/* Parcelable methods */
private QosSocketInfo(final Parcel in) {
mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in));
- mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ final boolean withFd = in.readBoolean();
+ if (withFd) {
+ mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ } else {
+ mParcelFileDescriptor = null;
+ }
- final int localAddressLength = in.readInt();
- mLocalSocketAddress = readSocketAddress(in, localAddressLength);
-
- final int remoteAddressLength = in.readInt();
- mRemoteSocketAddress = remoteAddressLength == 0 ? null
- : readSocketAddress(in, remoteAddressLength);
+ mLocalSocketAddress = readSocketAddress(in);
+ mRemoteSocketAddress = readSocketAddress(in);
mSocketType = in.readInt();
}
- private @NonNull InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) {
- final byte[] address = new byte[addressLength];
- in.readByteArray(address);
+ private InetSocketAddress readSocketAddress(final Parcel in) {
+ final byte[] addrBytes = in.createByteArray();
+ if (addrBytes == null) {
+ return null;
+ }
final int port = in.readInt();
try {
- return new InetSocketAddress(InetAddress.getByAddress(address), port);
+ return new InetSocketAddress(InetAddress.getByAddress(addrBytes), port);
} catch (final UnknownHostException e) {
/* This can never happen. UnknownHostException will never be thrown
since the address provided is numeric and non-null. */
@@ -198,20 +201,35 @@
@Override
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
- mNetwork.writeToParcel(dest, 0);
- mParcelFileDescriptor.writeToParcel(dest, 0);
+ writeToParcelInternal(dest, flags, /*includeFd=*/ true);
+ }
- final byte[] localAddress = mLocalSocketAddress.getAddress().getAddress();
- dest.writeInt(localAddress.length);
- dest.writeByteArray(localAddress);
+ /**
+ * Used when sending QosSocketInfo to telephony, which does not need access to the socket FD.
+ * @hide
+ */
+ public void writeToParcelWithoutFd(@NonNull final Parcel dest, final int flags) {
+ writeToParcelInternal(dest, flags, /*includeFd=*/ false);
+ }
+
+ private void writeToParcelInternal(
+ @NonNull final Parcel dest, final int flags, boolean includeFd) {
+ mNetwork.writeToParcel(dest, 0);
+
+ if (includeFd) {
+ dest.writeBoolean(true);
+ mParcelFileDescriptor.writeToParcel(dest, 0);
+ } else {
+ dest.writeBoolean(false);
+ }
+
+ dest.writeByteArray(mLocalSocketAddress.getAddress().getAddress());
dest.writeInt(mLocalSocketAddress.getPort());
if (mRemoteSocketAddress == null) {
- dest.writeInt(0);
+ dest.writeByteArray(null);
} else {
- final byte[] remoteAddress = mRemoteSocketAddress.getAddress().getAddress();
- dest.writeInt(remoteAddress.length);
- dest.writeByteArray(remoteAddress);
+ dest.writeByteArray(mRemoteSocketAddress.getAddress().getAddress());
dest.writeInt(mRemoteSocketAddress.getPort());
}
dest.writeInt(mSocketType);
diff --git a/framework/src/android/net/SocketNotConnectedException.java b/framework/src/android/net/SocketNotConnectedException.java
new file mode 100644
index 0000000..fa2a615
--- /dev/null
+++ b/framework/src/android/net/SocketNotConnectedException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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;
+
+/**
+ * Thrown when a previously bound socket becomes unbound.
+ *
+ * @hide
+ */
+public class SocketNotConnectedException extends Exception {
+ /** @hide */
+ public SocketNotConnectedException() {
+ super("The socket is not connected");
+ }
+}
diff --git a/framework/src/android/net/SocketRemoteAddressChangedException.java b/framework/src/android/net/SocketRemoteAddressChangedException.java
new file mode 100644
index 0000000..ecaeebc
--- /dev/null
+++ b/framework/src/android/net/SocketRemoteAddressChangedException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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;
+
+/**
+ * Thrown when the local address of the socket has changed.
+ *
+ * @hide
+ */
+public class SocketRemoteAddressChangedException extends Exception {
+ /** @hide */
+ public SocketRemoteAddressChangedException() {
+ super("The remote address of the socket changed");
+ }
+}
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index f3dfb57..42d0de5 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -73,16 +73,7 @@
}
RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
-
- // For the devices that support cgroup socket filter, the socket filter
- // should be loaded successfully by bpfloader. So we attach the filter to
- // cgroup if the program is pinned properly.
- // TODO: delete the if statement once all devices should support cgroup
- // socket filter (ie. the minimum kernel version required is 4.14).
- if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
- RETURN_IF_NOT_OK(
- attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
- }
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
return netdutils::status::ok;
}
@@ -113,6 +104,7 @@
RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
BPF_ANY));
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
@@ -242,7 +234,7 @@
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
if (!res.ok()) {
- ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
+ ALOGE("Failed to untag socket: %s", strerror(res.error().code()));
return -res.error().code();
}
return 0;
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 4d605ce..6c7a15e 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -205,6 +205,10 @@
configuration.error().message().c_str());
return -configuration.error().code();
}
+ if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+ ALOGE("%s unknown configuration value: %d", __func__, configuration.value());
+ return -EINVAL;
+ }
const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
BpfMap<StatsKey, StatsValue> statsMap(statsMapPath);
if (!statsMap.isValid()) {
diff --git a/service-t/src/com/android/server/IpSecService.java b/service-t/src/com/android/server/IpSecService.java
index 4bc40ea..16b9f1e 100644
--- a/service-t/src/com/android/server/IpSecService.java
+++ b/service-t/src/com/android/server/IpSecService.java
@@ -1452,6 +1452,11 @@
final ConnectivityManager connectivityManager =
mContext.getSystemService(ConnectivityManager.class);
final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+ if (lp == null) {
+ throw new IllegalArgumentException(
+ "LinkProperties is null. The underlyingNetwork may not be functional");
+ }
+
if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
throw new IllegalArgumentException(
"Underlying network cannot be the network being exposed by this tunnel");
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 4086e4e..6def44f 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -513,7 +513,7 @@
break;
}
- String name = fullName.substring(0, index);
+ String name = unescape(fullName.substring(0, index));
String rest = fullName.substring(index);
String type = rest.replace(".local.", "");
@@ -590,6 +590,35 @@
}
}
+ // The full service name is escaped from standard DNS rules on mdnsresponder, making it suitable
+ // for passing to standard system DNS APIs such as res_query() . Thus, make the service name
+ // unescape for getting right service address. See "Notes on DNS Name Escaping" on
+ // external/mdnsresponder/mDNSShared/dns_sd.h for more details.
+ private String unescape(String s) {
+ StringBuilder sb = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); ++i) {
+ char c = s.charAt(i);
+ if (c == '\\') {
+ if (++i >= s.length()) {
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = s.charAt(i);
+ if (c != '.' && c != '\\') {
+ if (i + 2 >= s.length()) {
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = (char) ((c - '0') * 100 + (s.charAt(i + 1) - '0') * 10
+ + (s.charAt(i + 2) - '0'));
+ i += 2;
+ }
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
@VisibleForTesting
NsdService(Context ctx, Handler handler, long cleanupDelayMs) {
mCleanupDelayMs = cleanupDelayMs;
@@ -738,7 +767,13 @@
String type = service.getServiceType();
int port = service.getPort();
byte[] textRecord = service.getTxtRecord();
- return mMDnsManager.registerService(regId, name, type, port, textRecord, IFACE_IDX_ANY);
+ final Network network = service.getNetwork();
+ final int registerInterface = getNetworkInterfaceIndex(network);
+ if (network != null && registerInterface == IFACE_IDX_ANY) {
+ Log.e(TAG, "Interface to register service on not found");
+ return false;
+ }
+ return mMDnsManager.registerService(regId, name, type, port, textRecord, registerInterface);
}
private boolean unregisterService(int regId) {
diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
index 6b623f4..6006539 100644
--- a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
+++ b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
@@ -16,23 +16,37 @@
package com.android.server.ethernet;
+import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+
import android.annotation.Nullable;
+import android.content.ApexEnvironment;
import android.net.IpConfiguration;
import android.os.Environment;
import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.net.IpConfigStore;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
/**
* This class provides an API to store and manage Ethernet network configuration.
*/
public class EthernetConfigStore {
- private static final String ipConfigFile = Environment.getDataDirectory() +
- "/misc/ethernet/ipconfig.txt";
+ private static final String TAG = EthernetConfigStore.class.getSimpleName();
+ private static final String CONFIG_FILE = "ipconfig.txt";
+ private static final String FILE_PATH = "/misc/ethernet/";
+ private static final String LEGACY_IP_CONFIG_FILE_PATH = Environment.getDataDirectory()
+ + FILE_PATH;
+ private static final String APEX_IP_CONFIG_FILE_PATH = ApexEnvironment.getApexEnvironment(
+ TETHERING_MODULE_NAME).getDeviceProtectedDataDir() + FILE_PATH;
private IpConfigStore mStore = new IpConfigStore();
- private ArrayMap<String, IpConfiguration> mIpConfigurations;
+ private final ArrayMap<String, IpConfiguration> mIpConfigurations;
private IpConfiguration mIpConfigurationForDefaultInterface;
private final Object mSync = new Object();
@@ -40,22 +54,70 @@
mIpConfigurations = new ArrayMap<>(0);
}
- public void read() {
- synchronized (mSync) {
- ArrayMap<String, IpConfiguration> configs =
- IpConfigStore.readIpConfigurations(ipConfigFile);
+ private static boolean doesConfigFileExist(final String filepath) {
+ return new File(filepath).exists();
+ }
- // This configuration may exist in old file versions when there was only a single active
- // Ethernet interface.
- if (configs.containsKey("0")) {
- mIpConfigurationForDefaultInterface = configs.remove("0");
+ private void writeLegacyIpConfigToApexPath(final String newFilePath, final String oldFilePath,
+ final String filename) {
+ final File directory = new File(newFilePath);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+
+ // Write the legacy IP config to the apex file path.
+ FileOutputStream fos = null;
+ final AtomicFile dst = new AtomicFile(new File(newFilePath + filename));
+ final AtomicFile src = new AtomicFile(new File(oldFilePath + filename));
+ try {
+ final byte[] raw = src.readFully();
+ if (raw.length > 0) {
+ fos = dst.startWrite();
+ fos.write(raw);
+ fos.flush();
+ dst.finishWrite(fos);
}
-
- mIpConfigurations = configs;
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to sync the legacy IP config to the apex file path.");
+ dst.failWrite(fos);
}
}
+ public void read() {
+ read(APEX_IP_CONFIG_FILE_PATH, LEGACY_IP_CONFIG_FILE_PATH, CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void read(final String newFilePath, final String oldFilePath, final String filename) {
+ synchronized (mSync) {
+ // Attempt to read the IP configuration from apex file path first.
+ if (doesConfigFileExist(newFilePath + filename)) {
+ loadConfigFileLocked(newFilePath + filename);
+ return;
+ }
+
+ // If the config file doesn't exist in the apex file path, attempt to read it from
+ // the legacy file path, if config file exists, write the legacy IP configuration to
+ // apex config file path, this should just happen on the first boot. New or updated
+ // config entries are only written to the apex config file later.
+ if (!doesConfigFileExist(oldFilePath + filename)) return;
+ loadConfigFileLocked(oldFilePath + filename);
+ writeLegacyIpConfigToApexPath(newFilePath, oldFilePath, filename);
+ }
+ }
+
+ private void loadConfigFileLocked(final String filepath) {
+ final ArrayMap<String, IpConfiguration> configs =
+ IpConfigStore.readIpConfigurations(filepath);
+ mIpConfigurations.putAll(configs);
+ }
+
public void write(String iface, IpConfiguration config) {
+ write(iface, config, APEX_IP_CONFIG_FILE_PATH + CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void write(String iface, IpConfiguration config, String filepath) {
boolean modified;
synchronized (mSync) {
@@ -67,7 +129,7 @@
}
if (modified) {
- mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations);
+ mStore.writeIpConfigurations(filepath, mIpConfigurations);
}
}
}
@@ -80,9 +142,6 @@
@Nullable
public IpConfiguration getIpConfigurationForDefaultInterface() {
- synchronized (mSync) {
- return mIpConfigurationForDefaultInterface == null
- ? null : new IpConfiguration(mIpConfigurationForDefaultInterface);
- }
+ return null;
}
}
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index fe27335..79802fb 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -31,10 +31,9 @@
import android.net.LinkProperties;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
-import android.net.NetworkSpecifier;
+import android.net.NetworkScore;
import android.net.ip.IIpClient;
import android.net.ip.IpClientCallbacks;
import android.net.ip.IpClientManager;
@@ -46,6 +45,7 @@
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -56,25 +56,23 @@
import java.io.FileDescriptor;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
- * {@link NetworkFactory} that represents Ethernet networks.
- *
- * This class reports a static network score of 70 when it is tracking an interface and that
- * interface's link is up, and a score of 0 otherwise.
+ * {@link NetworkProvider} that manages NetworkOffers for Ethernet networks.
*/
-public class EthernetNetworkFactory extends NetworkFactory {
+public class EthernetNetworkFactory {
private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
final static boolean DBG = true;
- private final static int NETWORK_SCORE = 70;
private static final String NETWORK_TYPE = "Ethernet";
private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
new ConcurrentHashMap<>();
private final Handler mHandler;
private final Context mContext;
+ private final NetworkProvider mProvider;
final Dependencies mDeps;
public static class Dependencies {
@@ -109,54 +107,24 @@
}
public EthernetNetworkFactory(Handler handler, Context context) {
- this(handler, context, new Dependencies());
+ this(handler, context, new NetworkProvider(context, handler.getLooper(), TAG),
+ new Dependencies());
}
@VisibleForTesting
- EthernetNetworkFactory(Handler handler, Context context, Dependencies deps) {
- super(handler.getLooper(), context, NETWORK_TYPE, createDefaultNetworkCapabilities());
-
+ EthernetNetworkFactory(Handler handler, Context context, NetworkProvider provider,
+ Dependencies deps) {
mHandler = handler;
mContext = context;
+ mProvider = provider;
mDeps = deps;
-
- setScoreFilter(NETWORK_SCORE);
}
- @Override
- public boolean acceptRequest(NetworkRequest request) {
- if (DBG) {
- Log.d(TAG, "acceptRequest, request: " + request);
- }
-
- return networkForRequest(request) != null;
- }
-
- @Override
- protected void needNetworkFor(NetworkRequest networkRequest) {
- NetworkInterfaceState network = networkForRequest(networkRequest);
-
- if (network == null) {
- Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
- return;
- }
-
- if (++network.refCount == 1) {
- network.start();
- }
- }
-
- @Override
- protected void releaseNetworkFor(NetworkRequest networkRequest) {
- NetworkInterfaceState network = networkForRequest(networkRequest);
- if (network == null) {
- Log.e(TAG, "releaseNetworkFor, failed to get a network for " + networkRequest);
- return;
- }
-
- if (--network.refCount == 0) {
- network.stop();
- }
+ /**
+ * Registers the network provider with the system.
+ */
+ public void register() {
+ mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(mProvider);
}
/**
@@ -194,9 +162,8 @@
}
final NetworkInterfaceState iface = new NetworkInterfaceState(
- ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, this, mDeps);
+ ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, mProvider, mDeps);
mTrackingInterfaces.put(ifaceName, iface);
- updateCapabilityFilter();
}
@VisibleForTesting
@@ -237,7 +204,6 @@
final NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
iface.updateInterface(ipConfig, capabilities, listener);
mTrackingInterfaces.put(ifaceName, iface);
- updateCapabilityFilter();
}
private static NetworkCapabilities mixInCapabilities(NetworkCapabilities nc,
@@ -248,16 +214,6 @@
return builder.build();
}
- private void updateCapabilityFilter() {
- NetworkCapabilities capabilitiesFilter = createDefaultNetworkCapabilities();
- for (NetworkInterfaceState iface: mTrackingInterfaces.values()) {
- capabilitiesFilter = mixInCapabilities(capabilitiesFilter, iface.mCapabilities);
- }
-
- if (DBG) Log.d(TAG, "updateCapabilityFilter: " + capabilitiesFilter);
- setCapabilityFilter(capabilitiesFilter);
- }
-
private static NetworkCapabilities createDefaultNetworkCapabilities() {
return NetworkCapabilities.Builder
.withoutDefaultCapabilities()
@@ -268,11 +224,8 @@
protected void removeInterface(String interfaceName) {
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
if (iface != null) {
- iface.maybeSendNetworkManagementCallbackForAbort();
- iface.stop();
+ iface.destroy();
}
-
- updateCapabilityFilter();
}
/** Returns true if state has been modified */
@@ -304,37 +257,6 @@
return mTrackingInterfaces.containsKey(ifaceName);
}
- private NetworkInterfaceState networkForRequest(NetworkRequest request) {
- String requestedIface = null;
-
- NetworkSpecifier specifier = request.getNetworkSpecifier();
- if (specifier instanceof EthernetNetworkSpecifier) {
- requestedIface = ((EthernetNetworkSpecifier) specifier)
- .getInterfaceName();
- }
-
- NetworkInterfaceState network = null;
- if (!TextUtils.isEmpty(requestedIface)) {
- NetworkInterfaceState n = mTrackingInterfaces.get(requestedIface);
- if (n != null && request.canBeSatisfiedBy(n.mCapabilities)) {
- network = n;
- }
- } else {
- for (NetworkInterfaceState n : mTrackingInterfaces.values()) {
- if (request.canBeSatisfiedBy(n.mCapabilities) && n.mLinkUp) {
- network = n;
- break;
- }
- }
- }
-
- if (DBG) {
- Log.i(TAG, "networkForRequest, request: " + request + ", network: " + network);
- }
-
- return network;
- }
-
private static void maybeSendNetworkManagementCallback(
@Nullable final INetworkInterfaceOutcomeReceiver listener,
@Nullable final String iface,
@@ -361,14 +283,16 @@
private final String mHwAddress;
private final Handler mHandler;
private final Context mContext;
- private final NetworkFactory mNetworkFactory;
+ private final NetworkProvider mNetworkProvider;
private final Dependencies mDeps;
+ private final NetworkProvider.NetworkOfferCallback mNetworkOfferCallback;
private static String sTcpBufferSizes = null; // Lazy initialized.
private boolean mLinkUp;
private int mLegacyType;
private LinkProperties mLinkProperties = new LinkProperties();
+ private Set<NetworkRequest> mRequests = new ArraySet<>();
private volatile @Nullable IpClientManager mIpClient;
private @NonNull NetworkCapabilities mCapabilities;
@@ -397,8 +321,6 @@
ConnectivityManager.TYPE_NONE);
}
- long refCount = 0;
-
private class EthernetIpClientCallback extends IpClientCallbacks {
private final ConditionVariable mIpClientStartCv = new ConditionVariable(false);
private final ConditionVariable mIpClientShutdownCv = new ConditionVariable(false);
@@ -469,17 +391,47 @@
}
}
+ private class EthernetNetworkOfferCallback implements NetworkProvider.NetworkOfferCallback {
+ @Override
+ public void onNetworkNeeded(@NonNull NetworkRequest request) {
+ if (DBG) {
+ Log.d(TAG, String.format("%s: onNetworkNeeded for request: %s", name, request));
+ }
+ // When the network offer is first registered, onNetworkNeeded is called with all
+ // existing requests.
+ // ConnectivityService filters requests for us based on the NetworkCapabilities
+ // passed in the registerNetworkOffer() call.
+ mRequests.add(request);
+ // if the network is already started, this is a no-op.
+ start();
+ }
+
+ @Override
+ public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+ if (DBG) {
+ Log.d(TAG,
+ String.format("%s: onNetworkUnneeded for request: %s", name, request));
+ }
+ mRequests.remove(request);
+ if (mRequests.isEmpty()) {
+ // not currently serving any requests, stop the network.
+ stop();
+ }
+ }
+ }
+
NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
@NonNull IpConfiguration ipConfig, @NonNull NetworkCapabilities capabilities,
- NetworkFactory networkFactory, Dependencies deps) {
+ NetworkProvider networkProvider, Dependencies deps) {
name = ifaceName;
mIpConfig = Objects.requireNonNull(ipConfig);
mCapabilities = Objects.requireNonNull(capabilities);
mLegacyType = getLegacyType(mCapabilities);
mHandler = handler;
mContext = context;
- mNetworkFactory = networkFactory;
+ mNetworkProvider = networkProvider;
mDeps = deps;
+ mNetworkOfferCallback = new EthernetNetworkOfferCallback();
mHwAddress = hwAddress;
}
@@ -502,9 +454,21 @@
+ "transport type.");
}
+ private static NetworkScore getBestNetworkScore() {
+ return new NetworkScore.Builder().build();
+ }
+
private void setCapabilities(@NonNull final NetworkCapabilities capabilities) {
mCapabilities = new NetworkCapabilities(capabilities);
mLegacyType = getLegacyType(mCapabilities);
+
+ if (mLinkUp) {
+ // registering a new network offer will update the existing one, not install a
+ // new one.
+ mNetworkProvider.registerNetworkOffer(getBestNetworkScore(),
+ new NetworkCapabilities(capabilities), cmd -> mHandler.post(cmd),
+ mNetworkOfferCallback);
+ }
}
void updateInterface(@Nullable final IpConfiguration ipConfig,
@@ -575,7 +539,7 @@
.setLegacyExtraInfo(mHwAddress)
.build();
mNetworkAgent = mDeps.makeEthernetNetworkAgent(mContext, mHandler.getLooper(),
- mCapabilities, mLinkProperties, config, mNetworkFactory.getProvider(),
+ mCapabilities, mLinkProperties, config, mNetworkProvider,
new EthernetNetworkAgent.Callbacks() {
@Override
public void onNetworkUnwanted() {
@@ -666,20 +630,21 @@
mLinkUp = up;
if (!up) { // was up, goes down
- // Send an abort on a provisioning request callback if necessary before stopping.
- maybeSendNetworkManagementCallbackForAbort();
- stop();
+ // retract network offer and stop IpClient.
+ destroy();
// If only setting the interface down, send a callback to signal completion.
EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, name, null);
} else { // was down, goes up
- stop();
- start(listener);
+ // register network offer
+ mNetworkProvider.registerNetworkOffer(getBestNetworkScore(),
+ new NetworkCapabilities(mCapabilities), (cmd) -> mHandler.post(cmd),
+ mNetworkOfferCallback);
}
return true;
}
- void stop() {
+ private void stop() {
// Invalidate all previous start requests
if (mIpClient != null) {
mIpClient.shutdown();
@@ -695,6 +660,13 @@
mLinkProperties.clear();
}
+ public void destroy() {
+ mNetworkProvider.unregisterNetworkOffer(mNetworkOfferCallback);
+ maybeSendNetworkManagementCallbackForAbort();
+ stop();
+ mRequests.clear();
+ }
+
private static void provisionIpClient(@NonNull final IpClientManager ipClient,
@NonNull final IpConfiguration config, @NonNull final String tcpBufferSizes) {
if (config.getProxySettings() == ProxySettings.STATIC ||
@@ -734,7 +706,6 @@
@Override
public String toString() {
return getClass().getSimpleName() + "{ "
- + "refCount: " + refCount + ", "
+ "iface: " + name + ", "
+ "up: " + mLinkUp + ", "
+ "hwAddress: " + mHwAddress + ", "
@@ -747,7 +718,6 @@
}
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
- super.dump(fd, pw, args);
pw.println(getClass().getSimpleName());
pw.println("Tracking interfaces:");
pw.increaseIndent();
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index 5e830ad..71d3e4f 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -22,11 +22,11 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.EthernetNetworkUpdateRequest;
import android.net.IEthernetManager;
import android.net.IEthernetServiceListener;
import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.ITetheredInterfaceCallback;
-import android.net.EthernetNetworkUpdateRequest;
import android.net.IpConfiguration;
import android.net.NetworkCapabilities;
import android.os.Binder;
@@ -260,27 +260,27 @@
}
@Override
- public void connectNetwork(@NonNull final String iface,
+ public void enableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
- Log.i(TAG, "connectNetwork called with: iface=" + iface + ", listener=" + listener);
+ Log.i(TAG, "enableInterface called with: iface=" + iface + ", listener=" + listener);
Objects.requireNonNull(iface);
throwIfEthernetNotStarted();
- enforceAdminPermission(iface, true, "connectNetwork()");
+ enforceAdminPermission(iface, false, "enableInterface()");
- mTracker.connectNetwork(iface, listener);
+ mTracker.enableInterface(iface, listener);
}
@Override
- public void disconnectNetwork(@NonNull final String iface,
+ public void disableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
- Log.i(TAG, "disconnectNetwork called with: iface=" + iface + ", listener=" + listener);
+ Log.i(TAG, "disableInterface called with: iface=" + iface + ", listener=" + listener);
Objects.requireNonNull(iface);
throwIfEthernetNotStarted();
- enforceAdminPermission(iface, true, "connectNetwork()");
+ enforceAdminPermission(iface, false, "disableInterface()");
- mTracker.disconnectNetwork(iface, listener);
+ mTracker.disableInterface(iface, listener);
}
@Override
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index e9053dd..c8a0412 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -285,13 +285,13 @@
}
@VisibleForTesting(visibility = PACKAGE)
- protected void connectNetwork(@NonNull final String iface,
+ protected void enableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
mHandler.post(() -> updateInterfaceState(iface, true, listener));
}
@VisibleForTesting(visibility = PACKAGE)
- protected void disconnectNetwork(@NonNull final String iface,
+ protected void disableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
mHandler.post(() -> updateInterfaceState(iface, false, listener));
}
@@ -586,14 +586,18 @@
}
}
- private class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
+ @VisibleForTesting
+ class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
@Override
public void onInterfaceLinkStateChanged(String iface, boolean up) {
if (DBG) {
Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
}
- mHandler.post(() -> updateInterfaceState(iface, up));
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ updateInterfaceState(iface, up);
+ });
}
@Override
@@ -601,7 +605,10 @@
if (DBG) {
Log.i(TAG, "onInterfaceAdded, iface: " + iface);
}
- mHandler.post(() -> maybeTrackInterface(iface));
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ maybeTrackInterface(iface);
+ });
}
@Override
@@ -609,7 +616,10 @@
if (DBG) {
Log.i(TAG, "onInterfaceRemoved, iface: " + iface);
}
- mHandler.post(() -> stopTrackingInterface(iface));
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ stopTrackingInterface(iface);
+ });
}
}
@@ -888,6 +898,8 @@
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
postAndWaitForRunnable(() -> {
pw.println(getClass().getSimpleName());
+ pw.println("Ethernet State: "
+ + (mEthernetState == ETHERNET_STATE_ENABLED ? "enabled" : "disabled"));
pw.println("Ethernet interface name filter: " + mIfaceMatch);
pw.println("Default interface: " + mDefaultInterface);
pw.println("Default interface mode: " + mDefaultInterfaceMode);
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
index 5011dec..3b44d81 100644
--- a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -38,7 +38,7 @@
private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName();
// This is current path but may be changed soon.
private static final String IFACE_INDEX_NAME_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_iface_index_name_map";
+ "/sys/fs/bpf/netd_shared/map_netd_iface_index_name_map";
private final IBpfMap<U32, InterfaceMapValue> mBpfMap;
private final INetd mNetd;
private final Handler mHandler;
diff --git a/service-t/src/com/android/server/net/NetworkStatsObservers.java b/service-t/src/com/android/server/net/NetworkStatsObservers.java
index fdfc893..df4e7f5 100644
--- a/service-t/src/com/android/server/net/NetworkStatsObservers.java
+++ b/service-t/src/com/android/server/net/NetworkStatsObservers.java
@@ -18,6 +18,7 @@
import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES;
+import android.annotation.NonNull;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -38,10 +39,12 @@
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.PerUidCounter;
import java.util.concurrent.atomic.AtomicInteger;
@@ -52,16 +55,26 @@
*/
class NetworkStatsObservers {
private static final String TAG = "NetworkStatsObservers";
+ private static final boolean LOG = true;
private static final boolean LOGV = false;
private static final int MSG_REGISTER = 1;
private static final int MSG_UNREGISTER = 2;
private static final int MSG_UPDATE_STATS = 3;
+ private static final int DUMP_USAGE_REQUESTS_COUNT = 200;
+
+ // The maximum number of request allowed per uid before an exception is thrown.
+ @VisibleForTesting
+ static final int MAX_REQUESTS_PER_UID = 100;
+
// All access to this map must be done from the handler thread.
// indexed by DataUsageRequest#requestId
private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
+ // Request counters per uid, this is thread safe.
+ private final PerUidCounter mDataUsageRequestsPerUid = new PerUidCounter(MAX_REQUESTS_PER_UID);
+
// Sequence number of DataUsageRequests
private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
@@ -77,13 +90,16 @@
*
* @return the normalized request wrapped within {@link RequestInfo}.
*/
- public DataUsageRequest register(Context context, DataUsageRequest inputRequest,
- IUsageCallback callback, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+ public DataUsageRequest register(@NonNull Context context,
+ @NonNull DataUsageRequest inputRequest, @NonNull IUsageCallback callback,
+ int callingPid, int callingUid, @NonNull String callingPackage,
+ @NetworkStatsAccess.Level int accessLevel) {
DataUsageRequest request = buildRequest(context, inputRequest, callingUid);
- RequestInfo requestInfo = buildRequestInfo(request, callback, callingUid,
- accessLevel);
+ RequestInfo requestInfo = buildRequestInfo(request, callback, callingPid, callingUid,
+ callingPackage, accessLevel);
+ if (LOG) Log.d(TAG, "Registering observer for " + requestInfo);
+ mDataUsageRequestsPerUid.incrementCountOrThrow(callingUid);
- if (LOGV) Log.v(TAG, "Registering observer for " + request);
getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
return request;
}
@@ -172,7 +188,7 @@
RequestInfo requestInfo;
requestInfo = mDataUsageRequests.get(request.requestId);
if (requestInfo == null) {
- if (LOGV) Log.v(TAG, "Trying to unregister unknown request " + request);
+ if (LOG) Log.d(TAG, "Trying to unregister unknown request " + request);
return;
}
if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
@@ -180,8 +196,9 @@
return;
}
- if (LOGV) Log.v(TAG, "Unregistering " + request);
+ if (LOG) Log.d(TAG, "Unregistering " + requestInfo);
mDataUsageRequests.remove(request.requestId);
+ mDataUsageRequestsPerUid.decrementCountOrThrow(callingUid);
requestInfo.unlinkDeathRecipient();
requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
}
@@ -214,18 +231,19 @@
}
private RequestInfo buildRequestInfo(DataUsageRequest request, IUsageCallback callback,
- int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+ int callingPid, int callingUid, @NonNull String callingPackage,
+ @NetworkStatsAccess.Level int accessLevel) {
if (accessLevel <= NetworkStatsAccess.Level.USER) {
- return new UserUsageRequestInfo(this, request, callback, callingUid,
- accessLevel);
+ return new UserUsageRequestInfo(this, request, callback, callingPid,
+ callingUid, callingPackage, accessLevel);
} else {
// Safety check in case a new access level is added and we forgot to update this
if (accessLevel < NetworkStatsAccess.Level.DEVICESUMMARY) {
throw new IllegalArgumentException(
"accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
}
- return new NetworkUsageRequestInfo(this, request, callback, callingUid,
- accessLevel);
+ return new NetworkUsageRequestInfo(this, request, callback, callingPid,
+ callingUid, callingPackage, accessLevel);
}
}
@@ -237,18 +255,22 @@
private final NetworkStatsObservers mStatsObserver;
protected final DataUsageRequest mRequest;
private final IUsageCallback mCallback;
+ protected final int mCallingPid;
protected final int mCallingUid;
+ protected final String mCallingPackage;
protected final @NetworkStatsAccess.Level int mAccessLevel;
protected NetworkStatsRecorder mRecorder;
protected NetworkStatsCollection mCollection;
RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
- IUsageCallback callback, int callingUid,
- @NetworkStatsAccess.Level int accessLevel) {
+ IUsageCallback callback, int callingPid, int callingUid,
+ @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
mStatsObserver = statsObserver;
mRequest = request;
mCallback = callback;
+ mCallingPid = callingPid;
mCallingUid = callingUid;
+ mCallingPackage = callingPackage;
mAccessLevel = accessLevel;
try {
@@ -269,7 +291,8 @@
@Override
public String toString() {
- return "RequestInfo from uid:" + mCallingUid
+ return "RequestInfo from pid/uid:" + mCallingPid + "/" + mCallingUid
+ + "(" + mCallingPackage + ")"
+ " for " + mRequest + " accessLevel:" + mAccessLevel;
}
@@ -338,9 +361,10 @@
private static class NetworkUsageRequestInfo extends RequestInfo {
NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
- IUsageCallback callback, int callingUid,
- @NetworkStatsAccess.Level int accessLevel) {
- super(statsObserver, request, callback, callingUid, accessLevel);
+ IUsageCallback callback, int callingPid, int callingUid,
+ @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
+ super(statsObserver, request, callback, callingPid, callingUid, callingPackage,
+ accessLevel);
}
@Override
@@ -380,9 +404,10 @@
private static class UserUsageRequestInfo extends RequestInfo {
UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
- IUsageCallback callback, int callingUid,
- @NetworkStatsAccess.Level int accessLevel) {
- super(statsObserver, request, callback, callingUid, accessLevel);
+ IUsageCallback callback, int callingPid, int callingUid,
+ @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
+ super(statsObserver, request, callback, callingPid, callingUid,
+ callingPackage, accessLevel);
}
@Override
@@ -448,4 +473,10 @@
mCurrentTime = currentTime;
}
}
+
+ public void dump(IndentingPrintWriter pw) {
+ for (int i = 0; i < Math.min(mDataUsageRequests.size(), DUMP_USAGE_REQUESTS_COUNT); i++) {
+ pw.println(mDataUsageRequests.valueAt(i));
+ }
+ }
}
diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
index f62765d..6f070d7 100644
--- a/service-t/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
@@ -455,6 +455,73 @@
}
}
+ /**
+ * Rewriter that will remove any histories or persisted data points before the
+ * specified cutoff time, only writing data back when modified.
+ */
+ public static class RemoveDataBeforeRewriter implements FileRotator.Rewriter {
+ private final NetworkStatsCollection mTemp;
+ private final long mCutoffMills;
+
+ public RemoveDataBeforeRewriter(long bucketDuration, long cutoffMills) {
+ mTemp = new NetworkStatsCollection(bucketDuration);
+ mCutoffMills = cutoffMills;
+ }
+
+ @Override
+ public void reset() {
+ mTemp.reset();
+ }
+
+ @Override
+ public void read(InputStream in) throws IOException {
+ mTemp.read(in);
+ mTemp.clearDirty();
+ mTemp.removeHistoryBefore(mCutoffMills);
+ }
+
+ @Override
+ public boolean shouldWrite() {
+ return mTemp.isDirty();
+ }
+
+ @Override
+ public void write(OutputStream out) throws IOException {
+ mTemp.write(out);
+ }
+ }
+
+ /**
+ * Remove persisted data which contains or is before the cutoff timestamp.
+ */
+ public void removeDataBefore(long cutoffMillis) throws IOException {
+ if (mRotator != null) {
+ try {
+ mRotator.rewriteAll(new RemoveDataBeforeRewriter(
+ mBucketDuration, cutoffMillis));
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem importing netstats", e);
+ recoverFromWtf();
+ } catch (OutOfMemoryError e) {
+ Log.wtf(TAG, "problem importing netstats", e);
+ recoverFromWtf();
+ }
+ }
+
+ // Clean up any pending stats
+ if (mPending != null) {
+ mPending.removeHistoryBefore(cutoffMillis);
+ }
+ if (mSinceBoot != null) {
+ mSinceBoot.removeHistoryBefore(cutoffMillis);
+ }
+
+ final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+ if (complete != null) {
+ complete.removeHistoryBefore(cutoffMillis);
+ }
+ }
+
public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
if (mPending != null) {
pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 82b1fb5..a015177 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -159,8 +159,11 @@
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
@@ -219,15 +222,15 @@
"netstats_combine_subtype_enabled";
private static final String UID_COUNTERSET_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_uid_counterset_map";
+ "/sys/fs/bpf/netd_shared/map_netd_uid_counterset_map";
private static final String COOKIE_TAG_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_cookie_tag_map";
+ "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map";
private static final String APP_UID_STATS_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_app_uid_stats_map";
+ "/sys/fs/bpf/netd_shared/map_netd_app_uid_stats_map";
private static final String STATS_MAP_A_PATH =
- "/sys/fs/bpf/net_shared/map_netd_stats_map_A";
+ "/sys/fs/bpf/netd_shared/map_netd_stats_map_A";
private static final String STATS_MAP_B_PATH =
- "/sys/fs/bpf/net_shared/map_netd_stats_map_B";
+ "/sys/fs/bpf/netd_shared/map_netd_stats_map_B";
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
@@ -374,9 +377,19 @@
private long mLastStatsSessionPoll;
- /** Map from UID to number of opened sessions */
- @GuardedBy("mOpenSessionCallsPerUid")
+ private final Object mOpenSessionCallsLock = new Object();
+ /**
+ * Map from UID to number of opened sessions. This is used for rate-limt an app to open
+ * session frequently
+ */
+ @GuardedBy("mOpenSessionCallsLock")
private final SparseIntArray mOpenSessionCallsPerUid = new SparseIntArray();
+ /**
+ * Map from key {@code OpenSessionKey} to count of opened sessions. This is for recording
+ * the caller of open session and it is only for debugging.
+ */
+ @GuardedBy("mOpenSessionCallsLock")
+ private final HashMap<OpenSessionKey, Integer> mOpenSessionCallsPerCaller = new HashMap<>();
private final static int DUMP_STATS_SESSION_COUNT = 20;
@@ -407,6 +420,44 @@
Clock.systemUTC());
}
+ /**
+ * This class is a key that used in {@code mOpenSessionCallsPerCaller} to identify the count of
+ * the caller.
+ */
+ private static class OpenSessionKey {
+ public final int uid;
+ public final String packageName;
+
+ OpenSessionKey(int uid, @NonNull String packageName) {
+ this.uid = uid;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ sb.append("uid=").append(uid).append(",");
+ sb.append("package=").append(packageName);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(@NonNull Object o) {
+ if (this == o) return true;
+ if (o.getClass() != getClass()) return false;
+
+ final OpenSessionKey key = (OpenSessionKey) o;
+ return this.uid == key.uid && TextUtils.equals(this.packageName, key.packageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uid, packageName);
+ }
+ }
+
private final class NetworkStatsHandler extends Handler {
NetworkStatsHandler(@NonNull Looper looper) {
super(looper);
@@ -794,16 +845,27 @@
return openSessionInternal(flags, callingPackage);
}
- private boolean isRateLimitedForPoll(int callingUid) {
- if (callingUid == android.os.Process.SYSTEM_UID) {
- return false;
- }
-
+ private boolean isRateLimitedForPoll(@NonNull OpenSessionKey key) {
final long lastCallTime;
final long now = SystemClock.elapsedRealtime();
- synchronized (mOpenSessionCallsPerUid) {
- int calls = mOpenSessionCallsPerUid.get(callingUid, 0);
- mOpenSessionCallsPerUid.put(callingUid, calls + 1);
+
+ synchronized (mOpenSessionCallsLock) {
+ Integer callsPerCaller = mOpenSessionCallsPerCaller.get(key);
+ if (callsPerCaller == null) {
+ mOpenSessionCallsPerCaller.put((key), 1);
+ } else {
+ mOpenSessionCallsPerCaller.put(key, Integer.sum(callsPerCaller, 1));
+ }
+
+ int callsPerUid = mOpenSessionCallsPerUid.get(key.uid, 0);
+ mOpenSessionCallsPerUid.put(key.uid, callsPerUid + 1);
+
+ if (key.uid == android.os.Process.SYSTEM_UID) {
+ return false;
+ }
+
+ // To avoid a non-system user to be rate-limited after system users open sessions,
+ // so update mLastStatsSessionPoll after checked if the uid is SYSTEM_UID.
lastCallTime = mLastStatsSessionPoll;
mLastStatsSessionPoll = now;
}
@@ -811,7 +873,7 @@
return now - lastCallTime < POLL_RATE_LIMIT_MS;
}
- private int restrictFlagsForCaller(int flags) {
+ private int restrictFlagsForCaller(int flags, @NonNull String callingPackage) {
// All non-privileged callers are not allowed to turn off POLL_ON_OPEN.
final boolean isPrivileged = PermissionUtils.checkAnyPermissionOf(mContext,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
@@ -821,14 +883,15 @@
}
// Non-system uids are rate limited for POLL_ON_OPEN.
final int callingUid = Binder.getCallingUid();
- flags = isRateLimitedForPoll(callingUid)
+ final OpenSessionKey key = new OpenSessionKey(callingUid, callingPackage);
+ flags = isRateLimitedForPoll(key)
? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN)
: flags;
return flags;
}
private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) {
- final int restrictedFlags = restrictFlagsForCaller(flags);
+ final int restrictedFlags = restrictFlagsForCaller(flags, callingPackage);
if ((restrictedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN
| NetworkStatsManager.FLAG_POLL_FORCE)) != 0) {
final long ident = Binder.clearCallingIdentity();
@@ -1279,13 +1342,14 @@
Objects.requireNonNull(request.template, "NetworkTemplate is null");
Objects.requireNonNull(callback, "callback is null");
- int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
DataUsageRequest normalizedRequest;
final long token = Binder.clearCallingIdentity();
try {
normalizedRequest = mStatsObservers.register(mContext,
- request, callback, callingUid, accessLevel);
+ request, callback, callingPid, callingUid, callingPackage, accessLevel);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1937,6 +2001,9 @@
for (int uid : uids) {
deleteKernelTagData(uid);
}
+
+ // TODO: Remove the UID's entries from mOpenSessionCallsPerUid and
+ // mOpenSessionCallsPerCaller
}
/**
@@ -2060,25 +2127,21 @@
pw.decreaseIndent();
// Get the top openSession callers
- final SparseIntArray calls;
- synchronized (mOpenSessionCallsPerUid) {
- calls = mOpenSessionCallsPerUid.clone();
+ final HashMap calls;
+ synchronized (mOpenSessionCallsLock) {
+ calls = new HashMap<>(mOpenSessionCallsPerCaller);
}
-
- final int N = calls.size();
- final long[] values = new long[N];
- for (int j = 0; j < N; j++) {
- values[j] = ((long) calls.valueAt(j) << 32) | calls.keyAt(j);
- }
- Arrays.sort(values);
-
- pw.println("Top openSession callers (uid=count):");
+ final List<Map.Entry<OpenSessionKey, Integer>> list = new ArrayList<>(calls.entrySet());
+ Collections.sort(list,
+ (left, right) -> Integer.compare(left.getValue(), right.getValue()));
+ final int num = list.size();
+ final int end = Math.max(0, num - DUMP_STATS_SESSION_COUNT);
+ pw.println("Top openSession callers:");
pw.increaseIndent();
- final int end = Math.max(0, N - DUMP_STATS_SESSION_COUNT);
- for (int j = N - 1; j >= end; j--) {
- final int uid = (int) (values[j] & 0xffffffff);
- final int count = (int) (values[j] >> 32);
- pw.print(uid); pw.print("="); pw.println(count);
+ for (int j = num - 1; j >= end; j--) {
+ final Map.Entry<OpenSessionKey, Integer> entry = list.get(j);
+ pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
+
}
pw.decreaseIndent();
pw.println();
@@ -2098,6 +2161,13 @@
}
});
pw.decreaseIndent();
+ pw.println();
+
+ pw.println("Stats Observers:");
+ pw.increaseIndent();
+ mStatsObservers.dump(pw);
+ pw.decreaseIndent();
+ pw.println();
pw.println("Dev stats:");
pw.increaseIndent();
diff --git a/service/Android.bp b/service/Android.bp
index 25b970a..91b9d1c 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -169,7 +169,7 @@
"networkstack-client",
"PlatformProperties",
"service-connectivity-protos",
- "NetworkStackApiCurrentShims",
+ "NetworkStackApiStableShims",
],
apex_available: [
"com.android.tethering",
@@ -216,6 +216,11 @@
apex_available: [
"com.android.tethering",
],
+ optimize: {
+ enabled: true,
+ shrink: true,
+ proguard_flags_files: ["proguard.flags"],
+ },
lint: { strict_updatability_linting: true },
}
diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp
index f491cc7..02b2875 100644
--- a/service/ServiceConnectivityResources/Android.bp
+++ b/service/ServiceConnectivityResources/Android.bp
@@ -23,6 +23,7 @@
name: "ServiceConnectivityResources",
sdk_version: "module_30",
min_sdk_version: "30",
+ target_sdk_version: "33",
resource_dirs: [
"res",
],
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index 4b21569..c7223fc 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -111,5 +111,13 @@
# From mdns-aidl-interface
rule android.net.mdns.aidl.** android.net.connectivity.@0
+# From nearby-service, including proto
+rule service.proto.** com.android.server.nearby.@0
+rule androidx.annotation.Keep* com.android.server.nearby.@0
+rule androidx.collection.** com.android.server.nearby.@0
+rule androidx.core.** com.android.server.nearby.@0
+rule androidx.versionedparcelable.** com.android.server.nearby.@0
+rule com.google.common.** com.android.server.nearby.@0
+
# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index f13c68d..bc70c93 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -39,148 +39,126 @@
namespace android {
-static void native_init(JNIEnv* env, jobject clazz) {
+#define CHECK_LOG(status) \
+ do { \
+ if (!isOk(status)) \
+ ALOGE("%s failed, error code = %d", __func__, status.code()); \
+ } while (0)
+
+static void native_init(JNIEnv* env, jclass clazz) {
Status status = mTc.start();
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
}
-static jint native_addNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_addNaughtyApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_removeNaughtyApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_addNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_addNiceApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_removeNiceApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
- if (!isOk(status)) {
- ALOGD("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_setChildChain(JNIEnv* env, jobject clazz, jint childChain, jboolean enable) {
+static jint native_setChildChain(JNIEnv* env, jobject self, jint childChain, jboolean enable) {
auto chain = static_cast<ChildChain>(childChain);
int res = mTc.toggleUidOwnerMap(chain, enable);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_replaceUidChain(JNIEnv* env, jobject clazz, jstring name, jboolean isAllowlist,
- jintArray jUids) {
+static jint native_replaceUidChain(JNIEnv* env, jobject self, jstring name, jboolean isAllowlist,
+ jintArray jUids) {
const ScopedUtfChars chainNameUtf8(env, name);
- if (chainNameUtf8.c_str() == nullptr) {
- return -EINVAL;
- }
+ if (chainNameUtf8.c_str() == nullptr) return -EINVAL;
const std::string chainName(chainNameUtf8.c_str());
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
int res = mTc.replaceUidOwnerMap(chainName, isAllowlist, data);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_setUidRule(JNIEnv* env, jobject clazz, jint childChain, jint uid,
- jint firewallRule) {
+static jint native_setUidRule(JNIEnv* env, jobject self, jint childChain, jint uid,
+ jint firewallRule) {
auto chain = static_cast<ChildChain>(childChain);
auto rule = static_cast<FirewallRule>(firewallRule);
FirewallType fType = mTc.getFirewallType(chain);
int res = mTc.changeUidOwnerRule(chain, uid, rule, fType);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_addUidInterfaceRules(JNIEnv* env, jobject clazz, jstring ifName,
- jintArray jUids) {
- const ScopedUtfChars ifNameUtf8(env, ifName);
- if (ifNameUtf8.c_str() == nullptr) {
- return -EINVAL;
+static jint native_addUidInterfaceRules(JNIEnv* env, jobject self, jstring ifName,
+ jintArray jUids) {
+ // Null ifName is a wildcard to allow apps to receive packets on all interfaces and ifIndex is
+ // set to 0.
+ int ifIndex = 0;
+ if (ifName != nullptr) {
+ const ScopedUtfChars ifNameUtf8(env, ifName);
+ const std::string interfaceName(ifNameUtf8.c_str());
+ ifIndex = if_nametoindex(interfaceName.c_str());
}
- const std::string interfaceName(ifNameUtf8.c_str());
- const int ifIndex = if_nametoindex(interfaceName.c_str());
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
Status status = mTc.addUidInterfaceRules(ifIndex, data);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeUidInterfaceRules(JNIEnv* env, jobject clazz, jintArray jUids) {
+static jint native_removeUidInterfaceRules(JNIEnv* env, jobject self, jintArray jUids) {
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
Status status = mTc.removeUidInterfaceRules(data);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_swapActiveStatsMap(JNIEnv* env, jobject clazz) {
+static jint native_swapActiveStatsMap(JNIEnv* env, jobject self) {
Status status = mTc.swapActiveStatsMap();
- if (!isOk(status)) {
- ALOGD("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static void native_setPermissionForUids(JNIEnv* env, jobject clazz, jint permission,
- jintArray jUids) {
+static void native_setPermissionForUids(JNIEnv* env, jobject self, jint permission,
+ jintArray jUids) {
ScopedIntArrayRO uids(env, jUids);
if (uids.get() == nullptr) return;
@@ -190,7 +168,7 @@
mTc.setPermissionForUids(permission, data);
}
-static void native_dump(JNIEnv* env, jobject clazz, jobject javaFd, jboolean verbose) {
+static void native_dump(JNIEnv* env, jobject self, jobject javaFd, jboolean verbose) {
int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
if (fd < 0) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
@@ -235,9 +213,8 @@
// clang-format on
int register_com_android_server_BpfNetMaps(JNIEnv* env) {
- return jniRegisterNativeMethods(env,
- "com/android/server/BpfNetMaps",
- gMethods, NELEM(gMethods));
+ return jniRegisterNativeMethods(env, "com/android/server/BpfNetMaps",
+ gMethods, NELEM(gMethods));
}
}; // namespace android
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 3e98edb..a9ede6a 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -88,7 +88,7 @@
} \
} while (0)
-const std::string uidMatchTypeToString(uint8_t match) {
+const std::string uidMatchTypeToString(uint32_t match) {
std::string matchType;
FLAG_MSG_TRANS(matchType, HAPPY_BOX_MATCH, match);
FLAG_MSG_TRANS(matchType, PENALTY_BOX_MATCH, match);
@@ -98,6 +98,7 @@
FLAG_MSG_TRANS(matchType, RESTRICTED_MATCH, match);
FLAG_MSG_TRANS(matchType, LOW_POWER_STANDBY_MATCH, match);
FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
+ FLAG_MSG_TRANS(matchType, LOCKDOWN_VPN_MATCH, match);
if (match) {
return StringPrintf("Unknown match: %u", match);
}
@@ -183,6 +184,7 @@
RETURN_IF_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH));
RETURN_IF_NOT_OK(mUidOwnerMap.clear());
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
@@ -272,7 +274,7 @@
if (oldMatch.ok()) {
UidOwnerValue newMatch = {
.iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif,
- .rule = static_cast<uint8_t>(oldMatch.value().rule & ~match),
+ .rule = oldMatch.value().rule & ~match,
};
if (newMatch.rule == 0) {
RETURN_IF_NOT_OK(mUidOwnerMap.deleteValue(uid));
@@ -286,23 +288,20 @@
}
Status TrafficController::addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif) {
- // iif should be non-zero if and only if match == MATCH_IIF
- if (match == IIF_MATCH && iif == 0) {
- return statusFromErrno(EINVAL, "Interface match must have nonzero interface index");
- } else if (match != IIF_MATCH && iif != 0) {
+ if (match != IIF_MATCH && iif != 0) {
return statusFromErrno(EINVAL, "Non-interface match must have zero interface index");
}
auto oldMatch = mUidOwnerMap.readValue(uid);
if (oldMatch.ok()) {
UidOwnerValue newMatch = {
- .iif = iif ? iif : oldMatch.value().iif,
- .rule = static_cast<uint8_t>(oldMatch.value().rule | match),
+ .iif = (match == IIF_MATCH) ? iif : oldMatch.value().iif,
+ .rule = oldMatch.value().rule | match,
};
RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
} else {
UidOwnerValue newMatch = {
.iif = iif,
- .rule = static_cast<uint8_t>(match),
+ .rule = match,
};
RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
}
@@ -335,6 +334,8 @@
return ALLOWLIST;
case LOW_POWER_STANDBY:
return ALLOWLIST;
+ case LOCKDOWN:
+ return DENYLIST;
case NONE:
default:
return DENYLIST;
@@ -360,6 +361,9 @@
case LOW_POWER_STANDBY:
res = updateOwnerMapEntry(LOW_POWER_STANDBY_MATCH, uid, rule, type);
break;
+ case LOCKDOWN:
+ res = updateOwnerMapEntry(LOCKDOWN_VPN_MATCH, uid, rule, type);
+ break;
case NONE:
default:
ALOGW("Unknown child chain: %d", chain);
@@ -399,9 +403,6 @@
Status TrafficController::addUidInterfaceRules(const int iif,
const std::vector<int32_t>& uidsToAdd) {
- if (!iif) {
- return statusFromErrno(EINVAL, "Interface rule must specify interface");
- }
std::lock_guard guard(mMutex);
for (auto uid : uidsToAdd) {
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index 9529cae..3f13532 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -307,6 +307,7 @@
checkUidOwnerRuleForChain(POWERSAVE, POWERSAVE_MATCH);
checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
checkUidOwnerRuleForChain(LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
+ checkUidOwnerRuleForChain(LOCKDOWN, LOCKDOWN_VPN_MATCH);
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST));
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST));
}
@@ -491,6 +492,70 @@
checkEachUidValue({10001, 10002}, IIF_MATCH);
}
+TEST_F(TrafficControllerTest, TestAddUidInterfaceFilteringRulesWithWildcard) {
+ // iif=0 is a wildcard
+ int iif = 0;
+ // Add interface rule with wildcard to uids
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000, 1001})));
+ expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif);
+}
+
+TEST_F(TrafficControllerTest, TestRemoveUidInterfaceFilteringRulesWithWildcard) {
+ // iif=0 is a wildcard
+ int iif = 0;
+ // Add interface rule with wildcard to two uids
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000, 1001})));
+ expectUidOwnerMapValues({1000, 1001}, IIF_MATCH, iif);
+
+ // Remove interface rule from one of the uids
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000})));
+ expectUidOwnerMapValues({1001}, IIF_MATCH, iif);
+ checkEachUidValue({1001}, IIF_MATCH);
+
+ // Remove interface rule from the remaining uid
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1001})));
+ expectMapEmpty(mFakeUidOwnerMap);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesWithWildcardAndExistingMatches) {
+ // Set up existing DOZABLE_MATCH and POWERSAVE_MATCH rule
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, DOZABLE_MATCH,
+ TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, POWERSAVE_MATCH,
+ TrafficController::IptOpInsert)));
+
+ // iif=0 is a wildcard
+ int iif = 0;
+ // Add interface rule with wildcard to the existing uid
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000})));
+ expectUidOwnerMapValues({1000}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif);
+
+ // Remove interface rule with wildcard from the existing uid
+ ASSERT_TRUE(isOk(mTc.removeUidInterfaceRules({1000})));
+ expectUidOwnerMapValues({1000}, POWERSAVE_MATCH | DOZABLE_MATCH, 0);
+}
+
+TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesWithWildcardAndNewMatches) {
+ // iif=0 is a wildcard
+ int iif = 0;
+ // Set up existing interface rule with wildcard
+ ASSERT_TRUE(isOk(mTc.addUidInterfaceRules(iif, {1000})));
+
+ // Add DOZABLE_MATCH and POWERSAVE_MATCH rule to the existing uid
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, DOZABLE_MATCH,
+ TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, POWERSAVE_MATCH,
+ TrafficController::IptOpInsert)));
+ expectUidOwnerMapValues({1000}, POWERSAVE_MATCH | DOZABLE_MATCH | IIF_MATCH, iif);
+
+ // Remove DOZABLE_MATCH and POWERSAVE_MATCH rule from the existing uid
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, DOZABLE_MATCH,
+ TrafficController::IptOpDelete)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000}, POWERSAVE_MATCH,
+ TrafficController::IptOpDelete)));
+ expectUidOwnerMapValues({1000}, IIF_MATCH, iif);
+}
+
TEST_F(TrafficControllerTest, TestGrantInternetPermission) {
std::vector<uid_t> appUids = {TEST_UID, TEST_UID2, TEST_UID3};
@@ -620,7 +685,7 @@
if (res.ok() || (res.error().code() == ENOENT)) {
return Result<void>();
}
- ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+ ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s", key,
strerror(res.error().code()));
}
// Move forward to next cookie in the map.
diff --git a/service/native/include/Common.h b/service/native/include/Common.h
index dc44845..847acec 100644
--- a/service/native/include/Common.h
+++ b/service/native/include/Common.h
@@ -35,6 +35,7 @@
POWERSAVE = 3,
RESTRICTED = 4,
LOW_POWER_STANDBY = 5,
+ LOCKDOWN = 6,
INVALID_CHAIN
};
// LINT.ThenChange(packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java)
diff --git a/service/proguard.flags b/service/proguard.flags
new file mode 100644
index 0000000..94397ab
--- /dev/null
+++ b/service/proguard.flags
@@ -0,0 +1,18 @@
+# Make sure proguard keeps all connectivity classes
+# TODO: instead of keeping everything, consider listing only "entry points"
+# (service loader, JNI registered methods, etc) and letting the optimizer do its job
+-keep class android.net.** { *; }
+-keep class com.android.connectivity.** { *; }
+-keep class com.android.net.** { *; }
+-keep class !com.android.server.nearby.**,com.android.server.** { *; }
+
+# Prevent proguard from stripping out any nearby-service and fast-pair-lite-protos fields.
+-keep class com.android.server.nearby.NearbyService { *; }
+-keep class com.android.server.nearby.service.proto { *; }
+
+# The lite proto runtime uses reflection to access fields based on the names in
+# the schema, keep all the fields.
+# This replicates the base proguard rule used by the build by default
+# (proguard_basic_keeps.flags), but needs to be specified here because the
+# com.google.protobuf package is jarjared to the below package.
+-keepclassmembers class * extends com.android.connectivity.com.google.protobuf.MessageLite { <fields>; }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 4183ac6..f760d3b 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -610,13 +610,6 @@
// Handle private DNS validation status updates.
private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
- /**
- * used to remove a network request, either a listener or a real request and call unavailable
- * arg1 = UID of caller
- * obj = NetworkRequest
- */
- private static final int EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE = 39;
-
/**
* Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
* been tested.
@@ -755,7 +748,7 @@
* The BPF program attached to the tc-police hook to account for to-be-dropped traffic.
*/
private static final String TC_POLICE_BPF_PROG_PATH =
- "/sys/fs/bpf/net_shared/prog_netd_schedact_ingress_account";
+ "/sys/fs/bpf/netd_shared/prog_netd_schedact_ingress_account";
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
@@ -1194,6 +1187,7 @@
/**
* Keeps track of the number of requests made under different uids.
*/
+ // TODO: Remove the hack and use com.android.net.module.util.PerUidCounter instead.
public static class PerUidCounter {
private final int mMaxCountPerUid;
@@ -2628,7 +2622,7 @@
verifyCallingUidAndPackage(callingPackageName, mDeps.getCallingUid());
enforceChangePermission(callingPackageName, callingAttributionTag);
if (mProtectedNetworks.contains(networkType)) {
- enforceConnectivityRestrictedNetworksPermission();
+ enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
}
InetAddress addr;
@@ -2982,18 +2976,35 @@
android.Manifest.permission.NETWORK_SETTINGS);
}
- private void enforceConnectivityRestrictedNetworksPermission() {
- try {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS,
- "ConnectivityService");
- return;
- } catch (SecurityException e) { /* fallback to ConnectivityInternalPermission */ }
- // TODO: Remove this fallback check after all apps have declared
- // CONNECTIVITY_USE_RESTRICTED_NETWORKS.
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CONNECTIVITY_INTERNAL,
- "ConnectivityService");
+ private boolean checkConnectivityRestrictedNetworksPermission(int callingUid,
+ boolean checkUidsAllowedList) {
+ if (PermissionUtils.checkAnyPermissionOf(mContext,
+ android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)) {
+ return true;
+ }
+
+ // fallback to ConnectivityInternalPermission
+ // TODO: Remove this fallback check after all apps have declared
+ // CONNECTIVITY_USE_RESTRICTED_NETWORKS.
+ if (PermissionUtils.checkAnyPermissionOf(mContext,
+ android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
+ return true;
+ }
+
+ // Check whether uid is in allowed on restricted networks list.
+ if (checkUidsAllowedList
+ && mPermissionMonitor.isUidAllowedOnRestrictedNetworks(callingUid)) {
+ return true;
+ }
+ return false;
+ }
+
+ private void enforceConnectivityRestrictedNetworksPermission(boolean checkUidsAllowedList) {
+ final int callingUid = mDeps.getCallingUid();
+ if (!checkConnectivityRestrictedNetworksPermission(callingUid, checkUidsAllowedList)) {
+ throw new SecurityException("ConnectivityService: user " + callingUid
+ + " has no permission to access restricted network.");
+ }
}
private void enforceKeepalivePermission() {
@@ -3417,6 +3428,10 @@
for (NetworkAgentInfo nai : networksSortedById()) {
pw.println(nai.toString());
pw.increaseIndent();
+ pw.println("Nat464Xlat:");
+ pw.increaseIndent();
+ nai.dumpNat464Xlat(pw);
+ pw.decreaseIndent();
pw.println(String.format(
"Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d",
nai.numForegroundNetworkRequests(),
@@ -3432,10 +3447,6 @@
pw.increaseIndent();
nai.dumpInactivityTimers(pw);
pw.decreaseIndent();
- pw.println("Nat464Xlat:");
- pw.increaseIndent();
- nai.dumpNat464Xlat(pw);
- pw.decreaseIndent();
pw.decreaseIndent();
}
}
@@ -4495,7 +4506,7 @@
private boolean hasCarrierPrivilegeForNetworkCaps(final int callingUid,
@NonNull final NetworkCapabilities caps) {
- if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
+ if (mCarrierPrivilegeAuthenticator != null) {
return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
callingUid, caps);
}
@@ -4525,7 +4536,6 @@
private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
ensureRunningOnConnectivityServiceThread();
- NetworkRequest requestToBeReleased = null;
for (final NetworkRequestInfo nri : nris) {
mNetworkRequestInfoLogs.log("REGISTER " + nri);
checkNrisConsistency(nri);
@@ -4540,13 +4550,6 @@
}
}
}
- if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
- if (!hasCarrierPrivilegeForNetworkCaps(nri.mUid, req.networkCapabilities)
- && !checkConnectivityRestrictedNetworksPermission(
- nri.mPid, nri.mUid)) {
- requestToBeReleased = req;
- }
- }
}
// If this NRI has a satisfier already, it is replacing an older request that
@@ -4558,11 +4561,6 @@
}
}
- if (requestToBeReleased != null) {
- releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased);
- return;
- }
-
if (mFlags.noRematchAllRequestsOnRegister()) {
rematchNetworksAndRequests(nris);
} else {
@@ -5402,11 +5400,6 @@
/* callOnUnavailable */ false);
break;
}
- case EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE: {
- handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
- /* callOnUnavailable */ true);
- break;
- }
case EVENT_SET_ACCEPT_UNVALIDATED: {
Network network = (Network) msg.obj;
handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
@@ -5976,6 +5969,10 @@
+ Arrays.toString(ranges) + "): netd command failed: " + e);
}
+ if (SdkLevel.isAtLeastT()) {
+ mPermissionMonitor.updateVpnLockdownUidRanges(requireVpn, ranges);
+ }
+
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
final boolean curMetered = nai.networkCapabilities.isMetered();
maybeNotifyNetworkBlocked(nai, curMetered, curMetered,
@@ -6625,7 +6622,7 @@
case REQUEST:
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
- callingAttributionTag);
+ callingAttributionTag, callingUid);
// TODO: this is incorrect. We mark the request as metered or not depending on
// the state of the app when the request is filed, but we never change the
// request if the app changes network state. http://b/29964605
@@ -6715,26 +6712,19 @@
}
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
- String callingPackageName, String callingAttributionTag) {
+ String callingPackageName, String callingAttributionTag, final int callingUid) {
if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
- if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
- enforceConnectivityRestrictedNetworksPermission();
+ // For T+ devices, callers with carrier privilege could request with CBS capabilities.
+ if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+ && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities)) {
+ return;
}
+ enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
} else {
enforceChangePermission(callingPackageName, callingAttributionTag);
}
}
- private boolean checkConnectivityRestrictedNetworksPermission(int callerPid, int callerUid) {
- if (checkAnyPermissionOf(callerPid, callerUid,
- android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
- || checkAnyPermissionOf(callerPid, callerUid,
- android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
- return true;
- }
- return false;
- }
-
@Override
public boolean requestBandwidthUpdate(Network network) {
enforceAccessPermission();
@@ -6793,7 +6783,7 @@
final int callingUid = mDeps.getCallingUid();
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
- callingAttributionTag);
+ callingAttributionTag, callingUid);
enforceMeteredApnPolicy(networkCapabilities);
ensureRequestableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
@@ -6916,13 +6906,6 @@
EVENT_RELEASE_NETWORK_REQUEST, mDeps.getCallingUid(), 0, networkRequest));
}
- private void releaseNetworkRequestAndCallOnUnavailable(NetworkRequest networkRequest) {
- ensureNetworkRequestHasType(networkRequest);
- mHandler.sendMessage(mHandler.obtainMessage(
- EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE, mDeps.getCallingUid(), 0,
- networkRequest));
- }
-
private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
if (mNetworkProviderInfos.containsKey(npi.messenger)) {
// Avoid creating duplicates. even if an app makes a direct AIDL call.
@@ -7753,10 +7736,10 @@
private void updateVpnFiltering(LinkProperties newLp, LinkProperties oldLp,
NetworkAgentInfo nai) {
- final String oldIface = oldLp != null ? oldLp.getInterfaceName() : null;
- final String newIface = newLp != null ? newLp.getInterfaceName() : null;
- final boolean wasFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, oldLp);
- final boolean needsFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, newLp);
+ final String oldIface = getVpnIsolationInterface(nai, nai.networkCapabilities, oldLp);
+ final String newIface = getVpnIsolationInterface(nai, nai.networkCapabilities, newLp);
+ final boolean wasFiltering = requiresVpnAllowRule(nai, oldLp, oldIface);
+ final boolean needsFiltering = requiresVpnAllowRule(nai, newLp, newIface);
if (!wasFiltering && !needsFiltering) {
// Nothing to do.
@@ -7769,11 +7752,19 @@
}
final Set<UidRange> ranges = nai.networkCapabilities.getUidRanges();
+ if (ranges == null || ranges.isEmpty()) {
+ return;
+ }
+
final int vpnAppUid = nai.networkCapabilities.getOwnerUid();
// TODO: this create a window of opportunity for apps to receive traffic between the time
// when the old rules are removed and the time when new rules are added. To fix this,
// make eBPF support two allowlisted interfaces so here new rules can be added before the
// old rules are being removed.
+
+ // Null iface given to onVpnUidRangesAdded/Removed is a wildcard to allow apps to receive
+ // packets on all interfaces. This is required to accept incoming traffic in Lockdown mode
+ // by overriding the Lockdown blocking rule.
if (wasFiltering) {
mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid);
}
@@ -7834,6 +7825,7 @@
}
nai.declaredCapabilities = new NetworkCapabilities(nc);
NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE),
mCarrierPrivilegeAuthenticator);
}
@@ -8062,15 +8054,14 @@
}
/**
- * Returns whether VPN isolation (ingress interface filtering) should be applied on the given
- * network.
+ * Returns the interface which requires VPN isolation (ingress interface filtering).
*
* Ingress interface filtering enforces that all apps under the given network can only receive
* packets from the network's interface (and loopback). This is important for VPNs because
* apps that cannot bypass a fully-routed VPN shouldn't be able to receive packets from any
* non-VPN interfaces.
*
- * As a result, this method should return true iff
+ * As a result, this method should return Non-null interface iff
* 1. the network is an app VPN (not legacy VPN)
* 2. the VPN does not allow bypass
* 3. the VPN is fully-routed
@@ -8079,15 +8070,32 @@
* @see INetd#firewallAddUidInterfaceRules
* @see INetd#firewallRemoveUidInterfaceRules
*/
- private boolean requiresVpnIsolation(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc,
+ @Nullable
+ private String getVpnIsolationInterface(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc,
LinkProperties lp) {
- if (nc == null || lp == null) return false;
- return nai.isVPN()
+ if (nc == null || lp == null) return null;
+ if (nai.isVPN()
&& !nai.networkAgentConfig.allowBypass
&& nc.getOwnerUid() != Process.SYSTEM_UID
&& lp.getInterfaceName() != null
&& (lp.hasIpv4DefaultRoute() || lp.hasIpv4UnreachableDefaultRoute())
- && (lp.hasIpv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute());
+ && (lp.hasIpv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute())
+ && !lp.hasExcludeRoute()) {
+ return lp.getInterfaceName();
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether we need to set interface filtering rule or not
+ */
+ private boolean requiresVpnAllowRule(NetworkAgentInfo nai, LinkProperties lp,
+ String filterIface) {
+ // Only filter if lp has an interface.
+ if (lp == null || lp.getInterfaceName() == null) return false;
+ // Before T, allow rules are only needed if VPN isolation is enabled.
+ // T and After T, allow rules are needed for all VPNs.
+ return filterIface != null || (nai.isVPN() && SdkLevel.isAtLeastT());
}
private static UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) {
@@ -8215,9 +8223,10 @@
if (!prevRanges.isEmpty()) {
updateVpnUidRanges(false, nai, prevRanges);
}
- final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties);
- final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties);
- final String iface = nai.linkProperties.getInterfaceName();
+ final String oldIface = getVpnIsolationInterface(nai, prevNc, nai.linkProperties);
+ final String newIface = getVpnIsolationInterface(nai, newNc, nai.linkProperties);
+ final boolean wasFiltering = requiresVpnAllowRule(nai, nai.linkProperties, oldIface);
+ final boolean shouldFilter = requiresVpnAllowRule(nai, nai.linkProperties, newIface);
// For VPN uid interface filtering, old ranges need to be removed before new ranges can
// be added, due to the range being expanded and stored as individual UIDs. For example
// the UIDs might be updated from [0, 99999] to ([0, 10012], [10014, 99999]) which means
@@ -8229,11 +8238,16 @@
// above, where the addition of new ranges happens before the removal of old ranges.
// TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range
// to be removed will never overlap with the new range to be added.
+
+ // Null iface given to onVpnUidRangesAdded/Removed is a wildcard to allow apps to
+ // receive packets on all interfaces. This is required to accept incoming traffic in
+ // Lockdown mode by overriding the Lockdown blocking rule.
if (wasFiltering && !prevRanges.isEmpty()) {
- mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges, prevNc.getOwnerUid());
+ mPermissionMonitor.onVpnUidRangesRemoved(oldIface, prevRanges,
+ prevNc.getOwnerUid());
}
if (shouldFilter && !newRanges.isEmpty()) {
- mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges, newNc.getOwnerUid());
+ mPermissionMonitor.onVpnUidRangesAdded(newIface, newRanges, newNc.getOwnerUid());
}
} catch (Exception e) {
// Never crash!
@@ -10624,7 +10638,11 @@
if (callback == null) throw new IllegalArgumentException("callback must be non-null");
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
- enforceConnectivityRestrictedNetworksPermission();
+ // TODO: Check allowed list here and ensure that either a) any QoS callback registered
+ // on this network is unregistered when the app loses permission or b) no QoS
+ // callbacks are sent for restricted networks unless the app currently has permission
+ // to access restricted networks.
+ enforceConnectivityRestrictedNetworksPermission(false /* checkUidsAllowedList */);
}
mQosCallbackTracker.registerCallback(callback, filter, nai);
}
@@ -10676,7 +10694,10 @@
Objects.requireNonNull(profile);
if (preferences.size() == 0) {
- preferences.add((new ProfileNetworkPreference.Builder()).build());
+ final ProfileNetworkPreference pref = new ProfileNetworkPreference.Builder()
+ .setPreference(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ .build();
+ preferences.add(pref);
}
PermissionUtils.enforceNetworkStackPermission(mContext);
@@ -10694,12 +10715,14 @@
final List<ProfileNetworkPreferenceList.Preference> preferenceList =
new ArrayList<ProfileNetworkPreferenceList.Preference>();
- boolean allowFallback = true;
+ boolean hasDefaultPreference = false;
for (final ProfileNetworkPreference preference : preferences) {
final NetworkCapabilities nc;
+ boolean allowFallback = true;
switch (preference.getPreference()) {
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
nc = null;
+ hasDefaultPreference = true;
if (preference.getPreferenceEnterpriseId() != 0) {
throw new IllegalArgumentException(
"Invalid enterprise identifier in setProfileNetworkPreferences");
@@ -10709,6 +10732,14 @@
allowFallback = false;
// continue to process the enterprise preference.
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
+ // This code is needed even though there is a check later on,
+ // because isRangeAlreadyInPreferenceList assumes that every preference
+ // has a UID list.
+ if (hasDefaultPreference) {
+ throw new IllegalArgumentException(
+ "Default profile preference should not be set along with other "
+ + "preference");
+ }
if (!isEnterpriseIdentifierValid(preference.getPreferenceEnterpriseId())) {
throw new IllegalArgumentException(
"Invalid enterprise identifier in setProfileNetworkPreferences");
@@ -10732,6 +10763,10 @@
}
preferenceList.add(new ProfileNetworkPreferenceList.Preference(
profile, nc, allowFallback));
+ if (hasDefaultPreference && preferenceList.size() > 1) {
+ throw new IllegalArgumentException(
+ "Default profile preference should not be set along with other preference");
+ }
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE,
new Pair<>(preferenceList, listener)));
@@ -10776,12 +10811,6 @@
return false;
}
- private void validateNetworkCapabilitiesOfProfileNetworkPreference(
- @Nullable final NetworkCapabilities nc) {
- if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
- ensureRequestableCapabilities(nc);
- }
-
private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
@NonNull final ProfileNetworkPreferenceList prefs) {
final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
@@ -10832,10 +10861,19 @@
private void handleSetProfileNetworkPreference(
@NonNull final List<ProfileNetworkPreferenceList.Preference> preferenceList,
@Nullable final IOnCompleteListener listener) {
+ /*
+ * handleSetProfileNetworkPreference is always called for single user.
+ * preferenceList only contains preferences for different uids within the same user
+ * (enforced by getUidListToBeAppliedForNetworkPreference).
+ * Clear all the existing preferences for the user before applying new preferences.
+ *
+ */
+ mProfileNetworkPreferences = mProfileNetworkPreferences.withoutUser(
+ preferenceList.get(0).user);
for (final ProfileNetworkPreferenceList.Preference preference : preferenceList) {
- validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
}
+
removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_PROFILE);
addPerAppDefaultNetworkRequests(
createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences));
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index e8fc06d..738caab 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -540,7 +540,7 @@
mClatCoordinator.dump(pw);
pw.decreaseIndent();
} else {
- pw.println("<not start>");
+ pw.println("<not started>");
}
}
}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 323888a..b40b6e0 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -19,6 +19,7 @@
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.transportNamesOf;
@@ -1224,20 +1225,22 @@
*
* @param nc the capabilities to sanitize
* @param creatorUid the UID of the process creating this network agent
+ * @param hasAutomotiveFeature true if this device has the automotive feature, false otherwise
* @param authenticator the carrier privilege authenticator to check for telephony constraints
*/
public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
- final int creatorUid, @NonNull final CarrierPrivilegeAuthenticator authenticator) {
+ final int creatorUid, final boolean hasAutomotiveFeature,
+ @Nullable final CarrierPrivilegeAuthenticator authenticator) {
if (nc.hasTransport(TRANSPORT_TEST)) {
nc.restrictCapabilitiesForTestNetwork(creatorUid);
}
- if (!areAllowedUidsAcceptableFromNetworkAgent(nc, authenticator)) {
+ if (!areAllowedUidsAcceptableFromNetworkAgent(nc, hasAutomotiveFeature, authenticator)) {
nc.setAllowedUids(new ArraySet<>());
}
}
private static boolean areAllowedUidsAcceptableFromNetworkAgent(
- @NonNull final NetworkCapabilities nc,
+ @NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
@Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
// NCs without access UIDs are fine.
if (!nc.hasAllowedUids()) return true;
@@ -1252,6 +1255,11 @@
// access UIDs
if (nc.hasTransport(TRANSPORT_TEST)) return true;
+ // Factories that make ethernet networks can allow UIDs for automotive devices.
+ if (nc.hasSingleTransport(TRANSPORT_ETHERNET) && hasAutomotiveFeature) {
+ return true;
+ }
+
// Factories that make cell networks can allow the UID for the carrier service package.
// This can only work in T where there is support for CarrierPrivilegeAuthenticator
if (null != carrierPrivilegeAuthenticator
@@ -1262,8 +1270,6 @@
return true;
}
- // TODO : accept Railway callers
-
return false;
}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index c02d9cf..e4a2c20 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -23,6 +23,9 @@
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetd.PERMISSION_NETWORK;
@@ -37,6 +40,7 @@
import static com.android.net.module.util.CollectionUtils.toIntArray;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -74,7 +78,6 @@
import com.android.server.BpfNetMaps;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -108,10 +111,19 @@
@GuardedBy("this")
private final SparseIntArray mUidToNetworkPerm = new SparseIntArray();
- // Keys are active non-bypassable and fully-routed VPN's interface name, Values are uid ranges
- // for apps under the VPN
+ // NonNull keys are active non-bypassable and fully-routed VPN's interface name, Values are uid
+ // ranges for apps under the VPNs which enable interface filtering.
+ // If key is null, Values are uid ranges for apps under the VPNs which are connected but do not
+ // enable interface filtering.
@GuardedBy("this")
- private final Map<String, Set<UidRange>> mVpnUidRanges = new HashMap<>();
+ private final Map<String, Set<UidRange>> mVpnInterfaceUidRanges = new ArrayMap<>();
+
+ // Items are uid ranges for apps under the VPN Lockdown
+ // Ranges were given through ConnectivityManager#setRequireVpnForUids, and ranges are allowed to
+ // have duplicates. Also, it is allowed to give ranges that are already subject to lockdown.
+ // So we need to maintain uid range with multiset.
+ @GuardedBy("this")
+ private final MultiSet<UidRange> mVpnLockdownUidRanges = new MultiSet<>();
// A set of appIds for apps across all users on the device. We track appIds instead of uids
// directly to reduce its size and also eliminate the need to update this set when user is
@@ -201,6 +213,38 @@
}
}
+ private static class MultiSet<T> {
+ private final Map<T, Integer> mMap = new ArrayMap<>();
+
+ /**
+ * Returns the number of key in the set before this addition.
+ */
+ public int add(T key) {
+ final int oldCount = mMap.getOrDefault(key, 0);
+ mMap.put(key, oldCount + 1);
+ return oldCount;
+ }
+
+ /**
+ * Return the number of key in the set before this removal.
+ */
+ public int remove(T key) {
+ final int oldCount = mMap.getOrDefault(key, 0);
+ if (oldCount == 0) {
+ Log.wtf(TAG, "Attempt to remove non existing key = " + key.toString());
+ } else if (oldCount == 1) {
+ mMap.remove(key);
+ } else {
+ mMap.put(key, oldCount - 1);
+ }
+ return oldCount;
+ }
+
+ public Set<T> getSet() {
+ return mMap.keySet();
+ }
+ }
+
public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd,
@NonNull final BpfNetMaps bpfNetMaps) {
this(context, netd, bpfNetMaps, new Dependencies());
@@ -421,7 +465,14 @@
if (appInfo == null) return false;
// Check whether package's uid is in allowed on restricted networks uid list. If so, this
// uid can have netd system permission.
- return mUidsAllowedOnRestrictedNetworks.contains(appInfo.uid);
+ return isUidAllowedOnRestrictedNetworks(appInfo.uid);
+ }
+
+ /**
+ * Returns whether the given uid is in allowed on restricted networks list.
+ */
+ public synchronized boolean isUidAllowedOnRestrictedNetworks(final int uid) {
+ return mUidsAllowedOnRestrictedNetworks.contains(uid);
}
@VisibleForTesting
@@ -619,16 +670,26 @@
}
private synchronized void updateVpnUid(int uid, boolean add) {
- for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ // Apps that can use restricted networks can always bypass VPNs.
+ if (hasRestrictedNetworksPermission(uid)) {
+ return;
+ }
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnInterfaceUidRanges.entrySet()) {
if (UidRange.containsUid(vpn.getValue(), uid)) {
final Set<Integer> changedUids = new HashSet<>();
changedUids.add(uid);
- removeBypassingUids(changedUids, -1 /* vpnAppUid */);
updateVpnUidsInterfaceRules(vpn.getKey(), changedUids, add);
}
}
}
+ private synchronized void updateLockdownUid(int uid, boolean add) {
+ if (UidRange.containsUid(mVpnLockdownUidRanges.getSet(), uid)
+ && !hasRestrictedNetworksPermission(uid)) {
+ updateLockdownUidRule(uid, add);
+ }
+ }
+
/**
* This handles both network and traffic permission, because there is no overlap in actual
* values, where network permission is NETWORK or SYSTEM, and traffic permission is INTERNET
@@ -722,9 +783,10 @@
// If the newly-installed package falls within some VPN's uid range, update Netd with it.
// This needs to happen after the mUidToNetworkPerm update above, since
- // removeBypassingUids() in updateVpnUid() depends on mUidToNetworkPerm to check if the
- // package can bypass VPN.
+ // hasRestrictedNetworksPermission() in updateVpnUid() and updateLockdownUid() depends on
+ // mUidToNetworkPerm to check if the package can bypass VPN.
updateVpnUid(uid, true /* add */);
+ updateLockdownUid(uid, true /* add */);
mAllApps.add(appId);
// Log package added.
@@ -768,9 +830,10 @@
// If the newly-removed package falls within some VPN's uid range, update Netd with it.
// This needs to happen before the mUidToNetworkPerm update below, since
- // removeBypassingUids() in updateVpnUid() depends on mUidToNetworkPerm to check if the
- // package can bypass VPN.
+ // hasRestrictedNetworksPermission() in updateVpnUid() and updateLockdownUid() depends on
+ // mUidToNetworkPerm to check if the package can bypass VPN.
updateVpnUid(uid, false /* add */);
+ updateLockdownUid(uid, false /* add */);
// If the package has been removed from all users on the device, clear it form mAllApps.
if (mPackageManager.getNameForUid(uid) == null) {
mAllApps.remove(appId);
@@ -852,48 +915,100 @@
/**
* Called when a new set of UID ranges are added to an active VPN network
*
- * @param iface The active VPN network's interface name
+ * @param iface The active VPN network's interface name. Null iface indicates that the app is
+ * allowed to receive packets on all interfaces.
* @param rangesToAdd The new UID ranges to be added to the network
* @param vpnAppUid The uid of the VPN app
*/
- public synchronized void onVpnUidRangesAdded(@NonNull String iface, Set<UidRange> rangesToAdd,
+ public synchronized void onVpnUidRangesAdded(@Nullable String iface, Set<UidRange> rangesToAdd,
int vpnAppUid) {
// Calculate the list of new app uids under the VPN due to the new UID ranges and update
// Netd about them. Because mAllApps only contains appIds instead of uids, the result might
// be an overestimation if an app is not installed on the user on which the VPN is running,
- // but that's safe.
+ // but that's safe: if an app is not installed, it cannot receive any packets, so dropping
+ // packets to that UID is fine.
final Set<Integer> changedUids = intersectUids(rangesToAdd, mAllApps);
removeBypassingUids(changedUids, vpnAppUid);
updateVpnUidsInterfaceRules(iface, changedUids, true /* add */);
- if (mVpnUidRanges.containsKey(iface)) {
- mVpnUidRanges.get(iface).addAll(rangesToAdd);
+ if (mVpnInterfaceUidRanges.containsKey(iface)) {
+ mVpnInterfaceUidRanges.get(iface).addAll(rangesToAdd);
} else {
- mVpnUidRanges.put(iface, new HashSet<UidRange>(rangesToAdd));
+ mVpnInterfaceUidRanges.put(iface, new HashSet<UidRange>(rangesToAdd));
}
}
/**
* Called when a set of UID ranges are removed from an active VPN network
*
- * @param iface The VPN network's interface name
+ * @param iface The VPN network's interface name. Null iface indicates that the app is allowed
+ * to receive packets on all interfaces.
* @param rangesToRemove Existing UID ranges to be removed from the VPN network
* @param vpnAppUid The uid of the VPN app
*/
- public synchronized void onVpnUidRangesRemoved(@NonNull String iface,
+ public synchronized void onVpnUidRangesRemoved(@Nullable String iface,
Set<UidRange> rangesToRemove, int vpnAppUid) {
// Calculate the list of app uids that are no longer under the VPN due to the removed UID
// ranges and update Netd about them.
final Set<Integer> changedUids = intersectUids(rangesToRemove, mAllApps);
removeBypassingUids(changedUids, vpnAppUid);
updateVpnUidsInterfaceRules(iface, changedUids, false /* add */);
- Set<UidRange> existingRanges = mVpnUidRanges.getOrDefault(iface, null);
+ Set<UidRange> existingRanges = mVpnInterfaceUidRanges.getOrDefault(iface, null);
if (existingRanges == null) {
loge("Attempt to remove unknown vpn uid Range iface = " + iface);
return;
}
existingRanges.removeAll(rangesToRemove);
if (existingRanges.size() == 0) {
- mVpnUidRanges.remove(iface);
+ mVpnInterfaceUidRanges.remove(iface);
+ }
+ }
+
+ /**
+ * Called when UID ranges under VPN Lockdown are updated
+ *
+ * @param add {@code true} if the uids are to be added to the Lockdown, {@code false} if they
+ * are to be removed from the Lockdown.
+ * @param ranges The updated UID ranges under VPN Lockdown. This function does not treat the VPN
+ * app's UID in any special way. The caller is responsible for excluding the VPN
+ * app UID from the passed-in ranges.
+ * Ranges can have duplications and/or contain the range that is already subject
+ * to lockdown. However, ranges can not have overlaps with other ranges including
+ * ranges that are currently subject to lockdown.
+ */
+ public synchronized void updateVpnLockdownUidRanges(boolean add, UidRange[] ranges) {
+ final Set<UidRange> affectedUidRanges = new HashSet<>();
+
+ for (final UidRange range : ranges) {
+ if (add) {
+ // Rule will be added if mVpnLockdownUidRanges does not have this uid range entry
+ // currently.
+ if (mVpnLockdownUidRanges.add(range) == 0) {
+ affectedUidRanges.add(range);
+ }
+ } else {
+ // Rule will be removed if the number of the range in the set is 1 before the
+ // removal.
+ if (mVpnLockdownUidRanges.remove(range) == 1) {
+ affectedUidRanges.add(range);
+ }
+ }
+ }
+
+ // mAllApps only contains appIds instead of uids. So the generated uid list might contain
+ // apps that are installed only on some users but not others. But that's safe: if an app is
+ // not installed, it cannot receive any packets, so dropping packets to that UID is fine.
+ final Set<Integer> affectedUids = intersectUids(affectedUidRanges, mAllApps);
+
+ // We skip adding rule to privileged apps and allow them to bypass incoming packet
+ // filtering. The behaviour is consistent with how lockdown works for outgoing packets, but
+ // the implementation is different: while ConnectivityService#setRequireVpnForUids does not
+ // exclude privileged apps from the prohibit routing rules used to implement outgoing packet
+ // filtering, privileged apps can still bypass outgoing packet filtering because the
+ // prohibit rules observe the protected from VPN bit.
+ for (final int uid: affectedUids) {
+ if (!hasRestrictedNetworksPermission(uid)) {
+ updateLockdownUidRule(uid, add);
+ }
}
}
@@ -932,7 +1047,7 @@
*/
private void removeBypassingUids(Set<Integer> uids, int vpnAppUid) {
uids.remove(vpnAppUid);
- uids.removeIf(uid -> mUidToNetworkPerm.get(uid, PERMISSION_NONE) == PERMISSION_SYSTEM);
+ uids.removeIf(this::hasRestrictedNetworksPermission);
}
/**
@@ -941,6 +1056,7 @@
*
* This is to instruct netd to set up appropriate filtering rules for these uids, such that they
* can only receive ingress packets from the VPN's tunnel interface (and loopback).
+ * Null iface set up a wildcard rule that allow app to receive packets on all interfaces.
*
* @param iface the interface name of the active VPN connection
* @param add {@code true} if the uids are to be added to the interface, {@code false} if they
@@ -961,6 +1077,18 @@
}
}
+ private void updateLockdownUidRule(int uid, boolean add) {
+ try {
+ if (add) {
+ mBpfNetMaps.setUidRule(FIREWALL_CHAIN_LOCKDOWN_VPN, uid, FIREWALL_RULE_DENY);
+ } else {
+ mBpfNetMaps.setUidRule(FIREWALL_CHAIN_LOCKDOWN_VPN, uid, FIREWALL_RULE_ALLOW);
+ }
+ } catch (ServiceSpecificException e) {
+ loge("Failed to " + (add ? "add" : "remove") + " Lockdown rule: " + e);
+ }
+ }
+
/**
* Send the updated permission information to netd. Called upon package install/uninstall.
*
@@ -1048,8 +1176,14 @@
/** Should only be used by unit tests */
@VisibleForTesting
- public Set<UidRange> getVpnUidRanges(String iface) {
- return mVpnUidRanges.get(iface);
+ public Set<UidRange> getVpnInterfaceUidRanges(String iface) {
+ return mVpnInterfaceUidRanges.get(iface);
+ }
+
+ /** Should only be used by unit tests */
+ @VisibleForTesting
+ public Set<UidRange> getVpnLockdownUidRanges() {
+ return mVpnLockdownUidRanges.getSet();
}
private synchronized void onSettingChanged() {
@@ -1114,7 +1248,7 @@
public void dump(IndentingPrintWriter pw) {
pw.println("Interface filtering rules:");
pw.increaseIndent();
- for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnInterfaceUidRanges.entrySet()) {
pw.println("Interface: " + vpn.getKey());
pw.println("UIDs: " + vpn.getValue().toString());
pw.println();
@@ -1122,6 +1256,14 @@
pw.decreaseIndent();
pw.println();
+ pw.println("Lockdown filtering rules:");
+ pw.increaseIndent();
+ for (final UidRange range : mVpnLockdownUidRanges.getSet()) {
+ pw.println("UIDs: " + range.toString());
+ }
+ pw.decreaseIndent();
+
+ pw.println();
pw.println("Update logs:");
pw.increaseIndent();
mPermissionUpdateLogs.reverseDump(pw);
diff --git a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
index 71f342d..5bafef9 100644
--- a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
+++ b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
@@ -70,23 +70,33 @@
/**
* Returns a new object consisting of this object plus the passed preference.
*
- * If a preference already exists for the same user, it will be replaced by the passed
- * preference. Passing a Preference object containing a null capabilities object is equivalent
- * to (and indeed, implemented as) removing the preference for this user.
+ * It is not expected that unwanted preference already exists for the same user.
+ * All preferences for the user that were previously configured should be cleared before
+ * adding a new preference.
+ * Passing a Preference object containing a null capabilities object is equivalent
+ * to removing the preference for this user.
*/
public ProfileNetworkPreferenceList plus(@NonNull final Preference pref) {
- final ArrayList<Preference> newPrefs = new ArrayList<>();
- for (final Preference existingPref : preferences) {
- if (!existingPref.user.equals(pref.user)) {
- newPrefs.add(existingPref);
- }
- }
+ final ArrayList<Preference> newPrefs = new ArrayList<>(preferences);
if (null != pref.capabilities) {
newPrefs.add(pref);
}
return new ProfileNetworkPreferenceList(newPrefs);
}
+ /**
+ * Remove all preferences corresponding to a user.
+ */
+ public ProfileNetworkPreferenceList withoutUser(UserHandle user) {
+ final ArrayList<Preference> newPrefs = new ArrayList<>();
+ for (final Preference existingPref : preferences) {
+ if (!existingPref.user.equals(user)) {
+ newPrefs.add(existingPref);
+ }
+ }
+ return new ProfileNetworkPreferenceList(newPrefs);
+ }
+
public boolean isEmpty() {
return preferences.isEmpty();
}
diff --git a/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java b/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
index 534dbe7..e682026 100644
--- a/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
+++ b/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
@@ -30,6 +30,8 @@
import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.Objects;
/**
@@ -149,6 +151,7 @@
void sendEventEpsQosSessionAvailable(final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
+ if (!validateOrSendErrorAndUnregister()) return;
try {
if (DBG) log("sendEventEpsQosSessionAvailable: sending...");
mCallback.onQosEpsBearerSessionAvailable(session, attributes);
@@ -159,6 +162,7 @@
void sendEventNrQosSessionAvailable(final QosSession session,
final NrQosSessionAttributes attributes) {
+ if (!validateOrSendErrorAndUnregister()) return;
try {
if (DBG) log("sendEventNrQosSessionAvailable: sending...");
mCallback.onNrQosSessionAvailable(session, attributes);
@@ -168,6 +172,7 @@
}
void sendEventQosSessionLost(@NonNull final QosSession session) {
+ if (!validateOrSendErrorAndUnregister()) return;
try {
if (DBG) log("sendEventQosSessionLost: sending...");
mCallback.onQosSessionLost(session);
@@ -185,6 +190,21 @@
}
}
+ private boolean validateOrSendErrorAndUnregister() {
+ final int exceptionType = mFilter.validate();
+ if (exceptionType != EX_TYPE_FILTER_NONE) {
+ log("validation fail before sending QosCallback.");
+ // Error callback is returned from Android T to prevent any disruption of application
+ // running on Android S.
+ if (SdkLevel.isAtLeastT()) {
+ sendEventQosCallbackError(exceptionType);
+ mQosCallbackTracker.unregisterCallback(mCallback);
+ }
+ return false;
+ }
+ return true;
+ }
+
private static void log(@NonNull final String msg) {
Log.d(TAG, msg);
}
diff --git a/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java b/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java
new file mode 100644
index 0000000..84b6e54
--- /dev/null
+++ b/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+public class EthernetNetworkManagementExceptionTest {
+ private static final String ERROR_MESSAGE = "Test error message";
+
+ @Test
+ public void testEthernetNetworkManagementExceptionParcelable() {
+ final EthernetNetworkManagementException e =
+ new EthernetNetworkManagementException(ERROR_MESSAGE);
+
+ assertParcelingIsLossless(e);
+ }
+
+ @Test
+ public void testEthernetNetworkManagementExceptionHasExpectedErrorMessage() {
+ final EthernetNetworkManagementException e =
+ new EthernetNetworkManagementException(ERROR_MESSAGE);
+
+ assertEquals(ERROR_MESSAGE, e.getMessage());
+ }
+}
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 8fc636a..b66a979 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -1261,6 +1261,18 @@
assertFalse(lp.hasIpv4UnreachableDefaultRoute());
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+ public void testHasExcludeRoute() {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 24), RTN_UNICAST));
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 0), RTN_UNICAST));
+ assertFalse(lp.hasExcludeRoute());
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 32), RTN_THROW));
+ assertTrue(lp.hasExcludeRoute());
+ }
+
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
@EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testRouteAddWithSameKey() throws Exception {
diff --git a/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt b/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt
index ca0e5ed..368a519 100644
--- a/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt
@@ -16,7 +16,7 @@
package android.net.netstats
-import android.net.NetworkIdentitySet
+import android.net.NetworkIdentity
import android.net.NetworkStatsCollection
import android.net.NetworkStatsHistory
import androidx.test.filters.SmallTest
@@ -40,7 +40,7 @@
@Test
fun testBuilder() {
- val ident = NetworkIdentitySet()
+ val ident = setOf<NetworkIdentity>()
val key1 = NetworkStatsCollection.Key(ident, /* uid */ 0, /* set */ 0, /* tag */ 0)
val key2 = NetworkStatsCollection.Key(ident, /* uid */ 1, /* set */ 0, /* tag */ 0)
val bucketDuration = 10L
@@ -63,4 +63,4 @@
val actualHistory = actualEntries[key1] ?: fail("There should be an entry for $key1")
assertEquals(history1.entries, actualHistory.entries)
}
-}
\ No newline at end of file
+}
diff --git a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
new file mode 100644
index 0000000..192694b
--- /dev/null
+++ b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 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.netstats
+
+import android.net.NetworkStats.DEFAULT_NETWORK_ALL
+import android.net.NetworkStats.METERED_ALL
+import android.net.NetworkStats.METERED_YES
+import android.net.NetworkStats.ROAMING_YES
+import android.net.NetworkStats.ROAMING_ALL
+import android.net.NetworkTemplate
+import android.net.NetworkTemplate.MATCH_BLUETOOTH
+import android.net.NetworkTemplate.MATCH_CARRIER
+import android.net.NetworkTemplate.MATCH_ETHERNET
+import android.net.NetworkTemplate.MATCH_MOBILE
+import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
+import android.net.NetworkTemplate.MATCH_PROXY
+import android.net.NetworkTemplate.MATCH_WIFI
+import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
+import android.net.NetworkTemplate.NETWORK_TYPE_ALL
+import android.net.NetworkTemplate.OEM_MANAGED_ALL
+import android.telephony.TelephonyManager
+import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.SC_V2
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+private const val TEST_IMSI1 = "imsi"
+private const val TEST_WIFI_KEY1 = "wifiKey1"
+private const val TEST_WIFI_KEY2 = "wifiKey2"
+
+@RunWith(JUnit4::class)
+@ConnectivityModuleTest
+class NetworkTemplateTest {
+ @Rule
+ @JvmField
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
+
+ @Test
+ fun testBuilderMatchRules() {
+ // Verify unknown match rules cannot construct templates.
+ listOf(Integer.MIN_VALUE, -1, Integer.MAX_VALUE).forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(it).build()
+ }
+ }
+
+ // Verify hidden match rules cannot construct templates.
+ listOf(MATCH_WIFI_WILDCARD, MATCH_MOBILE_WILDCARD, MATCH_PROXY).forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(it).build()
+ }
+ }
+
+ // Verify template which matches metered cellular and carrier networks with
+ // the given IMSI. See buildTemplateMobileAll and buildTemplateCarrierMetered.
+ listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
+ NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
+ .setMeteredness(METERED_YES).build().let {
+ val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+
+ // Verify template which matches roaming cellular and carrier networks with
+ // the given IMSI.
+ listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
+ NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
+ .setRoaming(ROAMING_YES).setMeteredness(METERED_YES).build().let {
+ val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+ ROAMING_YES, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+
+ // Verify carrier template cannot be created without IMSI.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(MATCH_CARRIER).build()
+ }
+
+ // Verify template which matches metered cellular networks,
+ // regardless of IMSI. See buildTemplateMobileWildcard.
+ NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_MOBILE_WILDCARD, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf<String>(),
+ METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches metered cellular networks and ratType.
+ // See NetworkTemplate#buildTemplateMobileWithRatType.
+ NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(TEST_IMSI1))
+ .setMeteredness(METERED_YES).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
+ .build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_UMTS,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches all wifi networks,
+ // regardless of Wifi Network Key. See buildTemplateWifiWildcard and buildTemplateWifi.
+ NetworkTemplate.Builder(MATCH_WIFI).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf<String>(),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches wifi networks with the given Wifi Network Key.
+ // See buildTemplateWifi(wifiNetworkKey).
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf(TEST_WIFI_KEY1),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches all wifi networks with the
+ // given Wifi Network Key, and IMSI. See buildTemplateWifi(wifiNetworkKey, subscriberId).
+ NetworkTemplate.Builder(MATCH_WIFI).setSubscriberIds(setOf(TEST_IMSI1))
+ .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI, TEST_IMSI1,
+ arrayOf(TEST_IMSI1), arrayOf(TEST_WIFI_KEY1),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches ethernet and bluetooth networks.
+ // See buildTemplateEthernet and buildTemplateBluetooth.
+ listOf(MATCH_ETHERNET, MATCH_BLUETOOTH).forEach { matchRule ->
+ NetworkTemplate.Builder(matchRule).build().let {
+ val expectedTemplate = NetworkTemplate(matchRule, null /*subscriberId*/,
+ null /*subscriberIds*/, arrayOf<String>(),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+ }
+
+ @Test
+ fun testBuilderWifiNetworkKeys() {
+ // Verify template builder which generates same template with the given different
+ // sequence keys.
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
+ setOf(TEST_WIFI_KEY1, TEST_WIFI_KEY2)).build().let {
+ val expectedTemplate = NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
+ setOf(TEST_WIFI_KEY2, TEST_WIFI_KEY1)).build()
+ assertEquals(expectedTemplate, it)
+ }
+
+ // Verify template which matches non-wifi networks with the given key is invalid.
+ listOf(MATCH_MOBILE, MATCH_CARRIER, MATCH_ETHERNET, MATCH_BLUETOOTH, -1,
+ Integer.MAX_VALUE).forEach { matchRule ->
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(matchRule).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
+ }
+ }
+
+ // Verify template which matches wifi networks with the given null key is invalid.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(null)).build()
+ }
+
+ // Verify template which matches wifi wildcard with the given empty key set.
+ NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf<String>()).build().let {
+ val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
+ arrayOf<String>() /*subscriberIds*/, arrayOf<String>(),
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+ assertEquals(expectedTemplate, it)
+ }
+ }
+}
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 47ea53e..ac84e57 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -26,6 +26,7 @@
"tradefed",
],
static_libs: [
+ "CompatChangeGatingTestBase",
"modules-utils-build-testing",
],
// Tag this module as a cts test artifact
@@ -37,6 +38,8 @@
data: [
":CtsHostsideNetworkTestsApp",
":CtsHostsideNetworkTestsApp2",
+ ":CtsHostsideNetworkTestsApp3",
+ ":CtsHostsideNetworkTestsApp3PreT",
":CtsHostsideNetworkTestsAppNext",
],
per_testcase_directory: true,
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 524bd65..93e9dcd 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -217,7 +217,10 @@
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);
+ // No sleep after the last turn
+ if (attempts <= maxAttempts) {
+ 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);
@@ -328,7 +331,10 @@
}
Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
+ "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on background state after "
+ maxTries + " attempts: " + state);
@@ -347,7 +353,10 @@
Log.d(TAG, "App not on foreground state on attempt #" + i
+ "; sleeping 1s before trying again");
turnScreenOn();
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on foreground state after "
+ maxTries + " attempts: " + state);
@@ -365,7 +374,10 @@
}
Log.d(TAG, "App not on foreground service state on attempt #" + i
+ "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on foreground service state after "
+ maxTries + " attempts: " + state);
@@ -506,7 +518,10 @@
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);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
+ }
}
fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
+ maxTries
@@ -578,7 +593,10 @@
}
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);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
+ ". Full list: " + uids);
@@ -738,7 +756,8 @@
protected void assertAppIdle(boolean enabled) throws Exception {
try {
- assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
+ assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG,
+ 30 /* maxTries */, 1 /* napTimeSeconds */, "Idle=" + enabled);
} catch (Throwable e) {
throw e;
}
@@ -765,7 +784,10 @@
return;
}
Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("app2 receiver is not ready in " + mUid);
}
@@ -814,8 +836,6 @@
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();
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index 01c8cd2..edfaf9f 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -23,6 +23,7 @@
defaults: ["cts_support_defaults"],
sdk_version: "test_current",
static_libs: [
+ "androidx.annotation_annotation",
"CtsHostsideNetworkTestsAidl",
"NetworkStackApiStableShims",
],
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
index 08cdea7..82f13ae 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -29,6 +29,9 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
+import android.view.WindowManager;
+
+import androidx.annotation.GuardedBy;
import com.android.cts.net.hostside.INetworkStateObserver;
@@ -37,18 +40,24 @@
*/
public class MyActivity extends Activity {
+ @GuardedBy("this")
private BroadcastReceiver finishCommandReceiver = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "MyActivity.onCreate()");
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
public void finish() {
- if (finishCommandReceiver != null) {
- unregisterReceiver(finishCommandReceiver);
+ synchronized (this) {
+ if (finishCommandReceiver != null) {
+ unregisterReceiver(finishCommandReceiver);
+ finishCommandReceiver = null;
+ }
}
super.finish();
}
@@ -71,14 +80,17 @@
super.onResume();
Log.d(TAG, "MyActivity.onResume(): " + getIntent());
Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
- 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));
+ synchronized (this) {
+ 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),
+ Context.RECEIVER_EXPORTED);
+ }
}
@Override
diff --git a/tests/cts/hostside/app3/Android.bp b/tests/cts/hostside/app3/Android.bp
new file mode 100644
index 0000000..141cf03
--- /dev/null
+++ b/tests/cts/hostside/app3/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+ name: "CtsHostsideNetworkTestsApp3Defaults",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "junit",
+ ],
+ static_libs: [
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ ],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkTestsApp3",
+ defaults: [
+ "cts_support_defaults",
+ "CtsHostsideNetworkTestsApp3Defaults",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkTestsApp3PreT",
+ target_sdk_version: "31",
+ defaults: [
+ "cts_support_defaults",
+ "CtsHostsideNetworkTestsApp3Defaults",
+ ],
+}
diff --git a/tests/cts/hostside/app3/AndroidManifest.xml b/tests/cts/hostside/app3/AndroidManifest.xml
new file mode 100644
index 0000000..eabcacb
--- /dev/null
+++ b/tests/cts/hostside/app3/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.app3">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.net.hostside.app3" />
+
+</manifest>
diff --git a/tests/cts/hostside/app3/src/com/android/cts/net/hostside/app3/ExcludedRoutesGatingTest.java b/tests/cts/hostside/app3/src/com/android/cts/net/hostside/app3/ExcludedRoutesGatingTest.java
new file mode 100644
index 0000000..a1a8209
--- /dev/null
+++ b/tests/cts/hostside/app3/src/com/android/cts/net/hostside/app3/ExcludedRoutesGatingTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.app3;
+
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.net.IpPrefix;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests to verify {@link LinkProperties#getRoutes} behavior, depending on
+ * {@LinkProperties#EXCLUDED_ROUTES} change state.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExcludedRoutesGatingTest {
+ @Before
+ public void setUp() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+ }
+
+ @After
+ public void tearDown() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testExcludedRoutesChangeEnabled() {
+ final LinkProperties lp = makeLinkPropertiesWithExcludedRoutes();
+
+ // Excluded routes change is enabled: non-RTN_UNICAST routes are visible.
+ assertEquals(2, lp.getRoutes().size());
+ assertEquals(2, lp.getAllRoutes().size());
+ }
+
+ @Test
+ public void testExcludedRoutesChangeDisabled() {
+ final LinkProperties lp = makeLinkPropertiesWithExcludedRoutes();
+
+ // Excluded routes change is disabled: non-RTN_UNICAST routes are filtered out.
+ assertEquals(0, lp.getRoutes().size());
+ assertEquals(0, lp.getAllRoutes().size());
+ }
+
+ private LinkProperties makeLinkPropertiesWithExcludedRoutes() {
+ final LinkProperties lp = new LinkProperties();
+
+ lp.addRoute(new RouteInfo(new IpPrefix("10.0.0.0/8"), null, null, RouteInfo.RTN_THROW));
+ lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64"), null, null,
+ RouteInfo.RTN_UNREACHABLE));
+
+ return lp;
+ }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java
new file mode 100644
index 0000000..b65fb6b
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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 android.compat.cts.CompatChangeGatingTestCase;
+
+import java.util.Set;
+
+/**
+ * Tests for the {@link android.net.LinkProperties#EXCLUDED_ROUTES} compatibility change.
+ */
+public class HostsideLinkPropertiesGatingTests extends CompatChangeGatingTestCase {
+ private static final String TEST_APK = "CtsHostsideNetworkTestsApp3.apk";
+ private static final String TEST_APK_PRE_T = "CtsHostsideNetworkTestsApp3PreT.apk";
+ private static final String TEST_PKG = "com.android.cts.net.hostside.app3";
+ private static final String TEST_CLASS = ".ExcludedRoutesGatingTest";
+
+ private static final long EXCLUDED_ROUTES_CHANGE_ID = 186082280;
+
+ protected void tearDown() throws Exception {
+ uninstallPackage(TEST_PKG, true);
+ }
+
+ public void testExcludedRoutesChangeEnabled() throws Exception {
+ installPackage(TEST_APK, true);
+ runDeviceCompatTest("testExcludedRoutesChangeEnabled");
+ }
+
+ public void testExcludedRoutesChangeDisabledPreT() throws Exception {
+ installPackage(TEST_APK_PRE_T, true);
+ runDeviceCompatTest("testExcludedRoutesChangeDisabled");
+ }
+
+ public void testExcludedRoutesChangeDisabledByOverride() throws Exception {
+ installPackage(TEST_APK, true);
+ runDeviceCompatTestWithChangeDisabled("testExcludedRoutesChangeDisabled");
+ }
+
+ public void testExcludedRoutesChangeEnabledByOverridePreT() throws Exception {
+ installPackage(TEST_APK_PRE_T, true);
+ runDeviceCompatTestWithChangeEnabled("testExcludedRoutesChangeEnabled");
+ }
+
+ private void runDeviceCompatTest(String methodName) throws Exception {
+ runDeviceCompatTest(TEST_PKG, TEST_CLASS, methodName, Set.of(), Set.of());
+ }
+
+ private void runDeviceCompatTestWithChangeEnabled(String methodName) throws Exception {
+ runDeviceCompatTest(TEST_PKG, TEST_CLASS, methodName, Set.of(EXCLUDED_ROUTES_CHANGE_ID),
+ Set.of());
+ }
+
+ private void runDeviceCompatTestWithChangeDisabled(String methodName) throws Exception {
+ runDeviceCompatTest(TEST_PKG, TEST_CLASS, methodName, Set.of(),
+ Set.of(EXCLUDED_ROUTES_CHANGE_ID));
+ }
+}
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index 3b47100..6b5bb93 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -44,7 +44,8 @@
android.permission.MANAGE_TEST_NETWORKS
-->
- <application android:usesCleartextTraffic="true">
+ <application android:debuggable="true"
+ android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
</application>
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 48a1c79..33f3af5 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -38,4 +38,20 @@
<option name="hidden-api-checks" value="false" />
<option name="isolated-storage" value="false" />
</test>
+ <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+ one of the Mainline modules below is present on the device used for testing. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <!-- Tethering Module (internal version). -->
+ <option name="mainline-module-package-name" value="com.google.android.tethering" />
+ <!-- Tethering Module (AOSP version). -->
+ <option name="mainline-module-package-name" value="com.android.tethering" />
+ <!-- NetworkStack Module (internal version). Should always be installed with CaptivePortalLogin. -->
+ <option name="mainline-module-package-name" value="com.google.android.networkstack" />
+ <!-- NetworkStack Module (AOSP version). Should always be installed with CaptivePortalLogin. -->
+ <option name="mainline-module-package-name" value="com.android.networkstack" />
+ <!-- Resolver Module (internal version). -->
+ <option name="mainline-module-package-name" value="com.google.android.resolv" />
+ <!-- Resolver Module (AOSP version). -->
+ <option name="mainline-module-package-name" value="com.android.resolv" />
+ </object>
</configuration>
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 5b37294..9b81a56 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -51,5 +51,8 @@
"cts",
"general-tests",
],
-
+ data: [
+ ":CtsNetTestAppForApi23",
+ ],
+ per_testcase_directory: true,
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index a129108..3b88189 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -52,6 +52,7 @@
import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
+import static android.net.ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -164,7 +165,6 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Pair;
import android.util.Range;
import androidx.test.InstrumentationRegistry;
@@ -182,6 +182,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRuleKt;
+import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.TestHttpServer;
@@ -1605,51 +1606,7 @@
private static boolean isTcpKeepaliveSupportedByKernel() {
final String kVersionString = VintfRuntimeInfo.getKernelVersion();
- return compareMajorMinorVersion(kVersionString, "4.8") >= 0;
- }
-
- private static Pair<Integer, Integer> getVersionFromString(String version) {
- // Only gets major and minor number of the version string.
- final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
- final Matcher m = versionPattern.matcher(version);
- if (m.matches()) {
- final int major = Integer.parseInt(m.group(1));
- final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
- return new Pair<>(major, minor);
- } else {
- return new Pair<>(0, 0);
- }
- }
-
- // TODO: Move to util class.
- private static int compareMajorMinorVersion(final String s1, final String s2) {
- final Pair<Integer, Integer> v1 = getVersionFromString(s1);
- final Pair<Integer, Integer> v2 = getVersionFromString(s2);
-
- if (v1.first == v2.first) {
- return Integer.compare(v1.second, v2.second);
- } else {
- return Integer.compare(v1.first, v2.first);
- }
- }
-
- /**
- * Verifies that version string compare logic returns expected result for various cases.
- * Note that only major and minor number are compared.
- */
- @Test
- public void testMajorMinorVersionCompare() {
- assertEquals(0, compareMajorMinorVersion("4.8.1", "4.8"));
- assertEquals(1, compareMajorMinorVersion("4.9", "4.8.1"));
- assertEquals(1, compareMajorMinorVersion("5.0", "4.8"));
- assertEquals(1, compareMajorMinorVersion("5", "4.8"));
- assertEquals(0, compareMajorMinorVersion("5", "5.0"));
- assertEquals(1, compareMajorMinorVersion("5-beta1", "4.8"));
- assertEquals(0, compareMajorMinorVersion("4.8.0.0", "4.8"));
- assertEquals(0, compareMajorMinorVersion("4.8-RC1", "4.8"));
- assertEquals(0, compareMajorMinorVersion("4.8", "4.8"));
- assertEquals(-1, compareMajorMinorVersion("3.10", "4.8.0"));
- assertEquals(-1, compareMajorMinorVersion("4.7.10.10", "4.8"));
+ return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.8") >= 0;
}
/**
@@ -3212,7 +3169,7 @@
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@Test
public void testUidsAllowedOnRestrictedNetworks() throws Exception {
- assumeTrue(TestUtils.shouldTestSApis());
+ assumeTestSApis();
// TODO (b/175199465): figure out a reasonable permission check for
// setUidsAllowedOnRestrictedNetworks that allows tests but not system-external callers.
@@ -3225,10 +3182,10 @@
// because it has been just installed to device. In case the uid is existed in setting
// mistakenly, try to remove the uid and set correct uids to setting.
originalUidsAllowedOnRestrictedNetworks.remove(uid);
- runWithShellPermissionIdentity(() ->
- ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
- mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+ runWithShellPermissionIdentity(() -> setUidsAllowedOnRestrictedNetworks(
+ mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+ // File a restricted network request with permission first to hold the connection.
final TestableNetworkCallback testNetworkCb = new TestableNetworkCallback();
final NetworkRequest testRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
@@ -3240,6 +3197,19 @@
runWithShellPermissionIdentity(() -> requestNetwork(testRequest, testNetworkCb),
CONNECTIVITY_USE_RESTRICTED_NETWORKS);
+ // File another restricted network request without permission.
+ final TestableNetworkCallback restrictedNetworkCb = new TestableNetworkCallback();
+ final NetworkRequest restrictedRequest = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(
+ TEST_RESTRICTED_NW_IFACE_NAME))
+ .build();
+ // Uid is not in allowed list and no permissions. Expect that SecurityException will throw.
+ assertThrows(SecurityException.class,
+ () -> mCm.requestNetwork(restrictedRequest, restrictedNetworkCb));
+
final NetworkAgent agent = createRestrictedNetworkAgent(mContext);
final Network network = agent.getNetwork();
@@ -3259,19 +3229,28 @@
final Set<Integer> newUidsAllowedOnRestrictedNetworks =
new ArraySet<>(originalUidsAllowedOnRestrictedNetworks);
newUidsAllowedOnRestrictedNetworks.add(uid);
- runWithShellPermissionIdentity(() ->
- ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
- mContext, newUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+ runWithShellPermissionIdentity(() -> setUidsAllowedOnRestrictedNetworks(
+ mContext, newUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
// Wait a while for sending allowed uids on the restricted network to netd.
- // TODD: Have a significant signal to know the uids has been send to netd.
+ // TODD: Have a significant signal to know the uids has been sent to netd.
assertBindSocketToNetworkSuccess(network);
+
+ if (TestUtils.shouldTestTApis()) {
+ // Uid is in allowed list. Try file network request again.
+ requestNetwork(restrictedRequest, restrictedNetworkCb);
+ // Verify that the network is restricted.
+ restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+ NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> network.equals(entry.getNetwork())
+ && (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+ }
} finally {
agent.unregister();
// Restore setting.
- runWithShellPermissionIdentity(() ->
- ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
- mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+ runWithShellPermissionIdentity(() -> setUidsAllowedOnRestrictedNetworks(
+ mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
}
}
@@ -3295,6 +3274,12 @@
assertTrue(dumpOutput, dumpOutput.contains("BPF map content"));
}
+ private void assumeTestSApis() {
+ // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
+ // shims, and @IgnoreUpTo does not check that.
+ assumeTrue(TestUtils.shouldTestSApis());
+ }
+
private void unregisterRegisteredCallbacks() {
for (NetworkCallback callback: mRegisteredCallbacks) {
mCm.unregisterNetworkCallback(callback);
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 1e42fe6..bbac09b 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -626,15 +626,31 @@
@Test
fun testParcelingDscpPolicyIsLossless(): Unit = createConnectedNetworkAgent().let {
(agent, callback) ->
+ val policyId = 1
+ val dscpValue = 1
+ val range = Range(4444, 4444)
+ val srcPort = 555
+
// Check that policy with partial parameters is lossless.
- val policy = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(4444, 4444)).build()
+ val policy = DscpPolicy.Builder(policyId, dscpValue).setDestinationPortRange(range).build()
+ assertEquals(policyId, policy.policyId)
+ assertEquals(dscpValue, policy.dscpValue)
+ assertEquals(range, policy.destinationPortRange)
assertParcelingIsLossless(policy)
// Check that policy with all parameters is lossless.
- val policy2 = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(4444, 4444))
+ val policy2 = DscpPolicy.Builder(policyId, dscpValue).setDestinationPortRange(range)
.setSourceAddress(LOCAL_IPV4_ADDRESS)
.setDestinationAddress(TEST_TARGET_IPV4_ADDR)
+ .setSourcePort(srcPort)
.setProtocol(IPPROTO_UDP).build()
+ assertEquals(policyId, policy2.policyId)
+ assertEquals(dscpValue, policy2.dscpValue)
+ assertEquals(range, policy2.destinationPortRange)
+ assertEquals(TEST_TARGET_IPV4_ADDR, policy2.destinationAddress)
+ assertEquals(LOCAL_IPV4_ADDRESS, policy2.sourceAddress)
+ assertEquals(srcPort, policy2.sourcePort)
+ assertEquals(IPPROTO_UDP, policy2.protocol)
assertParcelingIsLossless(policy2)
}
}
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 04434e5..0a02593 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -15,54 +15,71 @@
*/
package android.net.cts
+import android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.EthernetNetworkSpecifier
import android.net.InetAddresses
import android.net.IpConfiguration
import android.net.MacAddress
+import android.net.Network
+import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkRequest
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
-import android.platform.test.annotations.AppModeFull
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnit4
-import com.android.net.module.util.ArrayTrackRecord
-import com.android.net.module.util.TrackRecord
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.SC_V2
-import com.android.testutils.runAsShell
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import android.content.Context
-import org.junit.runner.RunWith
-import kotlin.test.assertNull
-import kotlin.test.fail
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
import android.os.Handler
import android.os.HandlerExecutor
import android.os.Looper
+import android.platform.test.annotations.AppModeFull
+import android.util.ArraySet
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.net.module.util.TrackRecord
+import com.android.networkstack.apishim.EthernetManagerShimImpl
import com.android.networkstack.apishim.common.EthernetManagerShim.InterfaceStateListener
+import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
+import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_ABSENT
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_DOWN
import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_UP
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
-import com.android.networkstack.apishim.EthernetManagerShimImpl
+import com.android.testutils.anyNetwork
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.RouterAdvertisementResponder
+import com.android.testutils.SC_V2
import com.android.testutils.TapPacketReader
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
import com.android.testutils.waitForIdle
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
import java.net.Inet6Address
-import java.util.concurrent.Executor
-import kotlin.test.assertFalse
import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
import kotlin.test.assertTrue
-import java.net.NetworkInterface
+import kotlin.test.fail
private const val TIMEOUT_MS = 1000L
private const val NO_CALLBACK_TIMEOUT_MS = 200L
private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
IpConfiguration.ProxySettings.NONE, null, null)
+private val ETH_REQUEST: NetworkRequest = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_ETHERNET)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .build()
@AppModeFull(reason = "Instant apps can't access EthernetManager")
@RunWith(AndroidJUnit4::class)
@@ -73,9 +90,12 @@
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
+ private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
+ private val ifaceListener = EthernetStateListener()
private val createdIfaces = ArrayList<EthernetTestInterface>()
private val addedListeners = ArrayList<EthernetStateListener>()
+ private val networkRequests = ArrayList<TestableNetworkCallback>()
private class EthernetTestInterface(
context: Context,
@@ -91,7 +111,7 @@
val tnm = context.getSystemService(TestNetworkManager::class.java)
tnm.createTapInterface(false /* bringUp */)
}
- val mtu = NetworkInterface.getByName(tapInterface.interfaceName).getMTU()
+ val mtu = 1500
packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
raResponder = RouterAdvertisementResponder(packetReader)
raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"),
@@ -141,14 +161,23 @@
}
fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
- expectCallback(InterfaceStateChanged(iface.interfaceName, state, role,
- if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null))
+ expectCallback(createChangeEvent(iface, state, role))
}
+ fun createChangeEvent(iface: EthernetTestInterface, state: Int, role: Int) =
+ InterfaceStateChanged(iface.interfaceName, state, role,
+ if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null)
+
fun pollForNextCallback(): CallbackEntry {
return events.poll(TIMEOUT_MS) ?: fail("Did not receive callback after ${TIMEOUT_MS}ms")
}
+ fun eventuallyExpect(expected: CallbackEntry) = events.poll(TIMEOUT_MS) { it == expected }
+
+ fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
+ assertNotNull(eventuallyExpect(createChangeEvent(iface, state, role)))
+ }
+
fun assertNoCallback() {
val cb = events.poll(NO_CALLBACK_TIMEOUT_MS)
assertNull(cb, "Expected no callback but got $cb")
@@ -158,6 +187,7 @@
@Before
fun setUp() {
setIncludeTestInterfaces(true)
+ addInterfaceStateListener(ifaceListener)
}
@After
@@ -165,22 +195,32 @@
setIncludeTestInterfaces(false)
for (iface in createdIfaces) {
iface.destroy()
+ ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
}
for (listener in addedListeners) {
em.removeInterfaceStateListener(listener)
}
+ networkRequests.forEach { cm.unregisterNetworkCallback(it) }
}
- private fun addInterfaceStateListener(executor: Executor, listener: EthernetStateListener) {
- em.addInterfaceStateListener(executor, listener)
+ private fun addInterfaceStateListener(listener: EthernetStateListener) {
+ runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
+ em.addInterfaceStateListener(HandlerExecutor(Handler(Looper.getMainLooper())), listener)
+ }
addedListeners.add(listener)
}
private fun createInterface(): EthernetTestInterface {
- return EthernetTestInterface(
+ val iface = EthernetTestInterface(
context,
Handler(Looper.getMainLooper())
).also { createdIfaces.add(it) }
+ with(ifaceListener) {
+ // when an interface comes up, we should always see a down cb before an up cb.
+ eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+ }
+ return iface
}
private fun setIncludeTestInterfaces(value: Boolean) {
@@ -192,49 +232,97 @@
private fun removeInterface(iface: EthernetTestInterface) {
iface.destroy()
createdIfaces.remove(iface)
+ ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
}
- @Test
- public fun testCallbacks() {
- val executor = HandlerExecutor(Handler(Looper.getMainLooper()))
+ private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback {
+ return TestableNetworkCallback().also {
+ cm.requestNetwork(request, it)
+ networkRequests.add(it)
+ }
+ }
+ private fun releaseNetwork(cb: TestableNetworkCallback) {
+ cm.unregisterNetworkCallback(cb)
+ networkRequests.remove(cb)
+ }
+
+ private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
+ NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
+ .setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
+
+ // It can take multiple seconds for the network to become available.
+ private fun TestableNetworkCallback.expectAvailable() =
+ expectCallback<Available>(anyNetwork(), 5000/*ms timeout*/).network
+
+ // b/233534110: eventuallyExpect<Lost>() does not advance ReadHead, use
+ // eventuallyExpect(Lost::class) instead.
+ private fun TestableNetworkCallback.eventuallyExpectLost(n: Network? = null) =
+ eventuallyExpect(Lost::class, TIMEOUT_MS) { n?.equals(it.network) ?: true }
+
+ private fun TestableNetworkCallback.assertNotLost(n: Network? = null) =
+ assertNoCallbackThat() { it is Lost && (n?.equals(it.network) ?: true) }
+
+ @Test
+ fun testCallbacks() {
// If an interface exists when the callback is registered, it is reported on registration.
val iface = createInterface()
- val listener = EthernetStateListener()
- addInterfaceStateListener(executor, listener)
- listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+ val listener1 = EthernetStateListener()
+ addInterfaceStateListener(listener1)
+ validateListenerOnRegistration(listener1)
// If an interface appears, existing callbacks see it.
// TODO: fix the up/up/down/up callbacks and only send down/up.
val iface2 = createInterface()
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
// Register a new listener, it should see state of all existing interfaces immediately.
val listener2 = EthernetStateListener()
- addInterfaceStateListener(executor, listener2)
- listener2.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
- listener2.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ addInterfaceStateListener(listener2)
+ validateListenerOnRegistration(listener2)
// Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
removeInterface(iface)
- for (listener in addedListeners) {
+ for (listener in listOf(listener1, listener2)) {
listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
}
removeInterface(iface2)
- for (listener in addedListeners) {
+ for (listener in listOf(listener1, listener2)) {
listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
listener.assertNoCallback()
}
}
+ /**
+ * Validate all interfaces are returned for an EthernetStateListener upon registration.
+ */
+ private fun validateListenerOnRegistration(listener: EthernetStateListener) {
+ // Get all tracked interfaces to validate on listener registration. Ordering and interface
+ // state (up/down) can't be validated for interfaces not created as part of testing.
+ val ifaces = em.getInterfaceList()
+ val polledIfaces = ArraySet<String>()
+ for (i in ifaces) {
+ val event = (listener.pollForNextCallback() as InterfaceStateChanged)
+ val iface = event.iface
+ assertTrue(polledIfaces.add(iface), "Duplicate interface $iface returned")
+ assertTrue(ifaces.contains(iface), "Untracked interface $iface returned")
+ // If the event's iface was created in the test, additional criteria can be validated.
+ createdIfaces.find { it.interfaceName.equals(iface) }?.let {
+ assertEquals(event, listener.createChangeEvent(it, STATE_LINK_UP, ROLE_CLIENT))
+ }
+ }
+ // Assert all callbacks are accounted for.
+ listener.assertNoCallback()
+ }
+
@Test
- public fun testGetInterfaceList() {
+ fun testGetInterfaceList() {
setIncludeTestInterfaces(true)
// Create two test interfaces and check the return list contains the interface names.
@@ -254,4 +342,105 @@
removeInterface(iface2)
}
+
+ @Test
+ fun testNetworkRequest_withSingleExistingInterface() {
+ setIncludeTestInterfaces(true)
+ createInterface()
+
+ // install a listener which will later be used to verify the Lost callback
+ val listenerCb = TestableNetworkCallback()
+ cm.registerNetworkCallback(ETH_REQUEST, listenerCb)
+ networkRequests.add(listenerCb)
+
+ val cb = requestNetwork(ETH_REQUEST)
+ val network = cb.expectAvailable()
+
+ cb.assertNotLost()
+ releaseNetwork(cb)
+ listenerCb.eventuallyExpectLost(network)
+ }
+
+ @Test
+ fun testNetworkRequest_beforeSingleInterfaceIsUp() {
+ setIncludeTestInterfaces(true)
+
+ val cb = requestNetwork(ETH_REQUEST)
+
+ // bring up interface after network has been requested
+ val iface = createInterface()
+ val network = cb.expectAvailable()
+
+ // remove interface before network request has been removed
+ cb.assertNotLost()
+ removeInterface(iface)
+ cb.eventuallyExpectLost()
+
+ releaseNetwork(cb)
+ }
+
+ @Test
+ fun testNetworkRequest_withMultipleInterfaces() {
+ setIncludeTestInterfaces(true)
+
+ val iface1 = createInterface()
+ val iface2 = createInterface()
+
+ val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
+
+ val network = cb.expectAvailable()
+ cb.expectCapabilitiesThat(network) {
+ it.networkSpecifier == EthernetNetworkSpecifier(iface2.interfaceName)
+ }
+
+ removeInterface(iface1)
+ cb.assertNotLost()
+ removeInterface(iface2)
+ cb.eventuallyExpectLost()
+
+ releaseNetwork(cb)
+ }
+
+ @Test
+ fun testNetworkRequest_withInterfaceBeingReplaced() {
+ setIncludeTestInterfaces(true)
+ val iface1 = createInterface()
+
+ val cb = requestNetwork(ETH_REQUEST)
+ val network = cb.expectAvailable()
+
+ // create another network and verify the request sticks to the current network
+ val iface2 = createInterface()
+ cb.assertNotLost()
+
+ // remove iface1 and verify the request brings up iface2
+ removeInterface(iface1)
+ cb.eventuallyExpectLost(network)
+ val network2 = cb.expectAvailable()
+
+ releaseNetwork(cb)
+ }
+
+ @Test
+ fun testNetworkRequest_withMultipleInterfacesAndRequests() {
+ setIncludeTestInterfaces(true)
+ val iface1 = createInterface()
+ val iface2 = createInterface()
+
+ val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.interfaceName))
+ val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
+ val cb3 = requestNetwork(ETH_REQUEST)
+
+ cb1.expectAvailable()
+ cb2.expectAvailable()
+ cb3.expectAvailable()
+
+ cb1.assertNotLost()
+ cb2.assertNotLost()
+ cb3.assertNotLost()
+
+ releaseNetwork(cb1)
+ releaseNetwork(cb2)
+ releaseNetwork(cb3)
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 7286bf6..2b1d173 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -22,6 +22,7 @@
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.TestableNetworkCallbackKt.anyNetwork;
@@ -60,12 +61,7 @@
import com.android.internal.util.HexDump;
import com.android.networkstack.apishim.ConstantsShim;
-import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl;
-import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl;
import com.android.networkstack.apishim.VpnManagerShimImpl;
-import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim;
-import com.android.networkstack.apishim.common.Ikev2VpnProfileShim;
-import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.apishim.common.VpnManagerShim;
import com.android.networkstack.apishim.common.VpnProfileStateShim;
import com.android.testutils.DevSdkIgnoreRule;
@@ -228,22 +224,17 @@
}
private Ikev2VpnProfile buildIkev2VpnProfileCommon(
- @NonNull Ikev2VpnProfileBuilderShim builderShim, boolean isRestrictedToTestNetworks,
+ @NonNull Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks,
boolean requiresValidation) throws Exception {
- builderShim.setBypassable(true)
+ builder.setBypassable(true)
.setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
.setProxy(TEST_PROXY_INFO)
.setMaxMtu(TEST_MTU)
.setMetered(false);
if (TestUtils.shouldTestTApis()) {
- builderShim.setRequiresInternetValidation(requiresValidation);
+ builder.setRequiresInternetValidation(requiresValidation);
}
-
- // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden
- // method and does not defined in shims.
- // TODO: replace it in alternative way to remove the hidden method usage
- final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
if (isRestrictedToTestNetworks) {
builder.restrictToTestNetworks();
}
@@ -259,14 +250,13 @@
? IkeSessionTestUtils.IKE_PARAMS_V6 : IkeSessionTestUtils.IKE_PARAMS_V4,
IkeSessionTestUtils.CHILD_PARAMS);
- final Ikev2VpnProfileBuilderShim builderShim =
- Ikev2VpnProfileBuilderShimImpl.newInstance(null, null, params)
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(params)
.setRequiresInternetValidation(requiresValidation)
.setProxy(TEST_PROXY_INFO)
.setMaxMtu(TEST_MTU)
.setMetered(false);
- final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
if (isRestrictedToTestNetworks) {
builder.restrictToTestNetworks();
}
@@ -275,9 +265,8 @@
private Ikev2VpnProfile buildIkev2VpnProfilePsk(@NonNull String remote,
boolean isRestrictedToTestNetworks, boolean requiresValidation) throws Exception {
- final Ikev2VpnProfileBuilderShim builder =
- Ikev2VpnProfileBuilderShimImpl.newInstance(remote, TEST_IDENTITY, null)
- .setAuthPsk(TEST_PSK);
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
requiresValidation);
}
@@ -285,8 +274,8 @@
private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
throws Exception {
- final Ikev2VpnProfileBuilderShim builder =
- Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY, null)
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
false /* requiresValidation */);
@@ -294,8 +283,8 @@
private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
throws Exception {
- final Ikev2VpnProfileBuilderShim builder =
- Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY, null)
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
.setAuthDigitalSignature(
mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
@@ -328,15 +317,8 @@
assertNull(profile.getServerRootCaCert());
assertNull(profile.getRsaPrivateKey());
assertNull(profile.getUserCert());
- final Ikev2VpnProfileShim<Ikev2VpnProfile> shim = new Ikev2VpnProfileShimImpl(profile);
- if (TestUtils.shouldTestTApis()) {
- assertEquals(requiresValidation, shim.isInternetValidationRequired());
- } else {
- try {
- shim.isInternetValidationRequired();
- fail("Only supported from API level 33");
- } catch (UnsupportedApiLevelException expected) {
- }
+ if (isAtLeastT()) {
+ assertEquals(requiresValidation, profile.isInternetValidationRequired());
}
}
@@ -348,8 +330,8 @@
final IkeTunnelConnectionParams expectedParams = new IkeTunnelConnectionParams(
IkeSessionTestUtils.IKE_PARAMS_V6, IkeSessionTestUtils.CHILD_PARAMS);
- final Ikev2VpnProfileBuilderShim ikeProfileBuilder =
- Ikev2VpnProfileBuilderShimImpl.newInstance(null, null, expectedParams);
+ final Ikev2VpnProfile.Builder ikeProfileBuilder =
+ new Ikev2VpnProfile.Builder(expectedParams);
// Verify the other Ike options could not be set with IkeTunnelConnectionParams.
final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
assertThrows(expected, () -> ikeProfileBuilder.setAuthPsk(TEST_PSK));
@@ -358,10 +340,9 @@
assertThrows(expected, () -> ikeProfileBuilder.setAuthDigitalSignature(
mUserCertKey.cert, mUserCertKey.key, mServerRootCa));
- final Ikev2VpnProfile profile = (Ikev2VpnProfile) ikeProfileBuilder.build().getProfile();
+ final Ikev2VpnProfile profile = ikeProfileBuilder.build();
- assertEquals(expectedParams,
- new Ikev2VpnProfileShimImpl(profile).getIkeTunnelConnectionParams());
+ assertEquals(expectedParams, profile.getIkeTunnelConnectionParams());
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index fb720a7..f86c5cd 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -29,8 +29,20 @@
import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
import static android.app.usage.NetworkStats.Bucket.TAG_NONE;
import static android.app.usage.NetworkStats.Bucket.UID_ALL;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+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.AppOpsManager;
+import android.app.Instrumentation;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
@@ -40,7 +52,11 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
+import android.net.NetworkStatsCollection;
+import android.net.NetworkStatsHistory;
import android.net.TrafficStats;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -48,11 +64,23 @@
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.telephony.TelephonyManager;
-import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.ConnectivityModuleTest;
+import com.android.testutils.DevSdkIgnoreRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.IOException;
import java.io.InputStream;
@@ -62,8 +90,18 @@
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
-public class NetworkStatsManagerTest extends InstrumentationTestCase {
+@ConnectivityModuleTest
+@AppModeFull(reason = "instant apps cannot be granted USAGE_STATS")
+@RunWith(AndroidJUnit4.class)
+public class NetworkStatsManagerTest {
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(Build.VERSION_CODES.Q);
+
private static final String LOG_TAG = "NetworkStatsManagerTest";
private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
@@ -164,9 +202,11 @@
};
private String mPkg;
+ private Context mContext;
private NetworkStatsManager mNsm;
private ConnectivityManager mCm;
private PackageManager mPm;
+ private Instrumentation mInstrumentation;
private long mStartTime;
private long mEndTime;
@@ -224,44 +264,40 @@
}
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mNsm = (NetworkStatsManager) getInstrumentation().getContext()
- .getSystemService(Context.NETWORK_STATS_SERVICE);
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mNsm = mContext.getSystemService(NetworkStatsManager.class);
mNsm.setPollForce(true);
- mCm = (ConnectivityManager) getInstrumentation().getContext()
- .getSystemService(Context.CONNECTIVITY_SERVICE);
+ mCm = mContext.getSystemService(ConnectivityManager.class);
+ mPm = mContext.getPackageManager();
+ mPkg = mContext.getPackageName();
- mPm = getInstrumentation().getContext().getPackageManager();
-
- mPkg = getInstrumentation().getContext().getPackageName();
-
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
if (mWriteSettingsMode != null) {
setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
}
if (mUsageStatsMode != null) {
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
}
- super.tearDown();
}
private void setAppOpsMode(String appop, String mode) throws Exception {
final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
- SystemUtil.runShellCommand(command);
+ SystemUtil.runShellCommand(mInstrumentation, command);
}
private String getAppOpsMode(String appop) throws Exception {
final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
- String result = SystemUtil.runShellCommand(command);
+ String result = SystemUtil.runShellCommand(mInstrumentation, command);
if (result == null) {
Log.w(LOG_TAG, "App op " + appop + " could not be read.");
}
@@ -269,7 +305,7 @@
}
private boolean isInForeground() throws IOException {
- String result = SystemUtil.runShellCommand(getInstrumentation(),
+ String result = SystemUtil.runShellCommand(mInstrumentation,
"cmd activity get-uid-state " + Process.myUid());
return result.contains("FOREGROUND");
}
@@ -366,15 +402,14 @@
private String getSubscriberId(int networkIndex) {
int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
if (ConnectivityManager.TYPE_MOBILE == networkType) {
- TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
- .getSystemService(Context.TELEPHONY_SERVICE);
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
(telephonyManager) -> telephonyManager.getSubscriberId());
}
return "";
}
- @AppModeFull
+ @Test
public void testDeviceSummary() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
@@ -410,7 +445,7 @@
}
}
- @AppModeFull
+ @Test
public void testUserSummary() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
@@ -446,7 +481,7 @@
}
}
- @AppModeFull
+ @Test
public void testAppSummary() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Use tolerance value that large enough to make sure stats of at
@@ -522,7 +557,7 @@
}
}
- @AppModeFull
+ @Test
public void testAppDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -565,7 +600,7 @@
}
}
- @AppModeFull
+ @Test
public void testUidDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -619,7 +654,7 @@
}
}
- @AppModeFull
+ @Test
public void testTagDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -726,7 +761,7 @@
bucket.getRxBytes(), bucket.getTxBytes()));
}
- @AppModeFull
+ @Test
public void testUidTagStateDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -803,7 +838,7 @@
}
}
- @AppModeFull
+ @Test
public void testCallback() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
@@ -825,6 +860,43 @@
// storing files of >2MB in CTS.
mNsm.unregisterUsageCallback(usageCallback);
+
+ // For T- devices, the registerUsageCallback invocation below will need a looper
+ // from the thread that calls into the API, which is not available in the test.
+ if (SdkLevel.isAtLeastT()) {
+ mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
+ getSubscriberId(i), THRESHOLD_BYTES, usageCallback);
+ mNsm.unregisterUsageCallback(usageCallback);
+ }
+ }
+ }
+
+ @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @Test
+ public void testDataMigrationUtils() throws Exception {
+ final List<String> prefixes = List.of(PREFIX_UID, PREFIX_XT, PREFIX_UID_TAG);
+ for (final String prefix : prefixes) {
+ final long duration = TextUtils.equals(PREFIX_XT, prefix) ? TimeUnit.HOURS.toMillis(1)
+ : TimeUnit.HOURS.toMillis(2);
+
+ final NetworkStatsCollection collection =
+ NetworkStatsDataMigrationUtils.readPlatformCollection(prefix, duration);
+
+ final long now = System.currentTimeMillis();
+ final Set<Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory>> entries =
+ collection.getEntries().entrySet();
+ for (final Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory> entry : entries) {
+ for (final NetworkStatsHistory.Entry historyEntry : entry.getValue().getEntries()) {
+ // Verify all value fields are reasonable.
+ assertTrue(historyEntry.getBucketStart() <= now);
+ assertTrue(historyEntry.getActiveTime() <= duration);
+ assertTrue(historyEntry.getRxBytes() >= 0);
+ assertTrue(historyEntry.getRxPackets() >= 0);
+ assertTrue(historyEntry.getTxBytes() >= 0);
+ assertTrue(historyEntry.getTxPackets() >= 0);
+ assertTrue(historyEntry.getOperations() >= 0);
+ }
+ }
}
}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index b139a9b..33a0a83 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -52,10 +52,7 @@
import androidx.test.runner.AndroidJUnit4
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
-import com.android.networkstack.apishim.ConstantsShim
import com.android.networkstack.apishim.NsdShimImpl
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.SC_V2
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
@@ -65,7 +62,6 @@
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.net.ServerSocket
@@ -82,6 +78,7 @@
private const val TAG = "NsdManagerTest"
private const val SERVICE_TYPE = "_nmt._tcp"
private const val TIMEOUT_MS = 2000L
+private const val NO_CALLBACK_TIMEOUT_MS = 200L
private const val DBG = false
private val nsdShim = NsdShimImpl.newInstance()
@@ -89,10 +86,6 @@
@AppModeFull(reason = "Socket cannot bind in instant app mode")
@RunWith(AndroidJUnit4::class)
class NsdManagerTest {
- // NsdManager is not updatable before S, so tests do not need to be backwards compatible
- @get:Rule
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
-
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
@@ -136,6 +129,11 @@
nextEvent.javaClass.simpleName)
return nextEvent
}
+
+ inline fun assertNoCallback(timeoutMs: Long = NO_CALLBACK_TIMEOUT_MS) {
+ val cb = nextEvents.poll(timeoutMs)
+ assertNull(cb, "Expected no callback but got $cb")
+ }
}
private class NsdRegistrationRecord : RegistrationListener,
@@ -249,9 +247,11 @@
fun setUp() {
handlerThread.start()
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1 = createTestNetwork()
- testNetwork2 = createTestNetwork()
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1 = createTestNetwork()
+ testNetwork2 = createTestNetwork()
+ }
}
}
@@ -290,9 +290,11 @@
@After
fun tearDown() {
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1.close(cm)
- testNetwork2.close(cm)
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1.close(cm)
+ testNetwork2.close(cm)
+ }
}
handlerThread.quitSafely()
}
@@ -393,14 +395,17 @@
si2.serviceName = serviceName
si2.port = localPort
val registrationRecord2 = NsdRegistrationRecord()
- val registeredInfo2 = registerService(registrationRecord2, si2)
+ nsdManager.registerService(si2, NsdManager.PROTOCOL_DNS_SD, registrationRecord2)
+ val registeredInfo2 = registrationRecord2.expectCallback<ServiceRegistered>().serviceInfo
// Expect a service record to be discovered (and filter the ones
// that are unrelated to this test)
val foundInfo2 = discoveryRecord.waitForServiceDiscovered(registeredInfo2.serviceName)
// Resolve the service
- val resolvedService2 = resolveService(foundInfo2)
+ val resolveRecord2 = NsdResolveRecord()
+ nsdManager.resolveService(foundInfo2, resolveRecord2)
+ val resolvedService2 = resolveRecord2.expectCallback<ServiceResolved>().serviceInfo
// Check that the resolved service doesn't have any TXT records
assertEquals(0, resolvedService2.attributes.size)
@@ -416,7 +421,7 @@
@Test
fun testNsdManager_DiscoverOnNetwork() {
// This test requires shims supporting T+ APIs (discovering on specific network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -450,7 +455,7 @@
@Test
fun testNsdManager_DiscoverWithNetworkRequest() {
// This test requires shims supporting T+ APIs (discovering on network request)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -515,7 +520,7 @@
@Test
fun testNsdManager_ResolveOnNetwork() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -556,6 +561,90 @@
}
}
+ @Test
+ fun testNsdManager_RegisterOnNetwork() {
+ // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
+ assumeTrue(TestUtils.shouldTestTApis())
+
+ val si = NsdServiceInfo()
+ si.serviceType = SERVICE_TYPE
+ si.serviceName = this.serviceName
+ si.network = testNetwork1.network
+ si.port = 12345 // Test won't try to connect so port does not matter
+
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ registerService(registrationRecord, si)
+ val discoveryRecord = NsdDiscoveryRecord()
+ val discoveryRecord2 = NsdDiscoveryRecord()
+ val discoveryRecord3 = NsdDiscoveryRecord()
+
+ tryTest {
+ // Discover service on testNetwork1.
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord)
+ // Expect that service is found on testNetwork1
+ val foundInfo = discoveryRecord.waitForServiceDiscovered(
+ serviceName, testNetwork1.network)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
+
+ // Discover service on testNetwork2.
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork2.network, Executor { it.run() }, discoveryRecord2)
+ // Expect that discovery is started then no other callbacks.
+ discoveryRecord2.expectCallback<DiscoveryStarted>()
+ discoveryRecord2.assertNoCallback()
+
+ // Discover service on all networks (not specify any network).
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ null as Network? /* network */, Executor { it.run() }, discoveryRecord3)
+ // Expect that service is found on testNetwork1
+ val foundInfo3 = discoveryRecord3.waitForServiceDiscovered(
+ serviceName, testNetwork1.network)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo3))
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord2)
+ discoveryRecord2.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ }
+ }
+
+ @Test
+ fun testNsdManager_RegisterServiceNameWithNonStandardCharacters() {
+ val serviceNames = "^Nsd.Test|Non-#AsCiI\\Characters&\\ufffe テスト 測試"
+ val si = NsdServiceInfo().apply {
+ serviceType = SERVICE_TYPE
+ serviceName = serviceNames
+ port = 12345 // Test won't try to connect so port does not matter
+ }
+
+ // Register the service name which contains non-standard characters.
+ val registrationRecord = NsdRegistrationRecord()
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
+ registrationRecord.expectCallback<ServiceRegistered>()
+
+ tryTest {
+ // Discover that service name.
+ val discoveryRecord = NsdDiscoveryRecord()
+ nsdManager.discoverServices(
+ SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord
+ )
+ val foundInfo = discoveryRecord.waitForServiceDiscovered(serviceNames)
+
+ // Expect that resolving the service name works properly even service name contains
+ // non-standard characters.
+ val resolveRecord = NsdResolveRecord()
+ nsdManager.resolveService(foundInfo, resolveRecord)
+ val resolvedCb = resolveRecord.expectCallback<ServiceResolved>()
+ assertEquals(foundInfo.serviceName, resolvedCb.serviceInfo.serviceName)
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ } cleanup {
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
/**
* Register a service and return its registration record.
*/
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 25694d7..db39e6f 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -42,7 +42,9 @@
#define PLATFORM "/sys/fs/bpf/"
#define TETHERING "/sys/fs/bpf/tethering/"
+#define PRIVATE "/sys/fs/bpf/net_private/"
#define SHARED "/sys/fs/bpf/net_shared/"
+#define NETD "/sys/fs/bpf/netd_shared/"
class BpfExistenceTest : public ::testing::Test {
};
@@ -95,32 +97,35 @@
SHARED "map_dscp_policy_ipv6_socket_to_policies_map_A",
SHARED "map_dscp_policy_ipv6_socket_to_policies_map_B",
SHARED "map_dscp_policy_switch_comp_map",
- SHARED "map_netd_app_uid_stats_map",
- SHARED "map_netd_configuration_map",
- SHARED "map_netd_cookie_tag_map",
- SHARED "map_netd_iface_index_name_map",
- SHARED "map_netd_iface_stats_map",
- SHARED "map_netd_stats_map_A",
- SHARED "map_netd_stats_map_B",
- SHARED "map_netd_uid_counterset_map",
- SHARED "map_netd_uid_owner_map",
- SHARED "map_netd_uid_permission_map",
- SHARED "prog_block_bind4_block_port",
- SHARED "prog_block_bind6_block_port",
+ NETD "map_netd_app_uid_stats_map",
+ NETD "map_netd_configuration_map",
+ NETD "map_netd_cookie_tag_map",
+ NETD "map_netd_iface_index_name_map",
+ NETD "map_netd_iface_stats_map",
+ NETD "map_netd_stats_map_A",
+ NETD "map_netd_stats_map_B",
+ NETD "map_netd_uid_counterset_map",
+ NETD "map_netd_uid_owner_map",
+ NETD "map_netd_uid_permission_map",
SHARED "prog_clatd_schedcls_egress4_clat_ether",
SHARED "prog_clatd_schedcls_egress4_clat_rawip",
SHARED "prog_clatd_schedcls_ingress6_clat_ether",
SHARED "prog_clatd_schedcls_ingress6_clat_rawip",
+ NETD "prog_netd_cgroupskb_egress_stats",
+ NETD "prog_netd_cgroupskb_ingress_stats",
+ NETD "prog_netd_cgroupsock_inet_create",
+ NETD "prog_netd_schedact_ingress_account",
+ NETD "prog_netd_skfilter_allowlist_xtbpf",
+ NETD "prog_netd_skfilter_denylist_xtbpf",
+ NETD "prog_netd_skfilter_egress_xtbpf",
+ NETD "prog_netd_skfilter_ingress_xtbpf",
+};
+
+static const set<string> INTRODUCED_T_5_4 = {
+ SHARED "prog_block_bind4_block_port",
+ SHARED "prog_block_bind6_block_port",
SHARED "prog_dscp_policy_schedcls_set_dscp_ether",
SHARED "prog_dscp_policy_schedcls_set_dscp_raw_ip",
- SHARED "prog_netd_cgroupskb_egress_stats",
- SHARED "prog_netd_cgroupskb_ingress_stats",
- SHARED "prog_netd_cgroupsock_inet_create",
- SHARED "prog_netd_schedact_ingress_account",
- SHARED "prog_netd_skfilter_allowlist_xtbpf",
- SHARED "prog_netd_skfilter_denylist_xtbpf",
- SHARED "prog_netd_skfilter_egress_xtbpf",
- SHARED "prog_netd_skfilter_ingress_xtbpf",
};
static const set<string> REMOVED_T = {
@@ -162,6 +167,7 @@
if (IsAtLeastT()) {
addAll(expected, INTRODUCED_T);
+ if (android::bpf::isAtLeastKernelVersion(5, 4, 0)) addAll(expected, INTRODUCED_T_5_4);
removeAll(expected, REMOVED_T);
addAll(unexpected, REMOVED_T);
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index f324630..c327868 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -41,6 +41,7 @@
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.mockito.ArgumentMatchers.anyBoolean;
@@ -72,6 +73,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -82,6 +84,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.lang.ref.WeakReference;
+
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
@@ -461,4 +465,49 @@
}
fail("expected exception of type " + throwableType);
}
+
+ private static class MockContext extends BroadcastInterceptingContext {
+ MockContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return mock(Context.class);
+ }
+ }
+
+ private WeakReference<Context> makeConnectivityManagerAndReturnContext() {
+ // Mockito may have an internal reference to the mock, creating MockContext for testing.
+ final Context c = new MockContext(mock(Context.class));
+
+ new ConnectivityManager(c, mService);
+
+ return new WeakReference<>(c);
+ }
+
+ private void forceGC() {
+ // First GC ensures that objects are collected for finalization, then second GC ensures
+ // they're garbage-collected after being finalized.
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }
+
+ @Test
+ public void testConnectivityManagerDoesNotLeakContext() throws Exception {
+ final WeakReference<Context> ref = makeConnectivityManagerAndReturnContext();
+
+ final int attempts = 100;
+ final long waitIntervalMs = 50;
+ for (int i = 0; i < attempts; i++) {
+ forceGC();
+ if (ref.get() == null) break;
+
+ Thread.sleep(waitIntervalMs);
+ }
+
+ assertNull("ConnectivityManager weak reference still not null after " + attempts
+ + " attempts", ref.get());
+ }
}
diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
index 0f02850..b518a61 100644
--- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -37,12 +37,15 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import android.annotation.NonNull;
import android.content.res.Resources;
+import android.net.NetworkStatsCollection.Key;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.RecurrenceRule;
import androidx.test.InstrumentationRegistry;
@@ -73,6 +76,8 @@
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Tests for {@link NetworkStatsCollection}.
@@ -531,6 +536,86 @@
assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0));
}
+ private static void assertCollectionEntries(
+ @NonNull Map<Key, NetworkStatsHistory> expectedEntries,
+ @NonNull NetworkStatsCollection collection) {
+ final Map<Key, NetworkStatsHistory> actualEntries = collection.getEntries();
+ assertEquals(expectedEntries.size(), actualEntries.size());
+ for (Key expectedKey : expectedEntries.keySet()) {
+ final NetworkStatsHistory expectedHistory = expectedEntries.get(expectedKey);
+ final NetworkStatsHistory actualHistory = actualEntries.get(expectedKey);
+ assertNotNull(actualHistory);
+ assertEquals(expectedHistory.getEntries(), actualHistory.getEntries());
+ actualEntries.remove(expectedKey);
+ }
+ assertEquals(0, actualEntries.size());
+ }
+
+ @Test
+ public void testRemoveHistoryBefore() {
+ final NetworkIdentity testIdent = new NetworkIdentity.Builder()
+ .setSubscriberId(TEST_IMSI).build();
+ final Key key1 = new Key(Set.of(testIdent), 0, 0, 0);
+ final Key key2 = new Key(Set.of(testIdent), 1, 0, 0);
+ final long bucketDuration = 10;
+
+ // Prepare entries for testing, with different bucket start timestamps.
+ final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 10, 40,
+ 4, 50, 5, 60);
+ final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(20, 10, 3,
+ 41, 7, 1, 0);
+ final NetworkStatsHistory.Entry entry3 = new NetworkStatsHistory.Entry(30, 10, 1,
+ 21, 70, 4, 1);
+
+ NetworkStatsHistory history1 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .build();
+ NetworkStatsHistory history2 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry2)
+ .addEntry(entry3)
+ .build();
+ NetworkStatsCollection collection = new NetworkStatsCollection.Builder(bucketDuration)
+ .addEntry(key1, history1)
+ .addEntry(key2, history2)
+ .build();
+
+ // Verify nothing is removed if the cutoff time is equal to bucketStart.
+ collection.removeHistoryBefore(10);
+ final Map<Key, NetworkStatsHistory> expectedEntries = new ArrayMap<>();
+ expectedEntries.put(key1, history1);
+ expectedEntries.put(key2, history2);
+ assertCollectionEntries(expectedEntries, collection);
+
+ // Verify entry1 will be removed if its bucket start before to cutoff timestamp.
+ collection.removeHistoryBefore(11);
+ history1 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry2)
+ .build();
+ history2 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry2)
+ .addEntry(entry3)
+ .build();
+ final Map<Key, NetworkStatsHistory> cutoff1Entries1 = new ArrayMap<>();
+ cutoff1Entries1.put(key1, history1);
+ cutoff1Entries1.put(key2, history2);
+ assertCollectionEntries(cutoff1Entries1, collection);
+
+ // Verify entry2 will be removed if its bucket start covers by cutoff timestamp.
+ collection.removeHistoryBefore(22);
+ history2 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry3)
+ .build();
+ final Map<Key, NetworkStatsHistory> cutoffEntries2 = new ArrayMap<>();
+ // History1 is not expected since the collection will omit empty entries.
+ cutoffEntries2.put(key2, history2);
+ assertCollectionEntries(cutoffEntries2, collection);
+
+ // Verify all entries will be removed if cutoff timestamp covers all.
+ collection.removeHistoryBefore(Long.MAX_VALUE);
+ assertEquals(0, collection.getEntries().size());
+ }
+
/**
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
* testing purposes.
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index c5f8c00..26079a2 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -270,7 +270,7 @@
}
@Test
- public void testRemove() throws Exception {
+ public void testRemoveStartingBefore() throws Exception {
stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
// record some data across 24 buckets
@@ -278,28 +278,28 @@
assertEquals(24, stats.size());
// try removing invalid data; should be no change
- stats.removeBucketsBefore(0 - DAY_IN_MILLIS);
+ stats.removeBucketsStartingBefore(0 - DAY_IN_MILLIS);
assertEquals(24, stats.size());
// try removing far before buckets; should be no change
- stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+ stats.removeBucketsStartingBefore(TEST_START - YEAR_IN_MILLIS);
assertEquals(24, stats.size());
// try removing just moments into first bucket; should be no change
- // since that bucket contains data beyond the cutoff
- stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+ // since that bucket doesn't contain data starts before the cutoff
+ stats.removeBucketsStartingBefore(TEST_START);
assertEquals(24, stats.size());
// try removing single bucket
- stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+ stats.removeBucketsStartingBefore(TEST_START + HOUR_IN_MILLIS);
assertEquals(23, stats.size());
// try removing multiple buckets
- stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+ stats.removeBucketsStartingBefore(TEST_START + (4 * HOUR_IN_MILLIS));
assertEquals(20, stats.size());
// try removing all buckets
- stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+ stats.removeBucketsStartingBefore(TEST_START + YEAR_IN_MILLIS);
assertEquals(0, stats.size());
}
@@ -349,7 +349,7 @@
stats.recordData(start, end, entry);
} else {
// trim something
- stats.removeBucketsBefore(r.nextLong());
+ stats.removeBucketsStartingBefore(r.nextLong());
}
}
assertConsistent(stats);
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 453612f..abd1825 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -29,12 +29,8 @@
import android.net.NetworkStats.METERED_NO
import android.net.NetworkStats.METERED_YES
import android.net.NetworkStats.ROAMING_ALL
-import android.net.NetworkTemplate.MATCH_BLUETOOTH
-import android.net.NetworkTemplate.MATCH_CARRIER
-import android.net.NetworkTemplate.MATCH_ETHERNET
import android.net.NetworkTemplate.MATCH_MOBILE
import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
-import android.net.NetworkTemplate.MATCH_PROXY
import android.net.NetworkTemplate.MATCH_WIFI
import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
import android.net.NetworkTemplate.NETWORK_TYPE_ALL
@@ -52,11 +48,9 @@
import android.net.wifi.WifiInfo
import android.os.Build
import android.telephony.TelephonyManager
-import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.SC_V2
import com.android.testutils.assertParcelSane
import org.junit.Before
import org.junit.Test
@@ -65,7 +59,6 @@
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
@@ -555,140 +548,4 @@
it.assertMatches(identMobileImsi3)
}
}
-
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
- @Test
- fun testBuilderMatchRules() {
- // Verify unknown match rules cannot construct templates.
- listOf(Integer.MIN_VALUE, -1, Integer.MAX_VALUE).forEach {
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(it).build()
- }
- }
-
- // Verify hidden match rules cannot construct templates.
- listOf(MATCH_WIFI_WILDCARD, MATCH_MOBILE_WILDCARD, MATCH_PROXY).forEach {
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(it).build()
- }
- }
-
- // Verify template which matches metered cellular and carrier networks with
- // the given IMSI. See buildTemplateMobileAll and buildTemplateCarrierMetered.
- listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
- NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
- .setMeteredness(METERED_YES).build().let {
- val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
- arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
- ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
- assertEquals(expectedTemplate, it)
- }
- }
-
- // Verify carrier template cannot be created without IMSI.
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(MATCH_CARRIER).build()
- }
-
- // Verify template which matches metered cellular networks,
- // regardless of IMSI. See buildTemplateMobileWildcard.
- NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_MOBILE_WILDCARD, null /*subscriberId*/,
- null /*subscriberIds*/, arrayOf<String>(),
- METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches metered cellular networks and ratType.
- // See NetworkTemplate#buildTemplateMobileWithRatType.
- NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(TEST_IMSI1))
- .setMeteredness(METERED_YES).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
- .build().let {
- val expectedTemplate = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1,
- arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
- ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_UMTS,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches all wifi networks,
- // regardless of Wifi Network Key. See buildTemplateWifiWildcard and buildTemplateWifi.
- NetworkTemplate.Builder(MATCH_WIFI).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
- null /*subscriberIds*/, arrayOf<String>(),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches wifi networks with the given Wifi Network Key.
- // See buildTemplateWifi(wifiNetworkKey).
- NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_WIFI, null /*subscriberId*/,
- null /*subscriberIds*/, arrayOf(TEST_WIFI_KEY1),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches all wifi networks with the
- // given Wifi Network Key, and IMSI. See buildTemplateWifi(wifiNetworkKey, subscriberId).
- NetworkTemplate.Builder(MATCH_WIFI).setSubscriberIds(setOf(TEST_IMSI1))
- .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_WIFI, TEST_IMSI1,
- arrayOf(TEST_IMSI1), arrayOf(TEST_WIFI_KEY1),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches ethernet and bluetooth networks.
- // See buildTemplateEthernet and buildTemplateBluetooth.
- listOf(MATCH_ETHERNET, MATCH_BLUETOOTH).forEach { matchRule ->
- NetworkTemplate.Builder(matchRule).build().let {
- val expectedTemplate = NetworkTemplate(matchRule, null /*subscriberId*/,
- null /*subscriberIds*/, arrayOf<String>(),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
- }
- }
-
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
- @Test
- fun testBuilderWifiNetworkKeys() {
- // Verify template builder which generates same template with the given different
- // sequence keys.
- NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
- setOf(TEST_WIFI_KEY1, TEST_WIFI_KEY2)).build().let {
- val expectedTemplate = NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
- setOf(TEST_WIFI_KEY2, TEST_WIFI_KEY1)).build()
- assertEquals(expectedTemplate, it)
- }
-
- // Verify template which matches non-wifi networks with the given key is invalid.
- listOf(MATCH_MOBILE, MATCH_CARRIER, MATCH_ETHERNET, MATCH_BLUETOOTH, -1,
- Integer.MAX_VALUE).forEach { matchRule ->
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(matchRule).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
- }
- }
-
- // Verify template which matches wifi networks with the given null key is invalid.
- assertFailsWith<IllegalArgumentException> {
- NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(null)).build()
- }
-
- // Verify template which matches wifi wildcard with the given empty key set.
- NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf<String>()).build().let {
- val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
- arrayOf<String>() /*subscriberIds*/, arrayOf<String>(),
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
- OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
- assertEquals(expectedTemplate, it)
- }
- }
}
diff --git a/tests/unit/java/android/net/QosSocketFilterTest.java b/tests/unit/java/android/net/QosSocketFilterTest.java
index 91f2cdd..6820b40 100644
--- a/tests/unit/java/android/net/QosSocketFilterTest.java
+++ b/tests/unit/java/android/net/QosSocketFilterTest.java
@@ -16,8 +16,17 @@
package android.net;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.os.Build;
@@ -29,6 +38,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -36,14 +46,14 @@
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
public class QosSocketFilterTest {
-
+ private static final int TEST_NET_ID = 1777;
+ private final Network mNetwork = new Network(TEST_NET_ID);
@Test
public void testPortExactMatch() {
final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4");
assertTrue(QosSocketFilter.matchesAddress(
new InetSocketAddress(addressA, 10), addressB, 10, 10));
-
}
@Test
@@ -77,5 +87,90 @@
assertFalse(QosSocketFilter.matchesAddress(
new InetSocketAddress(addressA, 10), addressB, 10, 10));
}
+
+ @Test
+ public void testAddressMatchWithAnyLocalAddresses() {
+ final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
+ final InetAddress addressB = InetAddresses.parseNumericAddress("0.0.0.0");
+ assertTrue(QosSocketFilter.matchesAddress(
+ new InetSocketAddress(addressA, 10), addressB, 10, 10));
+ assertFalse(QosSocketFilter.matchesAddress(
+ new InetSocketAddress(addressB, 10), addressA, 10, 10));
+ }
+
+ @Test
+ public void testProtocolMatch() throws Exception {
+ DatagramSocket socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 0));
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 10));
+ DatagramSocket socketV6 = new DatagramSocket(new InetSocketAddress("::1", 0));
+ socketV6.connect(new InetSocketAddress("::1", socketV6.getLocalPort() + 10));
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socketV6);
+ QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+ assertTrue(socketFilter.matchesProtocol(IPPROTO_UDP));
+ assertTrue(socketFilter6.matchesProtocol(IPPROTO_UDP));
+ assertFalse(socketFilter.matchesProtocol(IPPROTO_TCP));
+ assertFalse(socketFilter6.matchesProtocol(IPPROTO_TCP));
+ socket.close();
+ socketV6.close();
+ }
+
+ @Test
+ public void testValidate() throws Exception {
+ DatagramSocket socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 0));
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 7));
+ DatagramSocket socketV6 = new DatagramSocket(new InetSocketAddress("::1", 0));
+
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socketV6);
+ QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+ assertEquals(EX_TYPE_FILTER_NONE, socketFilter.validate());
+ assertEquals(EX_TYPE_FILTER_NONE, socketFilter6.validate());
+ socket.close();
+ socketV6.close();
+ }
+
+ @Test
+ public void testValidateUnbind() throws Exception {
+ DatagramSocket socket;
+ socket = new DatagramSocket(null);
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ assertEquals(EX_TYPE_FILTER_SOCKET_NOT_BOUND, socketFilter.validate());
+ socket.close();
+ }
+
+ @Test
+ public void testValidateLocalAddressChanged() throws Exception {
+ DatagramSocket socket = new DatagramSocket(null);
+ DatagramSocket socket6 = new DatagramSocket(null);
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socket6);
+ QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+ socket.bind(new InetSocketAddress("127.0.0.1", 0));
+ socket6.bind(new InetSocketAddress("::1", 0));
+ assertEquals(EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, socketFilter.validate());
+ assertEquals(EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, socketFilter6.validate());
+ socket.close();
+ socket6.close();
+ }
+
+ @Test
+ public void testValidateRemoteAddressChanged() throws Exception {
+ DatagramSocket socket;
+ socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 53137));
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 11));
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ assertEquals(EX_TYPE_FILTER_NONE, socketFilter.validate());
+ socket.disconnect();
+ assertEquals(EX_TYPE_FILTER_SOCKET_NOT_CONNECTED, socketFilter.validate());
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 13));
+ assertEquals(EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED, socketFilter.validate());
+ socket.close();
+ }
}
diff --git a/tests/unit/java/android/net/QosSocketInfoTest.java b/tests/unit/java/android/net/QosSocketInfoTest.java
new file mode 100644
index 0000000..749c182
--- /dev/null
+++ b/tests/unit/java/android/net/QosSocketInfoTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 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;
+
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.net.DatagramSocket;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class QosSocketInfoTest {
+ @Mock
+ private Network mMockNetwork = mock(Network.class);
+
+ @Test
+ public void testConstructWithSock() throws Exception {
+ ServerSocket server = new ServerSocket();
+ ServerSocket server6 = new ServerSocket();
+
+ InetSocketAddress clientAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress serverAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress clientAddr6 = new InetSocketAddress("::1", 0);
+ InetSocketAddress serverAddr6 = new InetSocketAddress("::1", 0);
+ server.bind(serverAddr);
+ server6.bind(serverAddr6);
+ Socket socket = new Socket(serverAddr.getAddress(), server.getLocalPort(),
+ clientAddr.getAddress(), clientAddr.getPort());
+ Socket socket6 = new Socket(serverAddr6.getAddress(), server6.getLocalPort(),
+ clientAddr6.getAddress(), clientAddr6.getPort());
+ QosSocketInfo sockInfo = new QosSocketInfo(mMockNetwork, socket);
+ QosSocketInfo sockInfo6 = new QosSocketInfo(mMockNetwork, socket6);
+ assertTrue(sockInfo.getLocalSocketAddress()
+ .equals(new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort())));
+ assertTrue(sockInfo.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket.getRemoteSocketAddress()));
+ assertEquals(SOCK_STREAM, sockInfo.getSocketType());
+ assertTrue(sockInfo6.getLocalSocketAddress()
+ .equals(new InetSocketAddress(socket6.getLocalAddress(), socket6.getLocalPort())));
+ assertTrue(sockInfo6.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket6.getRemoteSocketAddress()));
+ assertEquals(SOCK_STREAM, sockInfo6.getSocketType());
+ socket.close();
+ socket6.close();
+ server.close();
+ server6.close();
+ }
+
+ @Test
+ public void testConstructWithDatagramSock() throws Exception {
+ InetSocketAddress clientAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress serverAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress clientAddr6 = new InetSocketAddress("::1", 0);
+ InetSocketAddress serverAddr6 = new InetSocketAddress("::1", 0);
+ DatagramSocket socket = new DatagramSocket(null);
+ socket.setReuseAddress(true);
+ socket.bind(clientAddr);
+ socket.connect(serverAddr);
+ DatagramSocket socket6 = new DatagramSocket(null);
+ socket6.setReuseAddress(true);
+ socket6.bind(clientAddr);
+ socket6.connect(serverAddr);
+ QosSocketInfo sockInfo = new QosSocketInfo(mMockNetwork, socket);
+ QosSocketInfo sockInfo6 = new QosSocketInfo(mMockNetwork, socket6);
+ assertTrue(sockInfo.getLocalSocketAddress()
+ .equals((InetSocketAddress) socket.getLocalSocketAddress()));
+ assertTrue(sockInfo.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket.getRemoteSocketAddress()));
+ assertEquals(SOCK_DGRAM, sockInfo.getSocketType());
+ assertTrue(sockInfo6.getLocalSocketAddress()
+ .equals((InetSocketAddress) socket6.getLocalSocketAddress()));
+ assertTrue(sockInfo6.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket6.getRemoteSocketAddress()));
+ assertEquals(SOCK_DGRAM, sockInfo6.getSocketType());
+ socket.close();
+ }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 32ea9c3..ef97168 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -51,6 +51,9 @@
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
@@ -104,6 +107,10 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_2;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_4;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -136,6 +143,8 @@
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_PROFILE;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_VPN;
import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
+import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
+import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
import static com.android.testutils.ConcurrentUtils.await;
import static com.android.testutils.ConcurrentUtils.durationOf;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -467,6 +476,9 @@
private static final int TEST_APP_ID_2 = 104;
private static final int TEST_WORK_PROFILE_APP_UID_2 =
UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID_2);
+ private static final int TEST_APP_ID_3 = 105;
+ private static final int TEST_APP_ID_4 = 106;
+ private static final int TEST_APP_ID_5 = 107;
private static final String CLAT_PREFIX = "v4-";
private static final String MOBILE_IFNAME = "test_rmnet_data0";
@@ -796,6 +808,32 @@
}
}
+ // This was only added in the T SDK, but this test needs to build against the R+S SDKs, too.
+ private static int toSdkSandboxUid(int appUid) {
+ final int firstSdkSandboxUid = 20000;
+ return appUid + (firstSdkSandboxUid - Process.FIRST_APPLICATION_UID);
+ }
+
+ // This function assumes the UID range for user 0 ([1, 99999])
+ private static UidRangeParcel[] uidRangeParcelsExcludingUids(Integer... excludedUids) {
+ int start = 1;
+ Arrays.sort(excludedUids);
+ List<UidRangeParcel> parcels = new ArrayList<UidRangeParcel>();
+ for (int excludedUid : excludedUids) {
+ if (excludedUid == start) {
+ start++;
+ } else {
+ parcels.add(new UidRangeParcel(start, excludedUid - 1));
+ start = excludedUid + 1;
+ }
+ }
+ if (start <= 99999) {
+ parcels.add(new UidRangeParcel(start, 99999));
+ }
+
+ return parcels.toArray(new UidRangeParcel[0]);
+ }
+
private void waitForIdle() {
HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
waitForIdle(mCellNetworkAgent, TIMEOUT_MS);
@@ -5871,7 +5909,7 @@
}
/**
- * Validate the callback flow CBS request without carrier privilege.
+ * Validate the service throws if request with CBS but without carrier privilege.
*/
@Test
public void testCBSRequestWithoutCarrierPrivilege() throws Exception {
@@ -5880,10 +5918,8 @@
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
- // Now file the test request and expect it.
- mCm.requestNetwork(nr, networkCallback);
- networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
- mCm.unregisterNetworkCallback(networkCallback);
+ // Now file the test request and expect the service throws.
+ assertThrows(SecurityException.class, () -> mCm.requestNetwork(nr, networkCallback));
}
private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
@@ -9009,10 +9045,16 @@
allowList);
waitForIdle();
- UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
- UidRangeParcel secondHalf = new UidRangeParcel(VPN_UID + 1, 99999);
+ final Set<Integer> excludedUids = new ArraySet<Integer>();
+ excludedUids.add(VPN_UID);
+ if (SdkLevel.isAtLeastT()) {
+ // On T onwards, the corresponding SDK sandbox UID should also be excluded
+ excludedUids.add(toSdkSandboxUid(VPN_UID));
+ }
+ final UidRangeParcel[] uidRangeParcels = uidRangeParcelsExcludingUids(
+ excludedUids.toArray(new Integer[0]));
InOrder inOrder = inOrder(mMockNetd);
- expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+ expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
// Connect a network when lockdown is active, expect to see it blocked.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -9036,7 +9078,7 @@
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
vpnDefaultCallbackAsUid.assertNoCallback();
- expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
+ expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcels);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9053,13 +9095,14 @@
vpnUidDefaultCallback.assertNoCallback();
vpnDefaultCallbackAsUid.assertNoCallback();
- // The following requires that the UID of this test package is greater than VPN_UID. This
- // is always true in practice because a plain AOSP build with no apps installed has almost
- // 200 packages installed.
- final UidRangeParcel piece1 = new UidRangeParcel(1, VPN_UID - 1);
- final UidRangeParcel piece2 = new UidRangeParcel(VPN_UID + 1, uid - 1);
- final UidRangeParcel piece3 = new UidRangeParcel(uid + 1, 99999);
- expectNetworkRejectNonSecureVpn(inOrder, true, piece1, piece2, piece3);
+ excludedUids.add(uid);
+ if (SdkLevel.isAtLeastT()) {
+ // On T onwards, the corresponding SDK sandbox UID should also be excluded
+ excludedUids.add(toSdkSandboxUid(uid));
+ }
+ final UidRangeParcel[] uidRangeParcelsAlsoExcludingUs = uidRangeParcelsExcludingUids(
+ excludedUids.toArray(new Integer[0]));
+ expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcelsAlsoExcludingUs);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9085,12 +9128,12 @@
// Everything should now be blocked.
mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
waitForIdle();
- expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
+ expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcelsAlsoExcludingUs);
allowList.clear();
mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
allowList);
waitForIdle();
- expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+ expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
vpnUidCallback.assertNoCallback();
@@ -9462,6 +9505,46 @@
b2.expectBroadcast();
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testLockdownSetFirewallUidRule() throws Exception {
+ // For ConnectivityService#setAlwaysOnVpnPackage.
+ mServiceContext.setPermission(
+ Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED);
+ // Needed to call Vpn#setAlwaysOnPackage.
+ mServiceContext.setPermission(Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
+ // Needed to call Vpn#isAlwaysOnPackageSupported.
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
+
+ // Enable Lockdown
+ final ArrayList<String> allowList = new ArrayList<>();
+ mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE,
+ true /* lockdown */, allowList);
+ waitForIdle();
+
+ // Lockdown rule is set to apps uids
+ verify(mBpfNetMaps).setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP1_UID), eq(FIREWALL_RULE_DENY));
+ verify(mBpfNetMaps).setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP2_UID), eq(FIREWALL_RULE_DENY));
+
+ reset(mBpfNetMaps);
+
+ // Disable lockdown
+ mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */,
+ allowList);
+ waitForIdle();
+
+ // Lockdown rule is removed from apps uids
+ verify(mBpfNetMaps).setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP1_UID), eq(FIREWALL_RULE_ALLOW));
+ verify(mBpfNetMaps).setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP2_UID), eq(FIREWALL_RULE_ALLOW));
+
+ // Interface rules are not changed by Lockdown mode enable/disable
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ verify(mBpfNetMaps, never()).removeUidInterfaceRules(any());
+ }
+
/**
* Test mutable and requestable network capabilities such as
* {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and
@@ -10333,7 +10416,7 @@
verify(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
- assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
+ assertTrue(mService.mPermissionMonitor.getVpnInterfaceUidRanges("tun0").equals(vpnRange));
mMockVpn.disconnect();
waitForIdle();
@@ -10341,11 +10424,11 @@
// Disconnected VPN should have interface rules removed
verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
+ assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges("tun0"));
}
@Test
- public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
+ public void testLegacyVpnInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
@@ -10355,13 +10438,34 @@
mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
- // Legacy VPN should not have interface rules set up
- verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ if (SdkLevel.isAtLeastT()) {
+ // On T and above, A connected Legacy VPN should have interface rules with null
+ // interface. Null Interface is a wildcard and this accepts traffic from all the
+ // interfaces. There are two expected invocations, one during the VPN initial
+ // connection, one during the VPN LinkProperties update.
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
+ eq(null) /* iface */, uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
+ assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
+ vpnRange);
+
+ mMockVpn.disconnect();
+ waitForIdle();
+
+ // Disconnected VPN should have interface rules removed
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
+ assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ } else {
+ // Before T, Legacy VPN should not have interface rules.
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ }
}
@Test
- public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule()
- throws Exception {
+ public void testLocalIpv4OnlyVpnInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0"));
@@ -10371,8 +10475,31 @@
mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
- // IPv6 unreachable route should not be misinterpreted as a default route
- verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ if (SdkLevel.isAtLeastT()) {
+ // IPv6 unreachable route should not be misinterpreted as a default route
+ // On T and above, A connected VPN that does not provide a default route should have
+ // interface rules with null interface. Null Interface is a wildcard and this accepts
+ // traffic from all the interfaces. There are two expected invocations, one during the
+ // VPN initial connection, one during the VPN LinkProperties update.
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
+ eq(null) /* iface */, uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
+ assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
+ vpnRange);
+
+ mMockVpn.disconnect();
+ waitForIdle();
+
+ // Disconnected VPN should have interface rules removed
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
+ assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ } else {
+ // Before T, VPN with IPv6 unreachable route should not have interface rules.
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ }
}
@Test
@@ -11937,16 +12064,14 @@
mQosCallbackMockHelper.registerQosCallback(
mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
- final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 =
- (NetworkAgentWrapper.CallbackType.OnQosCallbackRegister)
- wrapper.getCallbackHistory().poll(1000, x -> true);
+ final OnQosCallbackRegister cbRegister1 =
+ (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
assertNotNull(cbRegister1);
final int registerCallbackId = cbRegister1.mQosCallbackId;
mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback);
- final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
- cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
- wrapper.getCallbackHistory().poll(1000, x -> true);
+ final OnQosCallbackUnregister cbUnregister =
+ (OnQosCallbackUnregister) wrapper.getCallbackHistory().poll(1000, x -> true);
assertNotNull(cbUnregister);
assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
assertNull(wrapper.getCallbackHistory().poll(200, x -> true));
@@ -12025,6 +12150,86 @@
&& session.getSessionType() == QosSession.TYPE_NR_BEARER));
}
+ @Test @IgnoreUpTo(SC_V2)
+ public void testQosCallbackAvailableOnValidationError() throws Exception {
+ mQosCallbackMockHelper = new QosCallbackMockHelper();
+ final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper;
+ final int sessionId = 10;
+ final int qosCallbackId = 1;
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+ mQosCallbackMockHelper.registerQosCallback(
+ mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+ OnQosCallbackRegister cbRegister1 =
+ (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
+ assertNotNull(cbRegister1);
+ final int registerCallbackId = cbRegister1.mQosCallbackId;
+
+ waitForIdle();
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+ final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
+ 1, 2, 3, 4, 5, new ArrayList<>());
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+ waitForIdle();
+
+ final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
+ cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
+ wrapper.getCallbackHistory().poll(1000, x -> true);
+ assertNotNull(cbUnregister);
+ assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
+ waitForIdle();
+ verify(mQosCallbackMockHelper.mCallback)
+ .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testQosCallbackLostOnValidationError() throws Exception {
+ mQosCallbackMockHelper = new QosCallbackMockHelper();
+ final int sessionId = 10;
+ final int qosCallbackId = 1;
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+ mQosCallbackMockHelper.registerQosCallback(
+ mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+ waitForIdle();
+ EpsBearerQosSessionAttributes attributes =
+ sendQosSessionEvent(qosCallbackId, sessionId, true);
+ waitForIdle();
+
+ verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session ->
+ session.getSessionId() == sessionId
+ && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes));
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+
+ sendQosSessionEvent(qosCallbackId, sessionId, false);
+ waitForIdle();
+ verify(mQosCallbackMockHelper.mCallback)
+ .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
+ }
+
+ private EpsBearerQosSessionAttributes sendQosSessionEvent(
+ int qosCallbackId, int sessionId, boolean available) {
+ if (available) {
+ final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
+ 1, 2, 3, 4, 5, new ArrayList<>());
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+ return attributes;
+ } else {
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER);
+ return null;
+ }
+
+ }
+
@Test
public void testQosCallbackTooManyRequests() throws Exception {
mQosCallbackMockHelper = new QosCallbackMockHelper();
@@ -14548,7 +14753,7 @@
profileNetworkPreferenceBuilder.setPreference(
PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(
- NetworkCapabilities.NET_ENTERPRISE_ID_2);
+ NET_ENTERPRISE_ID_2);
registerDefaultNetworkCallbacks();
testPreferenceForUserNetworkUpDownForGivenPreference(
profileNetworkPreferenceBuilder.build(), true,
@@ -14573,6 +14778,393 @@
}
/**
+ * Make sure per-profile networking preference throws exception when default preference
+ * is set along with enterprise preference.
+ */
+ @Test
+ public void testPreferenceWithInvalidPreferenceDefaultAndEnterpriseTogether()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ mServiceContext.setWorkProfile(testHandle, true);
+
+ final int testWorkProfileAppUid1 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder1.build(),
+ profileNetworkPreferenceBuilder2.build()),
+ r -> r.run(), listener));
+ Assert.assertThrows(IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreferences(
+ testHandle, List.of(profileNetworkPreferenceBuilder2.build(),
+ profileNetworkPreferenceBuilder1.build()),
+ r -> r.run(), listener));
+ }
+
+ /**
+ * Make sure per profile network preferences behave as expected when two slices with
+ * two different apps within same user profile is configured
+ * Make sure per profile network preferences overrides with latest preference when
+ * same user preference is set twice
+ */
+ @Test
+ public void testSetPreferenceWithOverridingPreference()
+ throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ mServiceContext.setWorkProfile(testHandle, true);
+ registerDefaultNetworkCallbacks();
+
+ final TestNetworkCallback appCb1 = new TestNetworkCallback();
+ final TestNetworkCallback appCb2 = new TestNetworkCallback();
+ final TestNetworkCallback appCb3 = new TestNetworkCallback();
+
+ final int testWorkProfileAppUid1 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+ final int testWorkProfileAppUid2 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_2);
+ final int testWorkProfileAppUid3 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_3);
+
+ registerDefaultNetworkCallbackAsUid(appCb1, testWorkProfileAppUid1);
+ registerDefaultNetworkCallbackAsUid(appCb2, testWorkProfileAppUid2);
+ registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);
+
+ // Connect both a regular cell agent and an enterprise network first.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
+ final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
+ workAgent1.connect(true);
+ workAgent2.connect(true);
+
+ mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+
+ // Set preferences for testHandle to map testWorkProfileAppUid1 to
+ // NET_ENTERPRISE_ID_1 and testWorkProfileAppUid2 to NET_ENTERPRISE_ID_2.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_2);
+ profileNetworkPreferenceBuilder2.setIncludedUids(new int[]{testWorkProfileAppUid2});
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder1.build(),
+ profileNetworkPreferenceBuilder2.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ appCb1.expectAvailableCallbacksValidated(workAgent1);
+ appCb2.expectAvailableCallbacksValidated(workAgent2);
+
+ // Set preferences for testHandle to map testWorkProfileAppUid3 to
+ // to NET_ENTERPRISE_ID_1.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder3 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder3.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder3.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder3.setIncludedUids(new int[]{testWorkProfileAppUid3});
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder3.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ appCb3.expectAvailableCallbacksValidated(workAgent1);
+ appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+ // Set the preferences for testHandle to default.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb2);
+ appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ workAgent2.disconnect();
+ mCellNetworkAgent.disconnect();
+
+ mCm.unregisterNetworkCallback(appCb1);
+ mCm.unregisterNetworkCallback(appCb2);
+ mCm.unregisterNetworkCallback(appCb3);
+ // Other callbacks will be unregistered by tearDown()
+ }
+
+ /**
+ * Make sure per profile network preferences behave as expected when multiple slices with
+ * multiple different apps within same user profile is configured.
+ */
+ @Test
+ public void testSetPreferenceWithMultiplePreferences()
+ throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ mServiceContext.setWorkProfile(testHandle, true);
+ registerDefaultNetworkCallbacks();
+
+ final TestNetworkCallback appCb1 = new TestNetworkCallback();
+ final TestNetworkCallback appCb2 = new TestNetworkCallback();
+ final TestNetworkCallback appCb3 = new TestNetworkCallback();
+ final TestNetworkCallback appCb4 = new TestNetworkCallback();
+ final TestNetworkCallback appCb5 = new TestNetworkCallback();
+
+ final int testWorkProfileAppUid1 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+ final int testWorkProfileAppUid2 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_2);
+ final int testWorkProfileAppUid3 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_3);
+ final int testWorkProfileAppUid4 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_4);
+ final int testWorkProfileAppUid5 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_5);
+
+ registerDefaultNetworkCallbackAsUid(appCb1, testWorkProfileAppUid1);
+ registerDefaultNetworkCallbackAsUid(appCb2, testWorkProfileAppUid2);
+ registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);
+ registerDefaultNetworkCallbackAsUid(appCb4, testWorkProfileAppUid4);
+ registerDefaultNetworkCallbackAsUid(appCb5, testWorkProfileAppUid5);
+
+ // Connect both a regular cell agent and an enterprise network first.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
+ final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
+ final TestNetworkAgentWrapper workAgent3 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_3);
+ final TestNetworkAgentWrapper workAgent4 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_4);
+ final TestNetworkAgentWrapper workAgent5 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_5);
+
+ workAgent1.connect(true);
+ workAgent2.connect(true);
+ workAgent3.connect(true);
+ workAgent4.connect(true);
+ workAgent5.connect(true);
+
+ mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb4.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb5.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent3.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent4.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent5.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder2.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_2);
+ profileNetworkPreferenceBuilder2.setIncludedUids(new int[]{testWorkProfileAppUid2});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder3 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder3.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder3.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_3);
+ profileNetworkPreferenceBuilder3.setIncludedUids(new int[]{testWorkProfileAppUid3});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder4 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder4.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder4.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_4);
+ profileNetworkPreferenceBuilder4.setIncludedUids(new int[]{testWorkProfileAppUid4});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder5 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder5.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder5.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_5);
+ profileNetworkPreferenceBuilder5.setIncludedUids(new int[]{testWorkProfileAppUid5});
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder1.build(),
+ profileNetworkPreferenceBuilder2.build(),
+ profileNetworkPreferenceBuilder3.build(),
+ profileNetworkPreferenceBuilder4.build(),
+ profileNetworkPreferenceBuilder5.build()),
+ r -> r.run(), listener);
+
+ listener.expectOnComplete();
+
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent3.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent4.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder4.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent5.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder5.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ appCb1.expectAvailableCallbacksValidated(workAgent1);
+ appCb2.expectAvailableCallbacksValidated(workAgent2);
+ appCb3.expectAvailableCallbacksValidated(workAgent3);
+ appCb4.expectAvailableCallbacksValidated(workAgent4);
+ appCb5.expectAvailableCallbacksValidated(workAgent5);
+
+ workAgent1.disconnect();
+ workAgent2.disconnect();
+ workAgent3.disconnect();
+ workAgent4.disconnect();
+ workAgent5.disconnect();
+
+ appCb1.expectCallback(CallbackEntry.LOST, workAgent1);
+ appCb2.expectCallback(CallbackEntry.LOST, workAgent2);
+ appCb3.expectCallback(CallbackEntry.LOST, workAgent3);
+ appCb4.expectCallback(CallbackEntry.LOST, workAgent4);
+ appCb5.expectCallback(CallbackEntry.LOST, workAgent5);
+
+ appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ appCb2.assertNoCallback();
+ appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ appCb4.assertNoCallback();
+ appCb5.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder4.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder5.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ mSystemDefaultNetworkCallback.assertNoCallback();
+ mDefaultNetworkCallback.assertNoCallback();
+
+ // Set the preferences for testHandle to default.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb3,
+ appCb5);
+ appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ appCb4.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ mCellNetworkAgent.disconnect();
+
+ mCm.unregisterNetworkCallback(appCb1);
+ mCm.unregisterNetworkCallback(appCb2);
+ mCm.unregisterNetworkCallback(appCb3);
+ mCm.unregisterNetworkCallback(appCb4);
+ mCm.unregisterNetworkCallback(appCb5);
+ // Other callbacks will be unregistered by tearDown()
+ }
+
+ /**
* Test that, in a given networking context, calling setPreferenceForUser to set per-profile
* defaults on then off works as expected.
*/
@@ -14937,6 +15529,27 @@
}
@Test
+ public void testAutomotiveEthernetAllowedUids() throws Exception {
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+
+ // In this test the automotive feature will be enabled.
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+
+ // Simulate a restricted ethernet network.
+ final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_ETHERNET)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+
+ mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET,
+ new LinkProperties(), agentNetCaps.build());
+ validateAllowedUids(mEthernetNetworkAgent, TRANSPORT_ETHERNET, agentNetCaps, true);
+ }
+
+ @Test
public void testCbsAllowedUids() throws Exception {
mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
@@ -14945,6 +15558,24 @@
doReturn(true).when(mCarrierPrivilegeAuthenticator)
.hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
+ // Simulate a restricted telephony network. The telephony factory is entitled to set
+ // the access UID to the service package on any of its restricted networks.
+ final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+ new LinkProperties(), agentNetCaps.build());
+ validateAllowedUids(mCellNetworkAgent, TRANSPORT_CELLULAR, agentNetCaps, false);
+ }
+
+ private void validateAllowedUids(final TestNetworkAgentWrapper testAgent,
+ @NetworkCapabilities.Transport final int transportUnderTest,
+ final NetworkCapabilities.Builder ncb, final boolean forAutomotive) throws Exception {
final ArraySet<Integer> serviceUidSet = new ArraySet<>();
serviceUidSet.add(TEST_PACKAGE_UID);
final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
@@ -14955,40 +15586,34 @@
final TestNetworkCallback cb = new TestNetworkCallback();
- // Simulate a restricted telephony network. The telephony factory is entitled to set
- // the access UID to the service package on any of its restricted networks.
- final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
- .addCapability(NET_CAPABILITY_INTERNET)
- .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
-
+ /* Test setting UIDs */
// Cell gets to set the service UID as access UID
mCm.requestNetwork(new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
+ .addTransportType(transportUnderTest)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.build(), cb);
- mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
- new LinkProperties(), ncb.build());
- mCellNetworkAgent.connect(true);
- cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ testAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(testAgent);
ncb.setAllowedUids(serviceUidSet);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(mCellNetworkAgent,
+ cb.expectCapabilitiesThat(testAgent,
caps -> caps.getAllowedUids().equals(serviceUidSet));
} else {
// S must ignore access UIDs.
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
}
+ /* Test setting UIDs is rejected when expected */
+ if (forAutomotive) {
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+ }
+
// ...but not to some other UID. Rejection sets UIDs to the empty set
ncb.setAllowedUids(nonServiceUidSet);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(mCellNetworkAgent,
+ cb.expectCapabilitiesThat(testAgent,
caps -> caps.getAllowedUids().isEmpty());
} else {
// S must ignore access UIDs.
@@ -14997,18 +15622,18 @@
// ...and also not to multiple UIDs even including the service UID
ncb.setAllowedUids(serviceUidSetPlus);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
- mCellNetworkAgent.disconnect();
- cb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ testAgent.disconnect();
+ cb.expectCallback(CallbackEntry.LOST, testAgent);
mCm.unregisterNetworkCallback(cb);
// Must be unset before touching the transports, because remove and add transport types
// check the specifier on the builder immediately, contradicting normal builder semantics
// TODO : fix the builder
ncb.setNetworkSpecifier(null);
- ncb.removeTransportType(TRANSPORT_CELLULAR);
+ ncb.removeTransportType(transportUnderTest);
ncb.addTransportType(TRANSPORT_WIFI);
// Wifi does not get to set access UID, even to the correct UID
mCm.requestNetwork(new NetworkRequest.Builder()
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
index 45f3d3c..061dc87 100644
--- a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -783,6 +783,23 @@
}
@Test
+ public void testSetNetworkForTunnelInterfaceFailsForNullLp() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final Network newFakeNetwork = new Network(1000);
+ final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+
+ try {
+ mIpSecService.setNetworkForTunnelInterface(
+ tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+ fail(
+ "Expected an IllegalArgumentException for underlying network with null"
+ + " LinkProperties");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
final IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index fb821c3..ecd17ba 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -30,6 +30,9 @@
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetd.PERMISSION_NETWORK;
@@ -761,8 +764,8 @@
MOCK_APPID1);
}
- @Test
- public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
+ private void doTestuidFilteringDuringVpnConnectDisconnectAndUidUpdates(@Nullable String ifName)
+ throws Exception {
doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
@@ -778,8 +781,8 @@
final Set<UidRange> vpnRange2 = Set.of(new UidRange(MOCK_UID12, MOCK_UID12));
// When VPN is connected, expect a rule to be set up for user app MOCK_UID11
- mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+ mPermissionMonitor.onVpnUidRangesAdded(ifName, vpnRange1, VPN_UID);
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID11}));
reset(mBpfNetMaps);
@@ -787,27 +790,38 @@
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, MOCK_UID11);
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID11}));
reset(mBpfNetMaps);
// During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
// old UID rules then adds the new ones. Expect netd to be updated
- mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
+ mPermissionMonitor.onVpnUidRangesRemoved(ifName, vpnRange1, VPN_UID);
verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID11}));
- mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID12}));
+ mPermissionMonitor.onVpnUidRangesAdded(ifName, vpnRange2, VPN_UID);
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID12}));
reset(mBpfNetMaps);
// When VPN is disconnected, expect rules to be torn down
- mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
+ mPermissionMonitor.onVpnUidRangesRemoved(ifName, vpnRange2, VPN_UID);
verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID12}));
- assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
+ assertNull(mPermissionMonitor.getVpnInterfaceUidRanges(ifName));
}
@Test
- public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
+ public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
+ doTestuidFilteringDuringVpnConnectDisconnectAndUidUpdates("tun0");
+ }
+
+ @Test
+ public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdatesWithWildcard()
+ throws Exception {
+ doTestuidFilteringDuringVpnConnectDisconnectAndUidUpdates(null /* ifName */);
+ }
+
+ private void doTestUidFilteringDuringPackageInstallAndUninstall(@Nullable String ifName) throws
+ Exception {
doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
@@ -818,12 +832,12 @@
mPermissionMonitor.startMonitoring();
final Set<UidRange> vpnRange = Set.of(UidRange.createForUser(MOCK_USER1),
UidRange.createForUser(MOCK_USER2));
- mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange, VPN_UID);
+ mPermissionMonitor.onVpnUidRangesAdded(ifName, vpnRange, VPN_UID);
// Newly-installed package should have uid rules added
addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_APPID1);
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
- verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID21}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID11}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq(ifName), aryEq(new int[]{MOCK_UID21}));
// Removed package should have its uid rules removed
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
@@ -831,6 +845,168 @@
verify(mBpfNetMaps, never()).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID21}));
}
+ @Test
+ public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
+ doTestUidFilteringDuringPackageInstallAndUninstall("tun0");
+ }
+
+ @Test
+ public void testUidFilteringDuringPackageInstallAndUninstallWithWildcard() throws Exception {
+ doTestUidFilteringDuringPackageInstallAndUninstall(null /* ifName */);
+ }
+
+ @Test
+ public void testLockdownUidFilteringWithLockdownEnableDisable() {
+ doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
+ buildPackageInfo(MOCK_PACKAGE2, MOCK_UID12),
+ buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
+ .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
+ mPermissionMonitor.startMonitoring();
+ // Every app on user 0 except MOCK_UID12 are under VPN.
+ final UidRange[] vpnRange1 = {
+ new UidRange(0, MOCK_UID12 - 1),
+ new UidRange(MOCK_UID12 + 1, UserHandle.PER_USER_RANGE - 1)
+ };
+
+ // Add Lockdown uid range, expect a rule to be set up for user app MOCK_UID11
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange1);
+ verify(mBpfNetMaps)
+ .setUidRule(
+ eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_DENY));
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange1));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range, expect rules to be torn down
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange1);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_ALLOW));
+ assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
+ }
+
+ @Test
+ public void testLockdownUidFilteringWithLockdownEnableDisableWithMultiAdd() {
+ doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
+ buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
+ .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
+ mPermissionMonitor.startMonitoring();
+ // MOCK_UID11 is under VPN.
+ final UidRange range = new UidRange(MOCK_UID11, MOCK_UID11);
+ final UidRange[] vpnRange = {range};
+
+ // Add Lockdown uid range at 1st time, expect a rule to be set up
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_DENY));
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Add Lockdown uid range at 2nd time, expect a rule not to be set up because the uid
+ // already has the rule
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange);
+ verify(mBpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt());
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range at 1st time, expect a rule not to be torn down because we added
+ // the range 2 times.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
+ verify(mBpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt());
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range at 2nd time, expect a rule to be torn down because we added
+ // twice and we removed twice.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_ALLOW));
+ assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
+ }
+
+ @Test
+ public void testLockdownUidFilteringWithLockdownEnableDisableWithDuplicates() {
+ doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
+ buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
+ .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
+ mPermissionMonitor.startMonitoring();
+ // MOCK_UID11 is under VPN.
+ final UidRange range = new UidRange(MOCK_UID11, MOCK_UID11);
+ final UidRange[] vpnRangeDuplicates = {range, range};
+ final UidRange[] vpnRange = {range};
+
+ // Add Lockdown uid ranges which contains duplicated uid ranges
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRangeDuplicates);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_DENY));
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range at 1st time, expect a rule not to be torn down because uid
+ // ranges we added contains duplicated uid ranges.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
+ verify(mBpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt());
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+
+ reset(mBpfNetMaps);
+
+ // Remove Lockdown uid range at 2nd time, expect a rule to be torn down.
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_ALLOW));
+ assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
+ }
+
+ @Test
+ public void testLockdownUidFilteringWithInstallAndUnInstall() {
+ doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+ buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
+ .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
+ doReturn(List.of(MOCK_USER1, MOCK_USER2)).when(mUserManager).getUserHandles(eq(true));
+
+ mPermissionMonitor.startMonitoring();
+ final UidRange[] vpnRange = {
+ UidRange.createForUser(MOCK_USER1),
+ UidRange.createForUser(MOCK_USER2)
+ };
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange);
+
+ // Installing package should add Lockdown rules
+ addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_APPID1);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_DENY));
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID21),
+ eq(FIREWALL_RULE_DENY));
+
+ reset(mBpfNetMaps);
+
+ // Uninstalling package should remove Lockdown rules
+ mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
+ verify(mBpfNetMaps)
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
+ eq(FIREWALL_RULE_ALLOW));
+ verify(mBpfNetMaps, never())
+ .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID21),
+ eq(FIREWALL_RULE_ALLOW));
+ }
// Normal package add/remove operations will trigger multiple intent for uids corresponding to
// each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 6da4f07..11fbcb9 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -28,9 +28,11 @@
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
import static android.net.VpnManager.TYPE_VPN_PLATFORM;
+import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.UserHandle.PER_USER_RANGE;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertArrayEquals;
@@ -86,8 +88,11 @@
import android.net.LinkProperties;
import android.net.LocalSocket;
import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkProvider;
import android.net.RouteInfo;
import android.net.UidRangeParcel;
import android.net.VpnManager;
@@ -104,6 +109,7 @@
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.INetworkManagementService;
+import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.UserHandle;
@@ -121,13 +127,16 @@
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
+import com.android.internal.util.HexDump;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.IpSecService;
+import com.android.server.vcn.util.PersistableBundleUtils;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers;
@@ -164,10 +173,13 @@
*/
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
+@IgnoreUpTo(VERSION_CODES.S_V2)
public class VpnTest {
private static final String TAG = "VpnTest";
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
// Mock users
static final UserInfo primaryUser = new UserInfo(27, "Primary", FLAG_ADMIN | FLAG_PRIMARY);
static final UserInfo secondaryUser = new UserInfo(15, "Secondary", FLAG_ADMIN);
@@ -191,14 +203,16 @@
private static final String TEST_IFACE_NAME = "TEST_IFACE";
private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
private static final long TEST_TIMEOUT_MS = 500L;
-
+ private static final String PRIMARY_USER_APP_EXCLUDE_KEY =
+ "VPN_APP_EXCLUDED_27_com.testvpn.vpn";
/**
* Names and UIDs for some fake packages. Important points:
* - UID is ordered increasing.
* - One pair of packages have consecutive UIDs.
*/
static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"};
- static final int[] PKG_UIDS = {66, 77, 78, 400};
+ static final String PKGS_BYTES = getPackageByteString(List.of(PKGS));
+ static final int[] PKG_UIDS = {10066, 10077, 10078, 10400};
// Mock packages
static final Map<String, Integer> mPackages = new ArrayMap<>();
@@ -311,6 +325,17 @@
return new Range<Integer>(start, stop);
}
+ private static String getPackageByteString(List<String> packages) {
+ try {
+ return HexDump.toHexString(
+ PersistableBundleUtils.toDiskStableBytes(PersistableBundleUtils.fromList(
+ packages, PersistableBundleUtils.STRING_SERIALIZER)),
+ true /* upperCase */);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
@Test
public void testRestrictedProfilesAreAddedToVpn() {
setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
@@ -360,7 +385,11 @@
Arrays.asList(packages), null /* disallowedApplications */);
assertEquals(rangeSet(
uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]),
- uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2])),
+ uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2]),
+ uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0]),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0])),
+ uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[1]),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[2]))),
allow);
// Denied list
@@ -371,7 +400,11 @@
uidRange(userStart, userStart + PKG_UIDS[0] - 1),
uidRange(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
/* Empty range between UIDS[1] and UIDS[2], should be excluded, */
- uidRange(userStart + PKG_UIDS[2] + 1, userStop)),
+ uidRange(userStart + PKG_UIDS[2] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)),
disallow);
}
@@ -412,18 +445,24 @@
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
// Switch to another app.
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[3] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
}));
}
@@ -438,17 +477,25 @@
PKGS[1], true, Collections.singletonList(PKGS[2])));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1]) - 1),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
}));
// Change allowed app list to PKGS[3].
assertTrue(vpn.setAlwaysOnPackage(
PKGS[1], true, Collections.singletonList(PKGS[3])));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
- new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
}));
// Change the VPN app.
@@ -456,32 +503,52 @@
PKGS[0], true, Collections.singletonList(PKGS[3])));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[0] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1)
+ new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
}));
// Remove the list of allowed packages.
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
- new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop),
+ new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
}));
// Add the list of allowed packages.
assertTrue(vpn.setAlwaysOnPackage(
PKGS[0], true, Collections.singletonList(PKGS[1])));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
- new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
// Try allowing a package with a comma, should be rejected.
@@ -494,11 +561,19 @@
PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[2] - 1),
- new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+ new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+ Process.toSdkSandboxUid(userStart + PKG_UIDS[2] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
}));
}
@@ -543,7 +618,10 @@
};
final UidRangeParcel[] exceptPkg0 = {
new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
- new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
+ new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1,
+ Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] - 1)),
+ new UidRangeParcel(Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] + 1),
+ entireUser[0].stop),
};
final InOrder order = inOrder(mConnectivityManager);
@@ -711,6 +789,47 @@
}
}
+ private Vpn prepareVpnForVerifyAppExclusionList() throws Exception {
+ final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+ when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+ when(mVpnProfileStore.get(PRIMARY_USER_APP_EXCLUDE_KEY))
+ .thenReturn(HexDump.hexStringToByteArray(PKGS_BYTES));
+
+ vpn.startVpnProfile(TEST_VPN_PKG);
+ verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+ vpn.mNetworkAgent = new NetworkAgent(mContext, Looper.getMainLooper(), TAG,
+ new NetworkCapabilities.Builder().build(), new LinkProperties(), 10 /* score */,
+ new NetworkAgentConfig.Builder().build(),
+ new NetworkProvider(mContext, Looper.getMainLooper(), TAG)) {};
+ return vpn;
+ }
+
+ @Test @IgnoreUpTo(S_V2)
+ public void testSetAndGetAppExclusionList() throws Exception {
+ final Vpn vpn = prepareVpnForVerifyAppExclusionList();
+ verify(mVpnProfileStore, never()).put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), any());
+ vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS));
+ verify(mVpnProfileStore)
+ .put(eq(PRIMARY_USER_APP_EXCLUDE_KEY),
+ eq(HexDump.hexStringToByteArray(PKGS_BYTES)));
+ assertEquals(vpn.createUserAndRestrictedProfilesRanges(
+ primaryUser.id, null, Arrays.asList(PKGS)),
+ vpn.mNetworkCapabilities.getUids());
+ assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
+ }
+
+ @Test @IgnoreUpTo(S_V2)
+ public void testSetAndGetAppExclusionListRestrictedUser() throws Exception {
+ final Vpn vpn = prepareVpnForVerifyAppExclusionList();
+ // Mock it to restricted profile
+ when(mUserManager.getUserInfo(anyInt())).thenReturn(restrictedProfileA);
+ // Restricted users cannot configure VPNs
+ assertThrows(SecurityException.class,
+ () -> vpn.setAppExclusionList(TEST_VPN_PKG, new ArrayList<>()));
+ assertThrows(SecurityException.class, () -> vpn.getAppExclusionList(TEST_VPN_PKG));
+ }
+
@Test
public void testProvisionVpnProfilePreconsented() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
new file mode 100644
index 0000000..a9f80ea
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.server.ethernet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.InetAddresses;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.util.ArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class EthernetConfigStoreTest {
+ private static final LinkAddress LINKADDR = new LinkAddress("192.168.1.100/25");
+ private static final InetAddress GATEWAY = InetAddresses.parseNumericAddress("192.168.1.1");
+ private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("8.8.8.8");
+ private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("8.8.4.4");
+ private static final StaticIpConfiguration STATIC_IP_CONFIG =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(LINKADDR)
+ .setGateway(GATEWAY)
+ .setDnsServers(new ArrayList<InetAddress>(
+ List.of(DNS1, DNS2)))
+ .build();
+ private static final ProxyInfo PROXY_INFO = ProxyInfo.buildDirectProxy("test", 8888);
+ private static final IpConfiguration APEX_IP_CONFIG =
+ new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
+ private static final IpConfiguration LEGACY_IP_CONFIG =
+ new IpConfiguration(IpAssignment.STATIC, ProxySettings.STATIC, STATIC_IP_CONFIG,
+ PROXY_INFO);
+
+ private EthernetConfigStore mEthernetConfigStore;
+ private File mApexTestDir;
+ private File mLegacyTestDir;
+ private File mApexConfigFile;
+ private File mLegacyConfigFile;
+
+ private void createTestDir() {
+ final Context context = InstrumentationRegistry.getContext();
+ final File baseDir = context.getFilesDir();
+ mApexTestDir = new File(baseDir.getPath() + "/apex");
+ mApexTestDir.mkdirs();
+
+ mLegacyTestDir = new File(baseDir.getPath() + "/legacy");
+ mLegacyTestDir.mkdirs();
+ }
+
+ @Before
+ public void setUp() {
+ createTestDir();
+ mEthernetConfigStore = new EthernetConfigStore();
+ }
+
+ @After
+ public void tearDown() {
+ mApexTestDir.delete();
+ mLegacyTestDir.delete();
+ }
+
+ private void assertConfigFileExist(final String filepath) {
+ assertTrue(new File(filepath).exists());
+ }
+
+ /** Wait for the delayed write operation completes. */
+ private void waitForMs(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (final InterruptedException e) {
+ fail("Thread was interrupted");
+ }
+ }
+
+ @Test
+ public void testWriteIpConfigToApexFilePathAndRead() throws Exception {
+ // Write the config file to the apex file path, pretend the config file exits and
+ // check if IP config should be read from apex file path.
+ mApexConfigFile = new File(mApexTestDir.getPath(), "test.txt");
+ mEthernetConfigStore.write("eth0", APEX_IP_CONFIG, mApexConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(APEX_IP_CONFIG, ipConfigurations.get("eth0"));
+
+ mApexConfigFile.delete();
+ }
+
+ @Test
+ public void testWriteIpConfigToLegacyFilePathAndRead() throws Exception {
+ // Write the config file to the legacy file path, pretend the config file exits and
+ // check if IP config should be read from legacy file path.
+ mLegacyConfigFile = new File(mLegacyTestDir, "test.txt");
+ mEthernetConfigStore.write("0", LEGACY_IP_CONFIG, mLegacyConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(LEGACY_IP_CONFIG, ipConfigurations.get("0"));
+
+ // Check the same config file in apex file path is created.
+ assertConfigFileExist(mApexTestDir.getPath() + "/test.txt");
+
+ final File apexConfigFile = new File(mApexTestDir.getPath() + "/test.txt");
+ apexConfigFile.delete();
+ mLegacyConfigFile.delete();
+ }
+}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index dfb4fcc..568d40f 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -32,7 +32,6 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -51,6 +50,7 @@
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
import android.net.NetworkRequest;
import android.net.StaticIpConfiguration;
import android.net.ip.IpClientCallbacks;
@@ -99,6 +99,7 @@
@Mock private EthernetNetworkAgent mNetworkAgent;
@Mock private InterfaceParams mInterfaceParams;
@Mock private Network mMockNetwork;
+ @Mock private NetworkProvider mNetworkProvider;
@Before
public void setUp() throws Exception {
@@ -112,7 +113,7 @@
private void initEthernetNetworkFactory() {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mDeps);
+ mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mNetworkProvider, mDeps);
}
private void setupNetworkAgentMock() {
@@ -239,9 +240,16 @@
mNetFactory.addInterface(iface, HW_ADDR, ipConfig,
createInterfaceCapsBuilder(transportType).build());
assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
+
+ ArgumentCaptor<NetworkOfferCallback> captor = ArgumentCaptor.forClass(
+ NetworkOfferCallback.class);
+ verify(mNetworkProvider).registerNetworkOffer(any(), any(), any(), captor.capture());
+ captor.getValue().onNetworkNeeded(createDefaultRequest());
+
verifyStart(ipConfig);
clearInvocations(mDeps);
clearInvocations(mIpClient);
+ clearInvocations(mNetworkProvider);
}
// creates a provisioned interface
@@ -281,29 +289,15 @@
// To create an unprovisioned interface, provision and then "stop" it, i.e. stop its
// NetworkAgent and IpClient. One way this can be done is by provisioning an interface and
// then calling onNetworkUnwanted.
- createAndVerifyProvisionedInterface(iface);
-
- mNetworkAgent.getCallbacks().onNetworkUnwanted();
- mLooper.dispatchAll();
- verifyStop();
+ mNetFactory.addInterface(iface, HW_ADDR, createDefaultIpConfig(),
+ createInterfaceCapsBuilder(NetworkCapabilities.TRANSPORT_ETHERNET).build());
+ assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
clearInvocations(mIpClient);
clearInvocations(mNetworkAgent);
}
@Test
- public void testAcceptRequest() throws Exception {
- initEthernetNetworkFactory();
- createInterfaceUndergoingProvisioning(TEST_IFACE);
- assertTrue(mNetFactory.acceptRequest(createDefaultRequest()));
-
- NetworkRequest wifiRequest = createDefaultRequestBuilder()
- .removeTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
- assertFalse(mNetFactory.acceptRequest(wifiRequest));
- }
-
- @Test
public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception {
initEthernetNetworkFactory();
createInterfaceUndergoingProvisioning(TEST_IFACE);
@@ -378,36 +372,6 @@
}
@Test
- public void testNeedNetworkForOnProvisionedInterface() throws Exception {
- initEthernetNetworkFactory();
- createAndVerifyProvisionedInterface(TEST_IFACE);
- mNetFactory.needNetworkFor(createDefaultRequest());
- verify(mIpClient, never()).startProvisioning(any());
- }
-
- @Test
- public void testNeedNetworkForOnUnprovisionedInterface() throws Exception {
- initEthernetNetworkFactory();
- createUnprovisionedInterface(TEST_IFACE);
- mNetFactory.needNetworkFor(createDefaultRequest());
- verify(mIpClient).startProvisioning(any());
-
- triggerOnProvisioningSuccess();
- verifyNetworkAgentRegistersAndConnects();
- }
-
- @Test
- public void testNeedNetworkForOnInterfaceUndergoingProvisioning() throws Exception {
- initEthernetNetworkFactory();
- createInterfaceUndergoingProvisioning(TEST_IFACE);
- mNetFactory.needNetworkFor(createDefaultRequest());
- verify(mIpClient, never()).startProvisioning(any());
-
- triggerOnProvisioningSuccess();
- verifyNetworkAgentRegistersAndConnects();
- }
-
- @Test
public void testProvisioningLoss() throws Exception {
initEthernetNetworkFactory();
when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
@@ -441,31 +405,6 @@
}
@Test
- public void testIpClientIsNotStartedWhenLinkIsDown() throws Exception {
- initEthernetNetworkFactory();
- createUnprovisionedInterface(TEST_IFACE);
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER);
-
- mNetFactory.needNetworkFor(createDefaultRequest());
-
- verify(mDeps, never()).makeIpClient(any(), any(), any());
-
- // BUG(b/191854824): requesting a network with a specifier (Android Auto use case) should
- // not start an IpClient when the link is down, but fixing this may make matters worse by
- // tiggering b/197548738.
- NetworkRequest specificNetRequest = new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
- .setNetworkSpecifier(new EthernetNetworkSpecifier(TEST_IFACE))
- .build();
- mNetFactory.needNetworkFor(specificNetRequest);
- mNetFactory.releaseNetworkFor(specificNetRequest);
-
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, true, NULL_LISTENER);
- // TODO: change to once when b/191854824 is fixed.
- verify(mDeps, times(2)).makeIpClient(any(), eq(TEST_IFACE), any());
- }
-
- @Test
public void testLinkPropertiesChanged() throws Exception {
initEthernetNetworkFactory();
createAndVerifyProvisionedInterface(TEST_IFACE);
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
index dd1f1ed..aff824c 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
-
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
@@ -35,8 +34,8 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.EthernetNetworkUpdateRequest;
+import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IpConfiguration;
import android.net.NetworkCapabilities;
import android.os.Handler;
@@ -111,18 +110,18 @@
}
@Test
- public void testConnectNetworkRejectsWhenEthNotStarted() {
+ public void testEnableInterfaceRejectsWhenEthNotStarted() {
mEthernetServiceImpl.mStarted.set(false);
assertThrows(IllegalStateException.class, () -> {
- mEthernetServiceImpl.connectNetwork("" /* iface */, null /* listener */);
+ mEthernetServiceImpl.enableInterface("" /* iface */, null /* listener */);
});
}
@Test
- public void testDisconnectNetworkRejectsWhenEthNotStarted() {
+ public void testDisableInterfaceRejectsWhenEthNotStarted() {
mEthernetServiceImpl.mStarted.set(false);
assertThrows(IllegalStateException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork("" /* iface */, null /* listener */);
+ mEthernetServiceImpl.disableInterface("" /* iface */, null /* listener */);
});
}
@@ -134,16 +133,16 @@
}
@Test
- public void testConnectNetworkRejectsNullIface() {
+ public void testEnableInterfaceRejectsNullIface() {
assertThrows(NullPointerException.class, () -> {
- mEthernetServiceImpl.connectNetwork(null /* iface */, NULL_LISTENER);
+ mEthernetServiceImpl.enableInterface(null /* iface */, NULL_LISTENER);
});
}
@Test
- public void testDisconnectNetworkRejectsNullIface() {
+ public void testDisableInterfaceRejectsNullIface() {
assertThrows(NullPointerException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork(null /* iface */, NULL_LISTENER);
+ mEthernetServiceImpl.disableInterface(null /* iface */, NULL_LISTENER);
});
}
@@ -165,22 +164,6 @@
eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getNetworkCapabilities()), isNull());
}
- @Test
- public void testConnectNetworkRejectsWithoutAutomotiveFeature() {
- toggleAutomotiveFeature(false);
- assertThrows(UnsupportedOperationException.class, () -> {
- mEthernetServiceImpl.connectNetwork("" /* iface */, NULL_LISTENER);
- });
- }
-
- @Test
- public void testDisconnectNetworkRejectsWithoutAutomotiveFeature() {
- toggleAutomotiveFeature(false);
- assertThrows(UnsupportedOperationException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork("" /* iface */, NULL_LISTENER);
- });
- }
-
private void denyManageEthPermission() {
doThrow(new SecurityException("")).when(mContext)
.enforceCallingOrSelfPermission(
@@ -202,18 +185,18 @@
}
@Test
- public void testConnectNetworkRejectsWithoutManageEthPermission() {
+ public void testEnableInterfaceRejectsWithoutManageEthPermission() {
denyManageEthPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@Test
- public void testDisconnectNetworkRejectsWithoutManageEthPermission() {
+ public void testDisableInterfaceRejectsWithoutManageEthPermission() {
denyManageEthPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@@ -231,20 +214,20 @@
}
@Test
- public void testConnectNetworkRejectsTestRequestWithoutTestPermission() {
+ public void testEnableInterfaceRejectsTestRequestWithoutTestPermission() {
enableTestInterface();
denyManageTestNetworksPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@Test
- public void testDisconnectNetworkRejectsTestRequestWithoutTestPermission() {
+ public void testDisableInterfaceRejectsTestRequestWithoutTestPermission() {
enableTestInterface();
denyManageTestNetworksPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@@ -258,15 +241,15 @@
}
@Test
- public void testConnectNetwork() {
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ public void testEnableInterface() {
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
@Test
- public void testDisconnectNetwork() {
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ public void testDisableInterface() {
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
@Test
@@ -324,23 +307,23 @@
}
@Test
- public void testConnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
+ public void testEnableInterfaceForTestRequestDoesNotRequireNetPermission() {
enableTestInterface();
toggleAutomotiveFeature(false);
denyManageEthPermission();
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
@Test
- public void testDisconnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
+ public void testDisableInterfaceForTestRequestDoesNotRequireAutoOrNetPermission() {
enableTestInterface();
toggleAutomotiveFeature(false);
denyManageEthPermission();
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
private void denyPermissions(String... permissions) {
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index b1831c4..ca988b6 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -29,22 +29,23 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.content.res.Resources;
import android.net.EthernetManager;
-import android.net.InetAddresses;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IEthernetServiceListener;
import android.net.INetd;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.InetAddresses;
+import android.net.InterfaceConfigurationParcel;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
-import android.net.InterfaceConfigurationParcel;
import android.net.LinkAddress;
import android.net.NetworkCapabilities;
import android.net.StaticIpConfiguration;
@@ -54,13 +55,13 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.connectivity.resources.R;
import com.android.testutils.HandlerUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -351,8 +352,8 @@
}
@Test
- public void testConnectNetworkCorrectlyCallsFactory() {
- tracker.connectNetwork(TEST_IFACE, NULL_LISTENER);
+ public void testEnableInterfaceCorrectlyCallsFactory() {
+ tracker.enableInterface(TEST_IFACE, NULL_LISTENER);
waitForIdle();
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */),
@@ -360,8 +361,8 @@
}
@Test
- public void testDisconnectNetworkCorrectlyCallsFactory() {
- tracker.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+ public void testDisableInterfaceCorrectlyCallsFactory() {
+ tracker.disableInterface(TEST_IFACE, NULL_LISTENER);
waitForIdle();
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */),
@@ -410,18 +411,24 @@
IpConfiguration configuration) { }
}
+ private InterfaceConfigurationParcel createMockedIfaceParcel(final String ifname,
+ final String hwAddr) {
+ final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
+ ifaceParcel.ifName = ifname;
+ ifaceParcel.hwAddr = hwAddr;
+ ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
+ return ifaceParcel;
+ }
+
@Test
public void testListenEthernetStateChange() throws Exception {
- final String testIface = "testtap123";
- final String testHwAddr = "11:22:33:44:55:66";
- final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
- ifaceParcel.ifName = testIface;
- ifaceParcel.hwAddr = testHwAddr;
- ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
-
tracker.setIncludeTestInterfaces(true);
waitForIdle();
+ final String testIface = "testtap123";
+ final String testHwAddr = "11:22:33:44:55:66";
+ final InterfaceConfigurationParcel ifaceParcel = createMockedIfaceParcel(testIface,
+ testHwAddr);
when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(ifaceParcel);
doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
@@ -453,4 +460,43 @@
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
anyInt(), any());
}
+
+ @Test
+ public void testListenEthernetStateChange_unsolicitedEventListener() throws Exception {
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {});
+ doReturn(new String[] {}).when(mFactory).getAvailableInterfaces(anyBoolean());
+
+ tracker.setIncludeTestInterfaces(true);
+ tracker.start();
+
+ final ArgumentCaptor<EthernetTracker.InterfaceObserver> captor =
+ ArgumentCaptor.forClass(EthernetTracker.InterfaceObserver.class);
+ verify(mNetd, timeout(TIMEOUT_MS)).registerUnsolicitedEventListener(captor.capture());
+ final EthernetTracker.InterfaceObserver observer = captor.getValue();
+
+ tracker.setEthernetEnabled(false);
+ waitForIdle();
+ reset(mFactory);
+ reset(mNetd);
+
+ final String testIface = "testtap1";
+ observer.onInterfaceAdded(testIface);
+ verify(mFactory, never()).addInterface(eq(testIface), anyString(), any(), any());
+ observer.onInterfaceRemoved(testIface);
+ verify(mFactory, never()).removeInterface(eq(testIface));
+
+ final String testHwAddr = "11:22:33:44:55:66";
+ final InterfaceConfigurationParcel testIfaceParce =
+ createMockedIfaceParcel(testIface, testHwAddr);
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
+ when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(testIfaceParce);
+ doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
+ tracker.setEthernetEnabled(true);
+ waitForIdle();
+ reset(mFactory);
+
+ final String testIface2 = "testtap2";
+ observer.onInterfaceRemoved(testIface2);
+ verify(mFactory, timeout(TIMEOUT_MS)).removeInterface(eq(testIface2));
+ }
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 5f9d1ff..e8c9637 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -32,6 +32,7 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
@@ -64,6 +65,7 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.Objects;
/**
@@ -86,11 +88,19 @@
private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
+ private static final int PID_SYSTEM = 1234;
+ private static final int PID_RED = 1235;
+ private static final int PID_BLUE = 1236;
+
private static final int UID_RED = UserHandle.PER_USER_RANGE + 1;
private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2;
private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
+ private static final String PACKAGE_SYSTEM = "android";
+ private static final String PACKAGE_RED = "RED";
+ private static final String PACKAGE_BLUE = "BLUE";
+
private static final long WAIT_TIMEOUT_MS = 500;
private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
private static final long BASE_BYTES = 7 * MB_IN_BYTES;
@@ -131,14 +141,15 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
final DataUsageRequest requestByApp = mStatsObservers.register(mContext, inputRequest,
- mUsageCallback, UID_RED, NetworkStatsAccess.Level.DEVICE);
+ mUsageCallback, PID_RED , UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
assertTrue(requestByApp.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, requestByApp.template));
assertEquals(thresholdTooLowBytes, requestByApp.thresholdInBytes);
// Verify the threshold requested by system uid won't be overridden.
final DataUsageRequest requestBySystem = mStatsObservers.register(mContext, inputRequest,
- mUsageCallback, Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ mUsageCallback, PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM,
+ NetworkStatsAccess.Level.DEVICE);
assertTrue(requestBySystem.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, requestBySystem.template));
assertEquals(1, requestBySystem.thresholdInBytes);
@@ -151,7 +162,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
assertEquals(highThresholdBytes, request.thresholdInBytes);
@@ -163,19 +174,64 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
DataUsageRequest request1 = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request1.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request1.template));
assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
DataUsageRequest request2 = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request2.requestId > request1.requestId);
assertTrue(Objects.equals(sTemplateWifi, request2.template));
assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
}
@Test
+ public void testRegister_limit() throws Exception {
+ final DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
+
+ // Register maximum requests for red.
+ final ArrayList<DataUsageRequest> redRequests = new ArrayList<>();
+ for (int i = 0; i < NetworkStatsObservers.MAX_REQUESTS_PER_UID; i++) {
+ final DataUsageRequest returnedRequest =
+ mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
+ redRequests.add(returnedRequest);
+ assertTrue(returnedRequest.requestId > 0);
+ }
+
+ // Verify request exceeds the limit throws.
+ assertThrows(IllegalStateException.class, () ->
+ mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE));
+
+ // Verify another uid is not affected.
+ final ArrayList<DataUsageRequest> blueRequests = new ArrayList<>();
+ for (int i = 0; i < NetworkStatsObservers.MAX_REQUESTS_PER_UID; i++) {
+ final DataUsageRequest returnedRequest =
+ mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+ PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.DEVICE);
+ blueRequests.add(returnedRequest);
+ assertTrue(returnedRequest.requestId > 0);
+ }
+
+ // Again, verify request exceeds the limit throws for the 2nd uid.
+ assertThrows(IllegalStateException.class, () ->
+ mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE));
+
+ // Unregister all registered requests. Note that exceptions cannot be tested since
+ // unregister is handled in the handler thread.
+ for (final DataUsageRequest request : redRequests) {
+ mStatsObservers.unregister(request, UID_RED);
+ }
+ for (final DataUsageRequest request : blueRequests) {
+ mStatsObservers.unregister(request, UID_BLUE);
+ }
+ }
+
+ @Test
public void testUnregister_unknownRequest_noop() throws Exception {
DataUsageRequest unknownRequest = new DataUsageRequest(
123456 /* id */, sTemplateWifi, THRESHOLD_BYTES);
@@ -189,7 +245,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -209,7 +265,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_RED, NetworkStatsAccess.Level.DEVICE);
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -237,7 +293,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -261,7 +317,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -291,7 +347,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -322,7 +378,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_RED, NetworkStatsAccess.Level.DEFAULT);
+ PID_RED, UID_RED, PACKAGE_SYSTEM , NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -355,7 +411,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
+ PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -387,7 +443,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_BLUE, NetworkStatsAccess.Level.USER);
+ PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -420,7 +476,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_RED, NetworkStatsAccess.Level.USER);
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);