Merge "Run CTS EthernetNetworkSpecifier in 12"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index b1e6a9f..a5b97a1 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,9 @@
]
},
{
+ "name": "bpf_existence_test"
+ },
+ {
"name": "netd_updatable_unit_test"
},
{
@@ -41,10 +44,6 @@
{
"name": "TetheringPrivilegedTests"
},
- // TODO: move to presubmit when known green.
- {
- "name": "bpf_existence_test"
- },
{
"name": "netd_updatable_unit_test",
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
@@ -76,6 +75,9 @@
]
},
{
+ "name": "bpf_existence_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ },
+ {
"name": "netd_updatable_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
@@ -128,10 +130,6 @@
},
{
"name": "TetheringCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
- },
- // TODO: move to mainline-presubmit when known green.
- {
- "name": "bpf_existence_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
}
],
"auto-postsubmit": [
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 705d187..de81a38 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -106,6 +106,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -278,7 +279,7 @@
final String localAddr = "192.0.2.3/28";
final String clientAddr = "192.0.2.2/28";
mTetheringEventCallback = enableEthernetTethering(iface,
- requestWithStaticIpv4(localAddr, clientAddr));
+ requestWithStaticIpv4(localAddr, clientAddr), null /* any upstream */);
mTetheringEventCallback.awaitInterfaceTethered();
assertInterfaceHasIpAddress(iface, localAddr);
@@ -361,7 +362,8 @@
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
.setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
- mTetheringEventCallback = enableEthernetTethering(iface, request);
+ mTetheringEventCallback = enableEthernetTethering(iface, request,
+ null /* any upstream */);
mTetheringEventCallback.awaitInterfaceLocalOnly();
// makePacketReader only works after tethering is started, because until then the interface
@@ -392,7 +394,7 @@
final String iface = mTetheredInterfaceRequester.getInterface();
// Enable Ethernet tethering and check that it starts.
- mTetheringEventCallback = enableEthernetTethering(iface);
+ mTetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
// There is nothing more we can do on a physical interface without connecting an actual
// client, which is not possible in this test.
@@ -405,8 +407,11 @@
private final CountDownLatch mLocalOnlyStartedLatch = new CountDownLatch(1);
private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1);
private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1);
- private final CountDownLatch mUpstreamConnectedLatch = new CountDownLatch(1);
+ private final CountDownLatch mUpstreamLatch = new CountDownLatch(1);
private final TetheringInterface mIface;
+ private final Network mExpectedUpstream;
+
+ private boolean mAcceptAnyUpstream = false;
private volatile boolean mInterfaceWasTethered = false;
private volatile boolean mInterfaceWasLocalOnly = false;
@@ -415,8 +420,14 @@
private volatile Network mUpstream = null;
MyTetheringEventCallback(TetheringManager tm, String iface) {
+ this(tm, iface, null);
+ mAcceptAnyUpstream = true;
+ }
+
+ MyTetheringEventCallback(TetheringManager tm, String iface, Network expectedUpstream) {
mTm = tm;
mIface = new TetheringInterface(TETHERING_ETHERNET, iface);
+ mExpectedUpstream = expectedUpstream;
}
public void unregister() {
@@ -526,19 +537,30 @@
Log.d(TAG, "Got upstream changed: " + network);
mUpstream = network;
- if (mUpstream != null) mUpstreamConnectedLatch.countDown();
+ if (mAcceptAnyUpstream || Objects.equals(mUpstream, mExpectedUpstream)) {
+ mUpstreamLatch.countDown();
+ }
}
- public Network awaitFirstUpstreamConnected() throws Exception {
- assertTrue("Did not receive upstream connected callback after " + TIMEOUT_MS + "ms",
- mUpstreamConnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ public Network awaitUpstreamChanged() throws Exception {
+ if (!mUpstreamLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Did not receive upstream " + (mAcceptAnyUpstream ? "any" : mExpectedUpstream)
+ + " callback after " + TIMEOUT_MS + "ms");
+ }
return mUpstream;
}
}
private MyTetheringEventCallback enableEthernetTethering(String iface,
- TetheringRequest request) throws Exception {
- MyTetheringEventCallback callback = new MyTetheringEventCallback(mTm, iface);
+ TetheringRequest request, Network expectedUpstream) throws Exception {
+ // Enable ethernet tethering with null expectedUpstream means the test accept any upstream
+ // after etherent tethering started.
+ final MyTetheringEventCallback callback;
+ if (expectedUpstream != null) {
+ callback = new MyTetheringEventCallback(mTm, iface, expectedUpstream);
+ } else {
+ callback = new MyTetheringEventCallback(mTm, iface);
+ }
mTm.registerTetheringEventCallback(mHandler::post, callback);
StartTetheringCallback startTetheringCallback = new StartTetheringCallback() {
@@ -565,10 +587,11 @@
return callback;
}
- private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception {
+ private MyTetheringEventCallback enableEthernetTethering(String iface, Network expectedUpstream)
+ throws Exception {
return enableEthernetTethering(iface,
new TetheringRequest.Builder(TETHERING_ETHERNET)
- .setShouldShowEntitlementUi(false).build());
+ .setShouldShowEntitlementUi(false).build(), expectedUpstream);
}
private int getMTU(TestNetworkInterface iface) throws SocketException {
@@ -592,7 +615,8 @@
private void checkVirtualEthernet(TestNetworkInterface iface, int mtu) throws Exception {
FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
mDownstreamReader = makePacketReader(fd, mtu);
- mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName());
+ mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName(),
+ null /* any upstream */);
checkTetheredClientCallbacks(mDownstreamReader);
}
@@ -690,7 +714,8 @@
private void assertInvalidStaticIpv4Request(String iface, String local, String client)
throws Exception {
try {
- enableEthernetTethering(iface, requestWithStaticIpv4(local, client));
+ enableEthernetTethering(iface, requestWithStaticIpv4(local, client),
+ null /* any upstream */);
fail("Unexpectedly accepted invalid IPv4 configuration: " + local + ", " + client);
} catch (IllegalArgumentException | NullPointerException expected) { }
}
@@ -746,9 +771,10 @@
assertEquals("TetheredInterfaceCallback for unexpected interface",
mDownstreamIface.getInterfaceName(), iface);
- mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName());
+ mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName(),
+ mUpstreamTracker.getNetwork());
assertEquals("onUpstreamChanged for unexpected network", mUpstreamTracker.getNetwork(),
- mTetheringEventCallback.awaitFirstUpstreamConnected());
+ mTetheringEventCallback.awaitUpstreamChanged());
mDownstreamReader = makePacketReader(mDownstreamIface);
// TODO: do basic forwarding test here.
@@ -951,9 +977,10 @@
assertEquals("TetheredInterfaceCallback for unexpected interface",
mDownstreamIface.getInterfaceName(), iface);
- mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName());
+ mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName(),
+ mUpstreamTracker.getNetwork());
assertEquals("onUpstreamChanged for unexpected network", mUpstreamTracker.getNetwork(),
- mTetheringEventCallback.awaitFirstUpstreamConnected());
+ mTetheringEventCallback.awaitUpstreamChanged());
mDownstreamReader = makePacketReader(mDownstreamIface);
mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
diff --git a/Tethering/tests/privileged/src/com/android/net/module/util/BpfBitmapTest.java b/Tethering/tests/privileged/src/com/android/net/module/util/BpfBitmapTest.java
new file mode 100644
index 0000000..2112396
--- /dev/null
+++ b/Tethering/tests/privileged/src/com/android/net/module/util/BpfBitmapTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+import static com.android.networkstack.tethering.util.TetheringUtils.getTetheringJniLibraryName;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.net.module.util.BpfBitmap;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+public final class BpfBitmapTest {
+ private static final String TEST_BITMAP_PATH =
+ "/sys/fs/bpf/tethering/map_test_bitmap";
+
+ private static final int mTestData[] = {0,1,2,6,63,64,72};
+ private BpfBitmap mTestBitmap;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestBitmap = new BpfBitmap(TEST_BITMAP_PATH);
+ mTestBitmap.clear();
+ assertTrue(mTestBitmap.isEmpty());
+ }
+
+ @Test
+ public void testSet() throws Exception {
+ for (int i : mTestData) {
+ mTestBitmap.set(i);
+ assertFalse(mTestBitmap.isEmpty());
+ assertTrue(mTestBitmap.get(i));
+ // Check that the next item in the bitmap is unset since test data is in
+ // ascending order.
+ assertFalse(mTestBitmap.get(i + 1));
+ }
+ }
+
+ @Test
+ public void testSetThenUnset() throws Exception {
+ for (int i : mTestData) {
+ mTestBitmap.set(i);
+ assertFalse(mTestBitmap.isEmpty());
+ assertTrue(mTestBitmap.get(i));
+ // Since test unsets all test data during each iteration, ensure all other
+ // bit are unset.
+ for (int j = 0; j < 128; ++j) if (j != i) assertFalse(mTestBitmap.get(j));
+ mTestBitmap.unset(i);
+ }
+ }
+
+ @Test
+ public void testSetAllThenUnsetAll() throws Exception {
+ for (int i : mTestData) {
+ mTestBitmap.set(i);
+ }
+
+ for (int i : mTestData) {
+ mTestBitmap.unset(i);
+ if (i < mTestData.length)
+ assertFalse(mTestBitmap.isEmpty());
+ assertFalse(mTestBitmap.get(i));
+ }
+ assertTrue(mTestBitmap.isEmpty());
+ }
+
+ @Test
+ public void testClear() throws Exception {
+ for (int i = 0; i < 128; ++i) {
+ mTestBitmap.set(i);
+ }
+ assertFalse(mTestBitmap.isEmpty());
+ mTestBitmap.clear();
+ assertTrue(mTestBitmap.isEmpty());
+ }
+}
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index a76e346..c9c73f1 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -28,6 +28,9 @@
// Used only by TetheringPrivilegedTests, not by production code.
DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
AID_NETWORK_STACK)
+// Used only by BpfBitmapTest, not by production code.
+DEFINE_BPF_MAP_GRW(bitmap, ARRAY, int, uint64_t, 2,
+ AID_NETWORK_STACK)
DEFINE_BPF_PROG_KVER("xdp/drop_ipv4_udp_ether", AID_ROOT, AID_NETWORK_STACK,
xdp_test, KVER(5, 9, 0))
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 85cfc09..f13c68d 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -24,6 +24,7 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
#include <nativehelper/ScopedPrimitiveArray.h>
+#include <netjniutils/netjniutils.h>
#include <net/if.h>
#include <vector>
@@ -108,6 +109,7 @@
}
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) {
@@ -144,6 +146,7 @@
}
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)) {
@@ -159,6 +162,7 @@
}
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)) {
@@ -186,6 +190,15 @@
mTc.setPermissionForUids(permission, data);
}
+static void native_dump(JNIEnv* env, jobject clazz, jobject javaFd, jboolean verbose) {
+ int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
+ if (fd < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
+ return;
+ }
+ mTc.dump(fd, verbose);
+}
+
/*
* JNI registration.
*/
@@ -216,6 +229,8 @@
(void*)native_swapActiveStatsMap},
{"native_setPermissionForUids", "(I[I)V",
(void*)native_setPermissionForUids},
+ {"native_dump", "(Ljava/io/FileDescriptor;Z)V",
+ (void*)native_dump},
};
// clang-format on
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 1cbfd94..3e98edb 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -65,7 +65,6 @@
using netdutils::Status;
using netdutils::statusFromErrno;
using netdutils::StatusOr;
-using netdutils::status::ok;
constexpr int kSockDiagMsgType = SOCK_DIAG_BY_FAMILY;
constexpr int kSockDiagDoneMsgType = NLMSG_DONE;
@@ -601,8 +600,10 @@
}
}
-void TrafficController::dump(DumpWriter& dw, bool verbose) {
+void TrafficController::dump(int fd, bool verbose) {
std::lock_guard guard(mMutex);
+ DumpWriter dw(fd);
+
ScopedIndent indentTop(dw);
dw.println("TrafficController");
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index 6fe117f..79e75ac 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -62,7 +62,7 @@
netdutils::Status updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
FirewallType type) EXCLUDES(mMutex);
- void dump(netdutils::DumpWriter& dw, bool verbose) EXCLUDES(mMutex);
+ void dump(int fd, bool verbose) EXCLUDES(mMutex);
netdutils::Status replaceRulesInMap(UidOwnerMatchType match, const std::vector<int32_t>& uids)
EXCLUDES(mMutex);
diff --git a/service/native/libs/libclat/bpfhelper.cpp b/service/native/libs/libclat/bpfhelper.cpp
index 6e230d0..00785ad 100644
--- a/service/native/libs/libclat/bpfhelper.cpp
+++ b/service/native/libs/libclat/bpfhelper.cpp
@@ -28,16 +28,6 @@
#define DEVICEPREFIX "v4-"
using android::base::unique_fd;
-using android::net::RAWIP;
-using android::net::getClatEgress4MapFd;
-using android::net::getClatIngress6MapFd;
-using android::net::getClatEgress4ProgFd;
-using android::net::getClatIngress6ProgFd;
-using android::net::tcQdiscAddDevClsact;
-using android::net::tcFilterAddDevEgressClatIpv4;
-using android::net::tcFilterAddDevIngressClatIpv6;
-using android::net::tcFilterDelDevEgressClatIpv4;
-using android::net::tcFilterDelDevIngressClatIpv6;
using android::bpf::BpfMap;
BpfMap<ClatEgress4Key, ClatEgress4Value> mClatEgress4Map;
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index ddee275..f2ca18b 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.system.OsConstants.EOPNOTSUPP;
+
import android.net.INetd;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -24,6 +26,9 @@
import com.android.modules.utils.build.SdkLevel;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
/**
* BpfNetMaps is responsible for providing traffic controller relevant functionality.
*
@@ -273,6 +278,23 @@
native_setPermissionForUids(permissions, uids);
}
+ /**
+ * Dump BPF maps
+ *
+ * @param fd file descriptor to output
+ * @throws IOException when file descriptor is invalid.
+ * @throws ServiceSpecificException when the method is called on an unsupported device.
+ */
+ public void dump(final FileDescriptor fd, boolean verbose)
+ throws IOException, ServiceSpecificException {
+ if (USE_NETD) {
+ throw new ServiceSpecificException(
+ EOPNOTSUPP, "dumpsys connectivity trafficcontroller dump not available on pre-T"
+ + " devices, use dumpsys netd trafficcontroller instead.");
+ }
+ native_dump(fd, verbose);
+ }
+
private static native void native_init();
private native int native_addNaughtyApp(int uid);
private native int native_removeNaughtyApp(int uid);
@@ -285,4 +307,5 @@
private native int native_removeUidInterfaceRules(int[] uids);
private native int native_swapActiveStatsMap();
private native void native_setPermissionForUids(int permissions, int[] uids);
+ private native void native_dump(FileDescriptor fd, boolean verbose);
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 6024a2a..c90fcd5 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -313,6 +313,7 @@
public static final String SHORT_ARG = "--short";
private static final String NETWORK_ARG = "networks";
private static final String REQUEST_ARG = "requests";
+ private static final String TRAFFICCONTROLLER_ARG = "trafficcontroller";
private static final boolean DBG = true;
private static final boolean DDBG = Log.isLoggable(TAG, Log.DEBUG);
@@ -645,8 +646,9 @@
* Event for NetworkMonitor to inform ConnectivityService that the probe status has changed.
* Both of the arguments are bitmasks, and the value of bits come from
* INetworkMonitor.NETWORK_VALIDATION_PROBE_*.
- * arg1 = A bitmask to describe which probes are completed.
- * arg2 = A bitmask to describe which probes are successful.
+ * arg1 = unused
+ * arg2 = netId
+ * obj = A Pair of integers: the bitmasks of, respectively, completed and successful probes.
*/
public static final int EVENT_PROBE_STATUS_CHANGED = 45;
@@ -1409,6 +1411,8 @@
// converting rateInBytesPerSecond from long to int is safe here because the
// setting's range is limited to INT_MAX.
// TODO: add long/uint64 support to tcFilterAddDevIngressPolice.
+ Log.i(TAG,
+ "enableIngressRateLimit on " + iface + ": " + rateInBytesPerSecond + "B/s");
TcUtils.tcFilterAddDevIngressPolice(params.index, TC_PRIO_POLICE, (short) ETH_P_ALL,
(int) rateInBytesPerSecond, TC_POLICE_BPF_PROG_PATH);
} catch (IOException e) {
@@ -1430,6 +1434,8 @@
return;
}
try {
+ Log.i(TAG,
+ "disableIngressRateLimit on " + iface);
TcUtils.tcFilterDelDev(params.index, true, TC_PRIO_POLICE, (short) ETH_P_ALL);
} catch (IOException e) {
loge("TcUtils.tcFilterDelDev(ifaceIndex=" + params.index
@@ -1752,7 +1758,7 @@
// Watch for ingress rate limit changes.
mSettingsObserver.observe(
- Settings.Secure.getUriFor(
+ Settings.Global.getUriFor(
ConnectivitySettingsManager.INGRESS_RATE_LIMIT_BYTES_PER_SECOND),
EVENT_INGRESS_RATE_LIMIT_CHANGED);
}
@@ -3212,6 +3218,10 @@
} else if (CollectionUtils.contains(args, REQUEST_ARG)) {
dumpNetworkRequests(pw);
return;
+ } else if (CollectionUtils.contains(args, TRAFFICCONTROLLER_ARG)) {
+ boolean verbose = !CollectionUtils.contains(args, SHORT_ARG);
+ dumpTrafficController(pw, fd, verbose);
+ return;
}
pw.print("NetworkProviders for:");
@@ -3429,6 +3439,17 @@
}
}
+ private void dumpTrafficController(IndentingPrintWriter pw, final FileDescriptor fd,
+ boolean verbose) {
+ try {
+ mBpfNetMaps.dump(fd, verbose);
+ } catch (ServiceSpecificException e) {
+ pw.println(e.getMessage());
+ } catch (IOException e) {
+ loge("Dump BPF maps failed, " + e);
+ }
+ }
+
private void dumpAllRequestInfoLogsToLogcat() {
try (PrintWriter logPw = new PrintWriter(new Writer() {
@Override
@@ -3602,19 +3623,21 @@
}
private boolean maybeHandleNetworkMonitorMessage(Message msg) {
+ final int netId = msg.arg2;
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
switch (msg.what) {
default:
return false;
case EVENT_PROBE_STATUS_CHANGED: {
- final Integer netId = (Integer) msg.obj;
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
if (nai == null) {
break;
}
+ final int probesCompleted = ((Pair<Integer, Integer>) msg.obj).first;
+ final int probesSucceeded = ((Pair<Integer, Integer>) msg.obj).second;
final boolean probePrivateDnsCompleted =
- ((msg.arg1 & NETWORK_VALIDATION_PROBE_PRIVDNS) != 0);
+ ((probesCompleted & NETWORK_VALIDATION_PROBE_PRIVDNS) != 0);
final boolean privateDnsBroken =
- ((msg.arg2 & NETWORK_VALIDATION_PROBE_PRIVDNS) == 0);
+ ((probesSucceeded & NETWORK_VALIDATION_PROBE_PRIVDNS) == 0);
if (probePrivateDnsCompleted) {
if (nai.networkCapabilities.isPrivateDnsBroken() != privateDnsBroken) {
nai.networkCapabilities.setPrivateDnsBroken(privateDnsBroken);
@@ -3641,7 +3664,6 @@
case EVENT_NETWORK_TESTED: {
final NetworkTestedResults results = (NetworkTestedResults) msg.obj;
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(results.mNetId);
if (nai == null) break;
handleNetworkTested(nai, results.mTestResult,
@@ -3649,9 +3671,7 @@
break;
}
case EVENT_PROVISIONING_NOTIFICATION: {
- final int netId = msg.arg2;
final boolean visible = toBool(msg.arg1);
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
// If captive portal status has changed, update capabilities or disconnect.
if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
nai.lastCaptivePortalDetected = visible;
@@ -3685,14 +3705,12 @@
break;
}
case EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
updatePrivateDns(nai, (PrivateDnsConfig) msg.obj);
break;
}
case EVENT_CAPPORT_DATA_CHANGED: {
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
handleCapportApiDataUpdate(nai, (CaptivePortalData) msg.obj);
break;
@@ -3832,6 +3850,7 @@
// the same looper so messages will be processed in sequence.
final Message msg = mTrackerHandler.obtainMessage(
EVENT_NETWORK_TESTED,
+ 0, mNetId,
new NetworkTestedResults(
mNetId, p.result, p.timestampMillis, p.redirectUrl));
mTrackerHandler.sendMessage(msg);
@@ -3854,7 +3873,7 @@
ConnectivityReportEvent reportEvent =
new ConnectivityReportEvent(p.timestampMillis, nai, extras);
final Message m = mConnectivityDiagnosticsHandler.obtainMessage(
- ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED, reportEvent);
+ ConnectivityDiagnosticsHandler.CMD_SEND_CONNECTIVITY_REPORT, reportEvent);
mConnectivityDiagnosticsHandler.sendMessage(m);
}
@@ -3869,7 +3888,7 @@
public void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) {
mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
EVENT_PROBE_STATUS_CHANGED,
- probesCompleted, probesSucceeded, new Integer(mNetId)));
+ 0, mNetId, new Pair<>(probesCompleted, probesSucceeded)));
}
@Override
@@ -9579,14 +9598,12 @@
/**
* Event for {@link NetworkStateTrackerHandler} to trigger ConnectivityReport callbacks
- * after processing {@link #EVENT_NETWORK_TESTED} events.
+ * after processing {@link #CMD_SEND_CONNECTIVITY_REPORT} events.
* obj = {@link ConnectivityReportEvent} representing ConnectivityReport info reported from
* NetworkMonitor.
* data = PersistableBundle of extras passed from NetworkMonitor.
- *
- * <p>See {@link ConnectivityService#EVENT_NETWORK_TESTED}.
*/
- private static final int EVENT_NETWORK_TESTED = ConnectivityService.EVENT_NETWORK_TESTED;
+ private static final int CMD_SEND_CONNECTIVITY_REPORT = 3;
/**
* Event for NetworkMonitor to inform ConnectivityService that a potential data stall has
@@ -9624,7 +9641,7 @@
(IConnectivityDiagnosticsCallback) msg.obj, msg.arg1);
break;
}
- case EVENT_NETWORK_TESTED: {
+ case CMD_SEND_CONNECTIVITY_REPORT: {
final ConnectivityReportEvent reportEvent =
(ConnectivityReportEvent) msg.obj;
@@ -10684,15 +10701,19 @@
}
private boolean canNetworkBeRateLimited(@NonNull final NetworkAgentInfo networkAgent) {
- if (!networkAgent.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) {
- // rate limits only apply to networks that provide internet connectivity.
+ final NetworkCapabilities agentCaps = networkAgent.networkCapabilities;
+ // Only test networks (they cannot hold NET_CAPABILITY_INTERNET) and networks that provide
+ // internet connectivity can be rate limited.
+ if (!agentCaps.hasCapability(NET_CAPABILITY_INTERNET) && !agentCaps.hasTransport(
+ TRANSPORT_TEST)) {
return false;
}
final String iface = networkAgent.linkProperties.getInterfaceName();
if (iface == null) {
- // This can never happen.
- logwtf("canNetworkBeRateLimited: LinkProperties#getInterfaceName returns null");
+ // This may happen in tests, but if there is no interface then there is nothing that
+ // can be rate limited.
+ loge("canNetworkBeRateLimited: LinkProperties#getInterfaceName returns null");
return false;
}
return true;
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index aebb80d..799f46b 100644
--- a/service/src/com/android/server/connectivity/FullScore.java
+++ b/service/src/com/android/server/connectivity/FullScore.java
@@ -21,8 +21,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkScore.KEEP_CONNECTED_NONE;
-import static android.net.NetworkScore.POLICY_EXITING;
-import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI;
import android.annotation.IntDef;
@@ -31,8 +29,10 @@
import android.net.NetworkCapabilities;
import android.net.NetworkScore;
import android.net.NetworkScore.KeepConnectedReason;
+import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.MessageUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -98,9 +98,17 @@
/** @hide */
public static final int POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD = 57;
+ // The network agent has communicated that this network no longer functions, and the underlying
+ // native network has been destroyed. The network will still be reported to clients as connected
+ // until a timeout expires, the agent disconnects, or the network no longer satisfies requests.
+ // This network should lose to an identical network that has not been destroyed, but should
+ // otherwise be scored exactly the same.
+ /** @hide */
+ public static final int POLICY_IS_DESTROYED = 56;
+
// To help iterate when printing
@VisibleForTesting
- static final int MIN_CS_MANAGED_POLICY = POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD;
+ static final int MIN_CS_MANAGED_POLICY = POLICY_IS_DESTROYED;
@VisibleForTesting
static final int MAX_CS_MANAGED_POLICY = POLICY_IS_VALIDATED;
@@ -112,21 +120,14 @@
private static final long EXTERNAL_POLICIES_MASK =
0x00000000FFFFFFFFL & ~(1L << POLICY_YIELD_TO_BAD_WIFI);
+ private static SparseArray<String> sMessageNames = MessageUtils.findMessageNames(
+ new Class[]{FullScore.class, NetworkScore.class}, new String[]{"POLICY_"});
+
@VisibleForTesting
static @NonNull String policyNameOf(final int policy) {
- switch (policy) {
- case POLICY_IS_VALIDATED: return "IS_VALIDATED";
- case POLICY_IS_VPN: return "IS_VPN";
- case POLICY_EVER_USER_SELECTED: return "EVER_USER_SELECTED";
- case POLICY_ACCEPT_UNVALIDATED: return "ACCEPT_UNVALIDATED";
- case POLICY_IS_UNMETERED: return "IS_UNMETERED";
- case POLICY_YIELD_TO_BAD_WIFI: return "YIELD_TO_BAD_WIFI";
- case POLICY_TRANSPORT_PRIMARY: return "TRANSPORT_PRIMARY";
- case POLICY_EXITING: return "EXITING";
- case POLICY_IS_INVINCIBLE: return "INVINCIBLE";
- case POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD: return "EVER_VALIDATED";
- }
- throw new IllegalArgumentException("Unknown policy : " + policy);
+ final String name = sMessageNames.get(policy);
+ if (name == null) throw new IllegalArgumentException("Unknown policy: " + policy);
+ return name.substring("POLICY_".length());
}
// Bitmask of all the policies applied to this score.
@@ -149,6 +150,7 @@
* @param config the NetworkAgentConfig of the network
* @param everValidated whether this network has ever validated
* @param yieldToBadWiFi whether this network yields to a previously validated wifi gone bad
+ * @param destroyed whether this network has been destroyed pending a replacement connecting
* @return a FullScore that is appropriate to use for ranking.
*/
// TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
@@ -156,7 +158,7 @@
// connectivity for backward compatibility.
public static FullScore fromNetworkScore(@NonNull final NetworkScore score,
@NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config,
- final boolean everValidated, final boolean yieldToBadWiFi) {
+ final boolean everValidated, final boolean yieldToBadWiFi, final boolean destroyed) {
return withPolicies(score.getLegacyInt(), score.getPolicies(),
score.getKeepConnectedReason(),
caps.hasCapability(NET_CAPABILITY_VALIDATED),
@@ -166,6 +168,7 @@
config.explicitlySelected,
config.acceptUnvalidated,
yieldToBadWiFi,
+ destroyed,
false /* invincible */); // only prospective scores can be invincible
}
@@ -174,7 +177,7 @@
*
* NetworkOffers have score filters that are compared to the scores of actual networks
* to see if they could possibly beat the current satisfier. Some things the agent can't
- * know in advance ; a good example is the validation bit – some networks will validate,
+ * know in advance; a good example is the validation bit – some networks will validate,
* others won't. For comparison purposes, assume the best, so all possibly beneficial
* networks will be brought up.
*
@@ -197,12 +200,14 @@
final boolean everUserSelected = false;
// Don't assume the user will accept unvalidated connectivity.
final boolean acceptUnvalidated = false;
+ // A network can only be destroyed once it has connected.
+ final boolean destroyed = false;
// A prospective score is invincible if the legacy int in the filter is over the maximum
// score.
final boolean invincible = score.getLegacyInt() > NetworkRanker.LEGACY_INT_MAX;
return withPolicies(score.getLegacyInt(), score.getPolicies(), KEEP_CONNECTED_NONE,
mayValidate, vpn, unmetered, everValidated, everUserSelected, acceptUnvalidated,
- yieldToBadWiFi, invincible);
+ yieldToBadWiFi, destroyed, invincible);
}
/**
@@ -218,7 +223,8 @@
public FullScore mixInScore(@NonNull final NetworkCapabilities caps,
@NonNull final NetworkAgentConfig config,
final boolean everValidated,
- final boolean yieldToBadWifi) {
+ final boolean yieldToBadWifi,
+ final boolean destroyed) {
return withPolicies(mLegacyInt, mPolicies, mKeepConnectedReason,
caps.hasCapability(NET_CAPABILITY_VALIDATED),
caps.hasTransport(TRANSPORT_VPN),
@@ -227,6 +233,7 @@
config.explicitlySelected,
config.acceptUnvalidated,
yieldToBadWifi,
+ destroyed,
false /* invincible */); // only prospective scores can be invincible
}
@@ -243,6 +250,7 @@
final boolean everUserSelected,
final boolean acceptUnvalidated,
final boolean yieldToBadWiFi,
+ final boolean destroyed,
final boolean invincible) {
return new FullScore(legacyInt, (externalPolicies & EXTERNAL_POLICIES_MASK)
| (isValidated ? 1L << POLICY_IS_VALIDATED : 0)
@@ -252,6 +260,7 @@
| (everUserSelected ? 1L << POLICY_EVER_USER_SELECTED : 0)
| (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0)
| (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0)
+ | (destroyed ? 1L << POLICY_IS_DESTROYED : 0)
| (invincible ? 1L << POLICY_IS_INVINCIBLE : 0),
keepConnectedReason);
}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index e917b3f..e29d616 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -106,6 +106,12 @@
// or tunnel) but does not disconnect from the AP/cell tower, or
// d. a stand-alone device offering a WiFi AP without an uplink for configuration purposes.
// 5. registered, created, connected, validated
+// 6. registered, created, connected, (validated or unvalidated), destroyed
+// This is an optional state where the underlying native network is destroyed but the network is
+// still connected for scoring purposes, so can satisfy requests, including the default request.
+// It is used when the transport layer wants to replace a network with another network (e.g.,
+// when Wi-Fi has roamed to a different BSSID that is part of a different L3 network) and does
+// not want the device to switch to another network until the replacement connects and validates.
//
// The device's default network connection:
// ----------------------------------------
@@ -184,6 +190,9 @@
// shows up in API calls, is able to satisfy NetworkRequests and can become the default network.
// This is a sticky bit; once set it is never cleared.
public boolean everConnected;
+ // Whether this network has been destroyed and is being kept temporarily until it is replaced.
+ public boolean destroyed;
+
// Set to true if this Network successfully passed validation or if it did not satisfy the
// default NetworkRequest in which case validation will not be attempted.
// This is a sticky bit; once set it is never cleared even if future validation attempts fail.
@@ -746,7 +755,7 @@
final NetworkCapabilities oldNc = networkCapabilities;
networkCapabilities = nc;
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, everValidatedForYield(),
- yieldToBadWiFi());
+ yieldToBadWiFi(), destroyed);
final NetworkMonitorManager nm = mNetworkMonitor;
if (nm != null) {
nm.notifyNetworkCapabilitiesChanged(nc);
@@ -874,7 +883,7 @@
/**
* Returns the number of requests currently satisfied by this network of type
- * {@link android.net.NetworkRequest.Type.BACKGROUND_REQUEST}.
+ * {@link android.net.NetworkRequest.Type#BACKGROUND_REQUEST}.
*/
public int numBackgroundNetworkRequests() {
return mNumBackgroundNetworkRequests;
@@ -961,7 +970,7 @@
*/
public void setScore(final NetworkScore score) {
mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
- everValidatedForYield(), yieldToBadWiFi());
+ everValidatedForYield(), yieldToBadWiFi(), destroyed);
}
/**
@@ -971,7 +980,7 @@
*/
public void updateScoreForNetworkAgentUpdate() {
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig,
- everValidatedForYield(), yieldToBadWiFi());
+ everValidatedForYield(), yieldToBadWiFi(), destroyed);
}
private boolean everValidatedForYield() {
@@ -1019,7 +1028,7 @@
* when a network is newly created.
*
* @param requestId The requestId of the request that no longer need to be served by this
- * network. Or {@link NetworkRequest.REQUEST_ID_NONE} if this is the
+ * network. Or {@link NetworkRequest#REQUEST_ID_NONE} if this is the
* {@code InactivityTimer} for a newly created network.
*/
// TODO: Consider creating a dedicated function for nascent network, e.g. start/stopNascent.
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index 43da1d0..babc353 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -28,6 +28,7 @@
import static com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED;
import static com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED;
import static com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD;
+import static com.android.server.connectivity.FullScore.POLICY_IS_DESTROYED;
import static com.android.server.connectivity.FullScore.POLICY_IS_INVINCIBLE;
import static com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED;
import static com.android.server.connectivity.FullScore.POLICY_IS_VPN;
@@ -263,6 +264,15 @@
}
}
+ // If two networks are equivalent, and one has been destroyed pending replacement, keep the
+ // other one. This ensures that when the replacement connects, it's preferred.
+ partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_IS_DESTROYED),
+ accepted, rejected);
+ if (accepted.size() == 1) return accepted.get(0);
+ if (accepted.size() > 0 && rejected.size() > 0) {
+ candidates = new ArrayList<>(accepted);
+ }
+
// At this point there are still multiple networks passing all the tests above. If any
// of them is the previous satisfier, keep it.
if (candidates.contains(currentSatisfier)) return currentSatisfier;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 5778b0d..7150918 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -334,9 +334,8 @@
@Nullable ProxyInfo proxyInfo,
@Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered)
throws Exception {
- startVpn(addresses, routes, new String[0] /* excludedRoutes */, allowedApplications,
- disallowedApplications, proxyInfo, underlyingNetworks, isAlwaysMetered,
- false /* addRoutesByIpPrefix */);
+ startVpn(addresses, routes, excludedRoutes, allowedApplications, disallowedApplications,
+ proxyInfo, underlyingNetworks, isAlwaysMetered, false /* addRoutesByIpPrefix */);
}
private void startVpn(
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index ea64252..8f6ff19 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -2174,7 +2174,7 @@
private void waitForAvailable(
@NonNull final TestableNetworkCallback cb, @NonNull final Network expectedNetwork) {
cb.expectAvailableCallbacks(expectedNetwork, false /* suspended */,
- true /* validated */,
+ null /* validated */,
false /* blocked */, NETWORK_CALLBACK_TIMEOUT_MS);
}
@@ -3259,6 +3259,19 @@
assertTrue(dumpOutput, dumpOutput.contains("Active default network"));
}
+ @Test @IgnoreUpTo(SC_V2)
+ public void testDumpBpfNetMaps() throws Exception {
+ final String[] args = new String[] {"--short", "trafficcontroller"};
+ String dumpOutput = DumpTestUtils.dumpServiceWithShellPermission(
+ Context.CONNECTIVITY_SERVICE, args);
+ assertTrue(dumpOutput, dumpOutput.contains("TrafficController"));
+ assertFalse(dumpOutput, dumpOutput.contains("BPF map content"));
+
+ dumpOutput = DumpTestUtils.dumpServiceWithShellPermission(
+ Context.CONNECTIVITY_SERVICE, args[1]);
+ assertTrue(dumpOutput, dumpOutput.contains("BPF map content"));
+ }
+
private void unregisterRegisteredCallbacks() {
for (NetworkCallback callback: mRegisteredCallbacks) {
mCm.unregisterNetworkCallback(callback);
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 810d1c6..225602f 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -472,7 +472,7 @@
@Test
fun testRejectedUpdates() {
- val callback = TestableNetworkCallback()
+ val callback = TestableNetworkCallback(DEFAULT_TIMEOUT_MS)
// will be cleaned up in tearDown
registerNetworkCallback(makeTestNetworkRequest(), callback)
val agent = createNetworkAgent(initialNc = ncWithAccessUids(200))
diff --git a/tests/cts/net/src/android/net/cts/RateLimitTest.java b/tests/cts/net/src/android/net/cts/RateLimitTest.java
new file mode 100644
index 0000000..5f9e0f3
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/RateLimitTest.java
@@ -0,0 +1,307 @@
+/*
+ * 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.cts;
+
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.icu.text.MessageFormat;
+import android.net.ConnectivityManager;
+import android.net.ConnectivitySettingsManager;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.RouteInfo;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.net.TestNetworkSpecifier;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.net.module.util.PacketBuilder;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestableNetworkAgent;
+import com.android.testutils.TestableNetworkCallback;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.util.Arrays;
+
+@AppModeFull(reason = "Instant apps cannot access /dev/tun, so createTunInterface fails")
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+public class RateLimitTest {
+ private static final String TAG = "RateLimitTest";
+ private static final LinkAddress LOCAL_IP4_ADDR = new LinkAddress("10.0.0.1/8");
+ private static final InetAddress REMOTE_IP4_ADDR = InetAddresses.parseNumericAddress("8.8.8.8");
+ private static final short TEST_UDP_PORT = 1234;
+ private static final byte TOS = 0;
+ private static final short ID = 27149;
+ private static final short DONT_FRAG_FLAG_MASK = (short) 0x4000; // flags=DF, offset=0
+ private static final byte TIME_TO_LIVE = 64;
+ private static final byte[] PAYLOAD = new byte[1472];
+
+ private Handler mHandler;
+ private Context mContext;
+ private TestNetworkManager mNetworkManager;
+ private TestNetworkInterface mTunInterface;
+ private ConnectivityManager mCm;
+ private TestNetworkSpecifier mNetworkSpecifier;
+ private NetworkCapabilities mNetworkCapabilities;
+ private TestableNetworkCallback mNetworkCallback;
+ private LinkProperties mLinkProperties;
+ private TestableNetworkAgent mNetworkAgent;
+ private Network mNetwork;
+ private DatagramSocket mSocket;
+
+ @Before
+ public void setUp() throws IOException {
+ mHandler = new Handler(Looper.getMainLooper());
+
+ runAsShell(MANAGE_TEST_NETWORKS, () -> {
+ mContext = getContext();
+
+ mNetworkManager = mContext.getSystemService(TestNetworkManager.class);
+ mTunInterface = mNetworkManager.createTunInterface(Arrays.asList(LOCAL_IP4_ADDR));
+ });
+
+ mCm = mContext.getSystemService(ConnectivityManager.class);
+ mNetworkSpecifier = new TestNetworkSpecifier(mTunInterface.getInterfaceName());
+ mNetworkCapabilities = new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .setNetworkSpecifier(mNetworkSpecifier).build();
+ mNetworkCallback = new TestableNetworkCallback();
+
+ mCm.requestNetwork(
+ new NetworkRequest.Builder()
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .setNetworkSpecifier(mNetworkSpecifier)
+ .build(),
+ mNetworkCallback);
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.addLinkAddress(LOCAL_IP4_ADDR);
+ mLinkProperties.setInterfaceName(mTunInterface.getInterfaceName());
+ mLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(IPV4_ADDR_ANY, 0), null,
+ mTunInterface.getInterfaceName()));
+
+
+ runAsShell(MANAGE_TEST_NETWORKS, () -> {
+ mNetworkAgent = new TestableNetworkAgent(mContext, mHandler.getLooper(),
+ mNetworkCapabilities, mLinkProperties,
+ new NetworkAgentConfig.Builder().setExplicitlySelected(
+ true).setUnvalidatedConnectivityAcceptable(true).build());
+
+ mNetworkAgent.register();
+ mNetworkAgent.markConnected();
+ });
+
+ mNetwork = mNetworkAgent.getNetwork();
+ mNetworkCallback.expectAvailableThenValidatedCallbacks(mNetwork, 5_000);
+ mSocket = new DatagramSocket(TEST_UDP_PORT);
+ mSocket.setSoTimeout(1_000);
+ mNetwork.bindSocket(mSocket);
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ // whatever happens, don't leave the device in rate limited state.
+ ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext, -1);
+ mSocket.close();
+ mNetworkAgent.unregister();
+ mTunInterface.getFileDescriptor().close();
+ mCm.unregisterNetworkCallback(mNetworkCallback);
+ }
+
+ private void assertGreaterThan(final String msg, long lhs, long rhs) {
+ assertTrue(msg + " -- Failed comparison: " + lhs + " > " + rhs, lhs > rhs);
+ }
+
+ private void assertLessThan(final String msg, long lhs, long rhs) {
+ assertTrue(msg + " -- Failed comparison: " + lhs + " < " + rhs, lhs < rhs);
+ }
+
+ private static void sendPacketsToTunInterfaceForDuration(final TestNetworkInterface iface,
+ final Duration duration) throws Exception {
+ final ByteBuffer buffer = PacketBuilder.allocate(false, IPPROTO_IP, IPPROTO_UDP,
+ PAYLOAD.length);
+ final PacketBuilder builder = new PacketBuilder(buffer);
+ builder.writeIpv4Header(TOS, ID, DONT_FRAG_FLAG_MASK, TIME_TO_LIVE,
+ (byte) IPPROTO_UDP, (Inet4Address) REMOTE_IP4_ADDR,
+ (Inet4Address) LOCAL_IP4_ADDR.getAddress());
+ builder.writeUdpHeader((short) TEST_UDP_PORT, (short) TEST_UDP_PORT);
+ buffer.put(PAYLOAD);
+ builder.finalizePacket();
+
+ // write packets to the tun fd as fast as possible for duration.
+ long endMillis = SystemClock.elapsedRealtime() + duration.toMillis();
+ while (SystemClock.elapsedRealtime() < endMillis) {
+ Os.write(iface.getFileDescriptor().getFileDescriptor(), buffer.array(), 0,
+ buffer.limit());
+ }
+ }
+
+ private static class RateMeasurementSocketReader extends Thread {
+ private volatile boolean mIsRunning = false;
+ private DatagramSocket mSocket;
+ private long mStartMillis = 0;
+ private long mStopMillis = 0;
+ private long mBytesReceived = 0;
+
+ RateMeasurementSocketReader(DatagramSocket socket) throws Exception {
+ mSocket = socket;
+ }
+
+ public void startTest() {
+ mIsRunning = true;
+ start();
+ }
+
+ public long stopAndGetResult() throws Exception {
+ mIsRunning = false;
+ join();
+
+ final long durationMillis = mStopMillis - mStartMillis;
+ return (long) ((double) mBytesReceived / (durationMillis / 1000.0));
+ }
+
+ @Override
+ public void run() {
+ // Since the actual data is not used, the buffer can just be recycled.
+ final byte[] recvBuf = new byte[ETHER_MTU];
+ final DatagramPacket receivedPacket = new DatagramPacket(recvBuf, recvBuf.length);
+ while (mIsRunning) {
+ try {
+ mSocket.receive(receivedPacket);
+
+ // don't start the test until after the first packet is received and increment
+ // mBytesReceived starting with the second packet.
+ long time = SystemClock.elapsedRealtime();
+ if (mStartMillis == 0) {
+ mStartMillis = time;
+ } else {
+ mBytesReceived += receivedPacket.getLength();
+ }
+ // there may not be another packet, update the stop time on every iteration.
+ mStopMillis = time;
+ } catch (SocketTimeoutException e) {
+ // sender has stopped sending data, do nothing and return.
+ } catch (IOException e) {
+ Log.e(TAG, "socket receive failed", e);
+ }
+ }
+ }
+ }
+
+ private long runIngressDataRateMeasurement(final Duration testDuration) throws Exception {
+ final RateMeasurementSocketReader reader = new RateMeasurementSocketReader(mSocket);
+ reader.startTest();
+ sendPacketsToTunInterfaceForDuration(mTunInterface, testDuration);
+ return reader.stopAndGetResult();
+ }
+
+ void waitForTcPoliceFilterInstalled(Duration timeout) throws IOException {
+ final String command = MessageFormat.format("tc filter show ingress dev {0}",
+ mTunInterface.getInterfaceName());
+ // wait for tc police to show up
+ final long startTime = SystemClock.elapsedRealtime();
+ final long timeoutTime = startTime + timeout.toMillis();
+ while (!SystemUtil.runShellCommand(command).contains("police")) {
+ assertLessThan("timed out waiting for tc police filter",
+ SystemClock.elapsedRealtime(), timeoutTime);
+ SystemClock.sleep(10);
+ }
+ Log.v(TAG, "waited " + (SystemClock.elapsedRealtime() - startTime)
+ + "ms for tc police filter to appear");
+ }
+
+ @Test
+ public void testIngressRateLimit_testLimit() throws Exception {
+ // If this value is too low, this test might become flaky because of the burst value that
+ // allows to send at a higher data rate for a short period of time. The faster the data rate
+ // and the longer the test, the less this test will be affected.
+ final long dataLimitInBytesPerSecond = 1_000_000; // 1MB/s
+ long resultInBytesPerSecond = runIngressDataRateMeasurement(Duration.ofSeconds(1));
+ assertGreaterThan("Failed initial test with rate limit disabled", resultInBytesPerSecond,
+ dataLimitInBytesPerSecond);
+
+ // enable rate limit and wait until the tc filter is installed before starting the test.
+ ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext,
+ dataLimitInBytesPerSecond);
+ waitForTcPoliceFilterInstalled(Duration.ofSeconds(1));
+
+ resultInBytesPerSecond = runIngressDataRateMeasurement(Duration.ofSeconds(10));
+ // Add 1% tolerance to reduce test flakiness. Burst size is constant at 128KiB.
+ assertLessThan("Failed test with rate limit enabled", resultInBytesPerSecond,
+ (long) (dataLimitInBytesPerSecond * 1.01));
+
+ ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext, -1);
+
+ resultInBytesPerSecond = runIngressDataRateMeasurement(Duration.ofSeconds(1));
+ assertGreaterThan("Failed test with rate limit disabled", resultInBytesPerSecond,
+ dataLimitInBytesPerSecond);
+ }
+
+ @Test
+ public void testIngressRateLimit_testSetting() {
+ int dataLimitInBytesPerSecond = 1_000_000;
+ ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext,
+ dataLimitInBytesPerSecond);
+ assertEquals(dataLimitInBytesPerSecond,
+ ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(mContext));
+ ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext, -1);
+ assertEquals(-1,
+ ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(mContext));
+ }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 16b3d5a..d5f1516 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -573,6 +573,12 @@
// is "<permission name>,<pid>,<uid>". PID+UID permissons have priority over generic ones.
private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
+ private void mockStringResource(int resId) {
+ doAnswer((inv) -> {
+ return "Mock string resource ID=" + inv.getArgument(0);
+ }).when(mInternalResources).getString(resId);
+ }
+
MockContext(Context base, ContentProvider settingsProvider) {
super(base);
@@ -585,6 +591,18 @@
}).when(mInternalResources)
.getStringArray(com.android.internal.R.array.networkAttributes);
+ final int[] stringResourcesToMock = new int[] {
+ com.android.internal.R.string.config_customVpnAlwaysOnDisconnectedDialogComponent,
+ com.android.internal.R.string.vpn_lockdown_config,
+ com.android.internal.R.string.vpn_lockdown_connected,
+ com.android.internal.R.string.vpn_lockdown_connecting,
+ com.android.internal.R.string.vpn_lockdown_disconnected,
+ com.android.internal.R.string.vpn_lockdown_error,
+ };
+ for (int resId : stringResourcesToMock) {
+ mockStringResource(resId);
+ }
+
mContentResolver = new MockContentResolver();
mContentResolver.addProvider(Settings.AUTHORITY, settingsProvider);
}
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index 785153a..e7f6245 100644
--- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
@@ -26,6 +26,8 @@
import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY
import com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED
import com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED
+import com.android.server.connectivity.FullScore.POLICY_IS_DESTROYED
+import com.android.server.connectivity.FullScore.POLICY_IS_UNMETERED
import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED
import com.android.server.connectivity.FullScore.POLICY_IS_VPN
import com.android.testutils.DevSdkIgnoreRule
@@ -47,7 +49,8 @@
validated: Boolean = false,
vpn: Boolean = false,
onceChosen: Boolean = false,
- acceptUnvalidated: Boolean = false
+ acceptUnvalidated: Boolean = false,
+ destroyed: Boolean = false
): FullScore {
val nac = NetworkAgentConfig.Builder().apply {
setUnvalidatedConnectivityAcceptable(acceptUnvalidated)
@@ -57,7 +60,7 @@
if (vpn) addTransportType(NetworkCapabilities.TRANSPORT_VPN)
if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}.build()
- return mixInScore(nc, nac, validated, false /* yieldToBadWifi */)
+ return mixInScore(nc, nac, validated, false /* yieldToBadWifi */, destroyed)
}
@Test
@@ -101,6 +104,7 @@
assertFailsWith<IllegalArgumentException> {
FullScore.policyNameOf(MAX_CS_MANAGED_POLICY + 1)
}
+ assertEquals("IS_UNMETERED", FullScore.policyNameOf(POLICY_IS_UNMETERED))
}
fun getAllPolicies() = Regex("POLICY_.*").let { nameRegex ->
@@ -118,6 +122,7 @@
assertTrue(ns.withPolicies(vpn = true).hasPolicy(POLICY_IS_VPN))
assertTrue(ns.withPolicies(onceChosen = true).hasPolicy(POLICY_EVER_USER_SELECTED))
assertTrue(ns.withPolicies(acceptUnvalidated = true).hasPolicy(POLICY_ACCEPT_UNVALIDATED))
+ assertTrue(ns.withPolicies(destroyed = true).hasPolicy(POLICY_IS_DESTROYED))
}
@Test
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 34bcf3f..aa4e4bb 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -63,8 +63,10 @@
import static com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+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.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -77,6 +79,7 @@
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.verify;
import static org.mockito.Mockito.when;
@@ -101,13 +104,13 @@
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.wifi.WifiInfo;
-import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.SimpleClock;
import android.provider.Settings;
+import android.system.ErrnoException;
import android.telephony.TelephonyManager;
import androidx.annotation.Nullable;
@@ -125,6 +128,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
+import com.android.testutils.TestBpfMap;
import com.android.testutils.TestableNetworkStatsProviderBinder;
import libcore.testing.io.TestIoUtils;
@@ -143,6 +147,7 @@
import java.time.ZoneOffset;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Tests for {@link NetworkStatsService}.
@@ -152,7 +157,8 @@
*/
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+// NetworkStatsService is not updatable before T, so tests do not need to be backwards compatible
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
private static final String TAG = "NetworkStatsServiceTest";
@@ -197,11 +203,16 @@
private HandlerThread mHandlerThread;
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
- private @Mock IBpfMap<U32, U8> mUidCounterSetMap;
- private @Mock IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap;
- private @Mock IBpfMap<StatsMapKey, StatsMapValue> mStatsMapA;
- private @Mock IBpfMap<StatsMapKey, StatsMapValue> mStatsMapB;
- private @Mock IBpfMap<UidStatsMapKey, StatsMapValue> mAppUidStatsMap;
+ private TestBpfMap<U32, U8> mUidCounterSetMap = spy(new TestBpfMap<>(U32.class, U8.class));
+
+ private TestBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap = new TestBpfMap<>(
+ CookieTagMapKey.class, CookieTagMapValue.class);
+ private TestBpfMap<StatsMapKey, StatsMapValue> mStatsMapA = new TestBpfMap<>(StatsMapKey.class,
+ StatsMapValue.class);
+ private TestBpfMap<StatsMapKey, StatsMapValue> mStatsMapB = new TestBpfMap<>(StatsMapKey.class,
+ StatsMapValue.class);
+ private TestBpfMap<UidStatsMapKey, StatsMapValue> mAppUidStatsMap = new TestBpfMap<>(
+ UidStatsMapKey.class, StatsMapValue.class);
private NetworkStatsService mService;
private INetworkStatsSession mSession;
@@ -1921,4 +1932,70 @@
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
}
+
+ private boolean cookieTagMapContainsUid(int uid) throws ErrnoException {
+ final AtomicBoolean found = new AtomicBoolean();
+ mCookieTagMap.forEach((k, v) -> {
+ if (v.uid == uid) {
+ found.set(true);
+ }
+ });
+ return found.get();
+ }
+
+ private static <K extends StatsMapKey, V extends StatsMapValue> boolean statsMapContainsUid(
+ TestBpfMap<K, V> map, int uid) throws ErrnoException {
+ final AtomicBoolean found = new AtomicBoolean();
+ map.forEach((k, v) -> {
+ if (k.uid == uid) {
+ found.set(true);
+ }
+ });
+ return found.get();
+ }
+
+ private void initBpfMapsWithTagData(int uid) throws ErrnoException {
+ // key needs to be unique, use some offset from uid.
+ mCookieTagMap.insertEntry(new CookieTagMapKey(1000 + uid), new CookieTagMapValue(uid, 1));
+ mCookieTagMap.insertEntry(new CookieTagMapKey(2000 + uid), new CookieTagMapValue(uid, 2));
+
+ mStatsMapA.insertEntry(new StatsMapKey(uid, 1, 0, 10), new StatsMapValue(5, 5000, 3, 3000));
+ mStatsMapA.insertEntry(new StatsMapKey(uid, 2, 0, 10), new StatsMapValue(5, 5000, 3, 3000));
+
+ mStatsMapB.insertEntry(new StatsMapKey(uid, 1, 0, 10), new StatsMapValue(0, 0, 0, 0));
+
+ mAppUidStatsMap.insertEntry(new UidStatsMapKey(uid), new StatsMapValue(10, 10000, 6, 6000));
+
+ mUidCounterSetMap.insertEntry(new U32(uid), new U8((short) 1));
+
+ assertTrue(cookieTagMapContainsUid(uid));
+ assertTrue(statsMapContainsUid(mStatsMapA, uid));
+ assertTrue(statsMapContainsUid(mStatsMapB, uid));
+ assertTrue(mAppUidStatsMap.containsKey(new UidStatsMapKey(uid)));
+ assertTrue(mUidCounterSetMap.containsKey(new U32(uid)));
+ }
+
+ @Test
+ public void testRemovingUidRemovesTagDataForUid() throws ErrnoException {
+ initBpfMapsWithTagData(UID_BLUE);
+ initBpfMapsWithTagData(UID_RED);
+
+ final Intent intent = new Intent(ACTION_UID_REMOVED);
+ intent.putExtra(EXTRA_UID, UID_BLUE);
+ mServiceContext.sendBroadcast(intent);
+
+ // assert that all UID_BLUE related tag data has been removed from the maps.
+ assertFalse(cookieTagMapContainsUid(UID_BLUE));
+ assertFalse(statsMapContainsUid(mStatsMapA, UID_BLUE));
+ assertFalse(statsMapContainsUid(mStatsMapB, UID_BLUE));
+ assertFalse(mAppUidStatsMap.containsKey(new UidStatsMapKey(UID_BLUE)));
+ assertFalse(mUidCounterSetMap.containsKey(new U32(UID_BLUE)));
+
+ // assert that UID_RED related tag data is still in the maps.
+ assertTrue(cookieTagMapContainsUid(UID_RED));
+ assertTrue(statsMapContainsUid(mStatsMapA, UID_RED));
+ assertTrue(statsMapContainsUid(mStatsMapB, UID_RED));
+ assertTrue(mAppUidStatsMap.containsKey(new UidStatsMapKey(UID_RED)));
+ assertTrue(mUidCounterSetMap.containsKey(new U32(UID_RED)));
+ }
}