Merge "Move tether_utils into utils folder" into main
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index b24e3ac..9e4e4a1 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -1,11 +1,12 @@
lorenzo@google.com
satk@google.com #{LAST_RESORT_SUGGESTION}
-# For cherry-picks of CLs that are already merged in aosp/master, or flaky test fixes.
+# For cherry-picks of CLs that are already merged in aosp/master, flaky test
+# fixes, or no-op refactors.
jchalard@google.com #{LAST_RESORT_SUGGESTION}
-# In addition to cherry-picks and flaky test fixes, also for APF firmware tests
-# (to verify correct behaviour of the wifi APF interpreter)
+# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
+# APF firmware tests (to verify correct behaviour of the wifi APF interpreter)
maze@google.com #{LAST_RESORT_SUGGESTION}
-# In addition to cherry-picks and flaky test fixes, also for incremental changes on NsdManager tests
-# to increase coverage for existing behavior, and testing of bug fixes in NsdManager
+# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
+# NsdManager tests
reminv@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 304a6ed..8a5d249 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -101,6 +101,7 @@
"block.o",
"clatd.o",
"dscpPolicy.o",
+ "gentle.o",
"netd.o",
"offload.o",
"offload@mainline.o",
@@ -112,7 +113,7 @@
],
prebuilts: [
"current_sdkinfo",
- "netbpfload.mainline.rc",
+ "netbpfload.33rc",
"netbpfload.35rc",
"ot-daemon.init.34rc",
],
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index a287b42..cccafd5 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -47,6 +47,7 @@
field public static final int TETHERING_INVALID = -1; // 0xffffffff
field public static final int TETHERING_NCM = 4; // 0x4
field public static final int TETHERING_USB = 1; // 0x1
+ field @FlaggedApi("com.android.net.flags.tethering_request_virtual") public static final int TETHERING_VIRTUAL = 7; // 0x7
field public static final int TETHERING_WIFI = 0; // 0x0
field public static final int TETHERING_WIFI_P2P = 3; // 0x3
field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 7b769d4..2963f87 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -68,6 +68,8 @@
public static class Flags {
static final String TETHERING_REQUEST_WITH_SOFT_AP_CONFIG =
"com.android.net.flags.tethering_request_with_soft_ap_config";
+ static final String TETHERING_REQUEST_VIRTUAL =
+ "com.android.net.flags.tethering_request_virtual";
}
private static final String TAG = TetheringManager.class.getSimpleName();
@@ -195,10 +197,18 @@
public static final int TETHERING_WIGIG = 6;
/**
+ * VIRTUAL tethering type.
+ * @hide
+ */
+ @FlaggedApi(Flags.TETHERING_REQUEST_VIRTUAL)
+ @SystemApi
+ public static final int TETHERING_VIRTUAL = 7;
+
+ /**
* The int value of last tethering type.
* @hide
*/
- public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG;
+ public static final int MAX_TETHERING_TYPE = TETHERING_VIRTUAL;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
diff --git a/Tethering/res/values-mcc310-mnc004-eu/strings.xml b/Tethering/res/values-mcc310-mnc004-eu/strings.xml
index c970dd7..ff2a505 100644
--- a/Tethering/res/values-mcc310-mnc004-eu/strings.xml
+++ b/Tethering/res/values-mcc310-mnc004-eu/strings.xml
@@ -18,7 +18,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="no_upstream_notification_title" msgid="3584617491053416666">"Konexioa partekatzeko aukerak ez du Interneteko konexiorik"</string>
<string name="no_upstream_notification_message" msgid="5626323795587558017">"Ezin dira konektatu gailuak"</string>
- <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desaktibatu konexioa partekatzeko aukera"</string>
+ <string name="no_upstream_notification_disable_button" msgid="868677179945695858">"Desaktibatu konexioa partekatzea"</string>
<string name="upstream_roaming_notification_title" msgid="2870229486619751829">"Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago"</string>
<string name="upstream_roaming_notification_message" msgid="5229740963392849544">"Baliteke tarifa gehigarriak ordaindu behar izatea ibiltaritza erabili bitartean"</string>
</resources>
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index d85d92f..29ced23 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -38,6 +38,7 @@
import static android.net.TetheringManager.TETHERING_INVALID;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHERING_WIGIG;
@@ -278,6 +279,7 @@
private TetheredInterfaceRequestShim mBluetoothIfaceRequest;
private String mConfiguredEthernetIface;
private String mConfiguredBluetoothIface;
+ private String mConfiguredVirtualIface;
private EthernetCallback mEthernetCallback;
private TetheredInterfaceCallbackShim mBluetoothCallback;
private SettingsObserver mSettingsObserver;
@@ -719,6 +721,9 @@
case TETHERING_ETHERNET:
result = setEthernetTethering(enable);
break;
+ case TETHERING_VIRTUAL:
+ result = setVirtualMachineTethering(enable);
+ break;
default:
Log.w(TAG, "Invalid tether type.");
result = TETHER_ERROR_UNKNOWN_TYPE;
@@ -972,6 +977,21 @@
}
}
+ private int setVirtualMachineTethering(final boolean enable) {
+ // TODO(340377643): Use bridge ifname when it's introduced, not fixed TAP ifname.
+ if (enable) {
+ mConfiguredVirtualIface = "avf_tap_fixed";
+ enableIpServing(
+ TETHERING_VIRTUAL,
+ mConfiguredVirtualIface,
+ getRequestedState(TETHERING_VIRTUAL));
+ } else if (mConfiguredVirtualIface != null) {
+ ensureIpServerStopped(mConfiguredVirtualIface);
+ mConfiguredVirtualIface = null;
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
void tether(String iface, int requestedState, final IIntResultListener listener) {
mHandler.post(() -> {
try {
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 120b871..5f04a98 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -42,7 +42,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -105,7 +104,7 @@
// Used to check if any tethering interface is available. Choose 200ms to be request timeout
// because the average interface requested time on cuttlefish@acloud is around 10ms.
// See TetheredInterfaceRequester.getInterface, isInterfaceForTetheringAvailable.
- private static final int AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS = 200;
+ private static final int SHORT_TIMEOUT_MS = 1000;
private static final int TETHER_REACHABILITY_ATTEMPTS = 20;
protected static final long WAIT_RA_TIMEOUT_MS = 2000;
@@ -154,7 +153,7 @@
private boolean mRunTests;
private HandlerThread mHandlerThread;
private Handler mHandler;
- private TetheredInterfaceRequester mTetheredInterfaceRequester;
+ protected TetheredInterfaceRequester mTetheredInterfaceRequester;
// Late initialization in initTetheringTester().
private TapPacketReader mUpstreamReader;
@@ -245,12 +244,14 @@
maybeUnregisterTetheringEventCallback(mTetheringEventCallback);
mTetheringEventCallback = null;
- runAsShell(NETWORK_SETTINGS, () -> mTetheredInterfaceRequester.release());
setIncludeTestInterfaces(false);
}
@After
public void tearDown() throws Exception {
+ if (mTetheredInterfaceRequester != null) {
+ mTetheredInterfaceRequester.release();
+ }
try {
if (mRunTests) cleanUp();
} finally {
@@ -263,33 +264,17 @@
}
}
- protected static boolean isInterfaceForTetheringAvailable() throws Exception {
- // Before T, all ethernet interfaces could be used for server mode. Instead of
- // waiting timeout, just checking whether the system currently has any
- // ethernet interface is more reliable.
- if (!SdkLevel.isAtLeastT()) {
- return runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS, () -> sEm.isAvailable());
- }
-
+ protected boolean isInterfaceForTetheringAvailable() throws Exception {
// If previous test case doesn't release tethering interface successfully, the other tests
// after that test may be skipped as unexcepted.
// TODO: figure out a better way to check default tethering interface existenion.
- final TetheredInterfaceRequester requester = new TetheredInterfaceRequester();
- try {
- // Use short timeout (200ms) for requesting an existing interface, if any, because
- // it should reurn faster than requesting a new tethering interface. Using default
- // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
- // test module timeout on internal testing.
- // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
- // this check into #setUpOnce.
- return requester.getInterface(AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS) != null;
- } catch (TimeoutException e) {
- return false;
- } finally {
- runAsShell(NETWORK_SETTINGS, () -> {
- requester.release();
- });
- }
+ // Use short timeout (200ms) for requesting an existing interface, if any, because
+ // it should reurn faster than requesting a new tethering interface. Using default
+ // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
+ // test module timeout on internal testing.
+ // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
+ // this check into #setUpOnce.
+ return mTetheredInterfaceRequester.isPhysicalInterfaceAvailable(SHORT_TIMEOUT_MS);
}
protected static void setIncludeTestInterfaces(boolean include) {
@@ -304,14 +289,6 @@
});
}
- protected String getTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.getInterface();
- }
-
- protected CompletableFuture<String> requestTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.requestInterface();
- }
-
protected static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
long timeoutMs) {
final long deadline = SystemClock.uptimeMillis() + timeoutMs;
@@ -605,6 +582,11 @@
private TetheredInterfaceRequest mRequest;
private final CompletableFuture<String> mFuture = new CompletableFuture<>();
+ TetheredInterfaceRequester() {
+ mRequest = runAsShell(NETWORK_SETTINGS, () ->
+ sEm.requestTetheredInterface(c -> c.run() /* executor */, this));
+ }
+
@Override
public void onAvailable(String iface) {
Log.d(TAG, "Ethernet interface available: " + iface);
@@ -616,28 +598,21 @@
mFuture.completeExceptionally(new IllegalStateException("onUnavailable received"));
}
- public CompletableFuture<String> requestInterface() {
- assertNull("BUG: more than one tethered interface request", mRequest);
- Log.d(TAG, "Requesting tethered interface");
- mRequest = runAsShell(NETWORK_SETTINGS, () ->
- sEm.requestTetheredInterface(c -> c.run() /* executor */, this));
- return mFuture;
- }
-
- public String getInterface(int timeout) throws Exception {
- return requestInterface().get(timeout, TimeUnit.MILLISECONDS);
+ public boolean isPhysicalInterfaceAvailable(int timeout) {
+ try {
+ final String iface = mFuture.get(timeout, TimeUnit.MILLISECONDS);
+ return !iface.startsWith("testtap");
+ } catch (Exception e) {
+ return false;
+ }
}
public String getInterface() throws Exception {
- return getInterface(TIMEOUT_MS);
+ return mFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public void release() {
- if (mRequest != null) {
- mFuture.obtrudeException(new IllegalStateException("Request already released"));
- mRequest.release();
- mRequest = null;
- }
+ runAsShell(NETWORK_SETTINGS, () -> mRequest.release());
}
}
@@ -658,7 +633,10 @@
lp.setLinkAddresses(addresses);
lp.setDnsServers(dnses);
- return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(sContext, lp, TIMEOUT_MS));
+ // TODO: initTestNetwork can take up to 15 seconds on a workstation. Investigate when and
+ // why this is the case. It is unclear whether a 30 second timeout is enough when running
+ // these tests in the much slower test infra.
+ return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(sContext, lp, 30_000));
}
protected void sendDownloadPacketUdp(@NonNull final InetAddress srcIp,
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index c54d1b4..5c258b2 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -59,7 +59,7 @@
import com.android.testutils.NetworkStackModuleTest;
import com.android.testutils.TapPacketReader;
-import org.junit.BeforeClass;
+import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,8 +75,6 @@
import java.util.Collection;
import java.util.List;
import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@RunWith(AndroidJUnit4.class)
@@ -151,33 +149,14 @@
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
};
- /** Enable/disable tethering once before running the tests. */
- @BeforeClass
- public static void setUpOnce() throws Exception {
- // The first test case may experience tethering restart with IP conflict handling.
- // Tethering would cache the last upstreams so that the next enabled tethering avoids
- // picking up the address that is in conflict with the upstreams. To protect subsequent
- // tests, turn tethering on and off before running them.
- MyTetheringEventCallback callback = null;
- TestNetworkInterface testIface = null;
- assumeTrue(sEm != null);
- try {
- // If the physical ethernet interface is available, do nothing.
- if (isInterfaceForTetheringAvailable()) return;
-
- testIface = createTestInterface();
- setIncludeTestInterfaces(true);
-
- callback = enableEthernetTethering(testIface.getInterfaceName(), null);
- callback.awaitUpstreamChanged(true /* throwTimeoutException */);
- } catch (TimeoutException e) {
- Log.d(TAG, "WARNNING " + e);
- } finally {
- maybeCloseTestInterface(testIface);
- maybeUnregisterTetheringEventCallback(callback);
-
- setIncludeTestInterfaces(false);
- }
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ // TODO: See b/318121782#comment4. Register an ethernet InterfaceStateListener, and wait for
+ // the callback to report client mode. This happens as soon as both
+ // TetheredInterfaceRequester and the tethering code itself have released the interface,
+ // i.e. after stopTethering() has completed.
+ Thread.sleep(3000);
}
@Test
@@ -201,7 +180,7 @@
Log.d(TAG, "Including test interfaces");
setIncludeTestInterfaces(true);
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -223,8 +202,6 @@
// This test requires manipulating packets. Skip if there is a physical Ethernet connected.
assumeFalse(isInterfaceForTetheringAvailable());
- CompletableFuture<String> futureIface = requestTetheredInterface();
-
setIncludeTestInterfaces(true);
TestNetworkInterface downstreamIface = null;
@@ -234,7 +211,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -264,7 +241,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -338,7 +315,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -388,7 +365,7 @@
MyTetheringEventCallback tetheringEventCallback = null;
try {
// Get an interface to use.
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
// Enable Ethernet tethering and check that it starts.
tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
@@ -509,17 +486,23 @@
// TODO: test BPF offload maps {rule, stats}.
}
- // Test network topology:
- //
- // public network (rawip) private network
- // | UE |
- // +------------+ V +------------+------------+ V +------------+
- // | Sever +---------+ Upstream | Downstream +---------+ Client |
- // +------------+ +------------+------------+ +------------+
- // remote ip public ip private ip
- // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
- //
- private void runUdp4Test() throws Exception {
+
+ /**
+ * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
+ * using which data path.
+ */
+ @Test
+ public void testTetherUdpV4() throws Exception {
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
+ //
final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
toList(TEST_IP4_DNS));
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
@@ -541,15 +524,6 @@
sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
}
- /**
- * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
- * using which data path.
- */
- @Test
- public void testTetherUdpV4() throws Exception {
- runUdp4Test();
- }
-
// Test network topology:
//
// public network (rawip) private network
@@ -599,7 +573,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
@@ -707,7 +681,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
// [1] Send DNS query.
@@ -751,7 +725,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
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 d5d71bc..47aebe8 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -22,19 +22,24 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.net.MacAddress;
import android.os.Build;
+import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.ArrayMap;
import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -42,10 +47,18 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.io.File;
import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -56,11 +69,26 @@
private static final int TEST_MAP_SIZE = 16;
private static final String TETHER_DOWNSTREAM6_FS_PATH =
"/sys/fs/bpf/tethering/map_test_tether_downstream6_map";
+ private static final String TETHER2_DOWNSTREAM6_FS_PATH =
+ "/sys/fs/bpf/tethering/map_test_tether2_downstream6_map";
+ private static final String TETHER3_DOWNSTREAM6_FS_PATH =
+ "/sys/fs/bpf/tethering/map_test_tether3_downstream6_map";
private ArrayMap<TetherDownstream6Key, Tether6Value> mTestData;
private BpfMap<TetherDownstream6Key, Tether6Value> mTestMap;
+ private final boolean mShouldTestSingleWriterMap;
+
+ @Parameterized.Parameters
+ public static Collection<Boolean> shouldTestSingleWriterMap() {
+ return Arrays.asList(true, false);
+ }
+
+ public BpfMapTest(boolean shouldTestSingleWriterMap) {
+ mShouldTestSingleWriterMap = shouldTestSingleWriterMap;
+ }
+
@BeforeClass
public static void setupOnce() {
System.loadLibrary(getTetheringJniLibraryName());
@@ -82,11 +110,16 @@
initTestMap();
}
- private void initTestMap() throws Exception {
- mTestMap = new BpfMap<>(
- TETHER_DOWNSTREAM6_FS_PATH,
- TetherDownstream6Key.class, Tether6Value.class);
+ private BpfMap<TetherDownstream6Key, Tether6Value> openTestMap() throws Exception {
+ return mShouldTestSingleWriterMap
+ ? SingleWriterBpfMap.getSingleton(TETHER2_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class)
+ : new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, TetherDownstream6Key.class,
+ Tether6Value.class);
+ }
+ private void initTestMap() throws Exception {
+ mTestMap = openTestMap();
mTestMap.forEach((key, value) -> {
try {
assertTrue(mTestMap.deleteEntry(key));
@@ -125,7 +158,7 @@
assertEquals(OsConstants.EPERM, expected.errno);
}
}
- try (BpfMap writeOnlyMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_WRONLY,
+ try (BpfMap writeOnlyMap = new BpfMap<>(TETHER3_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_WRONLY,
TetherDownstream6Key.class, Tether6Value.class)) {
assertNotNull(writeOnlyMap);
try {
@@ -357,6 +390,25 @@
}
@Test
+ public void testMapContentsCorrectOnOpen() throws Exception {
+ final BpfMap<TetherDownstream6Key, Tether6Value> map1, map2;
+
+ map1 = openTestMap();
+ map1.clear();
+ for (int i = 0; i < mTestData.size(); i++) {
+ map1.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ }
+
+ // We can't close and reopen map1, because close does nothing. Open another map instead.
+ map2 = openTestMap();
+ for (int i = 0; i < mTestData.size(); i++) {
+ assertEquals(mTestData.valueAt(i), map2.getValue(mTestData.keyAt(i)));
+ }
+
+ map1.clear();
+ }
+
+ @Test
public void testInsertOverflow() throws Exception {
final ArrayMap<TetherDownstream6Key, Tether6Value> testData =
new ArrayMap<>();
@@ -396,8 +448,18 @@
}
}
- private static int getNumOpenFds() {
- return new File("/proc/" + Os.getpid() + "/fd").listFiles().length;
+ private static int getNumOpenBpfMapFds() throws Exception {
+ int numFds = 0;
+ File[] openFiles = new File("/proc/self/fd").listFiles();
+ for (int i = 0; i < openFiles.length; i++) {
+ final Path path = openFiles[i].toPath();
+ if (!Files.isSymbolicLink(path)) continue;
+ if ("anon_inode:bpf-map".equals(Files.readSymbolicLink(path).toString())) {
+ numFds++;
+ }
+ }
+ assertNotEquals("Couldn't find any BPF map fds opened by this process", 0, numFds);
+ return numFds;
}
@Test
@@ -406,7 +468,7 @@
// cache, expect that the fd amount is not increased in the iterations.
// See the comment of BpfMap#close.
final int iterations = 1000;
- final int before = getNumOpenFds();
+ final int before = getNumOpenBpfMapFds();
for (int i = 0; i < iterations; i++) {
try (BpfMap<TetherDownstream6Key, Tether6Value> map = new BpfMap<>(
TETHER_DOWNSTREAM6_FS_PATH,
@@ -414,15 +476,74 @@
// do nothing
}
}
- final int after = getNumOpenFds();
+ final int after = getNumOpenBpfMapFds();
// Check that the number of open fds is the same as before.
- // If this exact match becomes flaky, we probably need to distinguish that fd is belong
- // to "bpf-map".
- // ex:
- // $ adb shell ls -all /proc/16196/fd
- // [..] network_stack 64 2022-07-26 22:01:02.300002956 +0800 749 -> anon_inode:bpf-map
- // [..] network_stack 64 2022-07-26 22:01:02.188002956 +0800 75 -> anon_inode:[eventfd]
assertEquals("Fd leak after " + iterations + " iterations: ", before, after);
}
+
+ @Test
+ public void testNullKey() {
+ assertThrows(NullPointerException.class, () ->
+ mTestMap.insertOrReplaceEntry(null, mTestData.valueAt(0)));
+ }
+
+ private void runBenchmarkThread(BpfMap<TetherDownstream6Key, Tether6Value> map,
+ CompletableFuture<Integer> future, int runtimeMs) {
+ int numReads = 0;
+ final Random r = new Random();
+ final long start = SystemClock.elapsedRealtime();
+ final long stop = start + runtimeMs;
+ while (SystemClock.elapsedRealtime() < stop) {
+ try {
+ final Tether6Value v = map.getValue(mTestData.keyAt(r.nextInt(mTestData.size())));
+ assertNotNull(v);
+ numReads++;
+ } catch (Exception e) {
+ future.completeExceptionally(e);
+ return;
+ }
+ }
+ future.complete(numReads);
+ }
+
+ @Test
+ public void testSingleWriterCacheEffectiveness() throws Exception {
+ assumeTrue(mShouldTestSingleWriterMap);
+ // Benchmark parameters.
+ final int timeoutMs = 5_000; // Only hit if threads don't complete.
+ final int benchmarkTimeMs = 2_000;
+ final int minReads = 50;
+ // Local testing on cuttlefish suggests that caching is ~10x faster.
+ // Only require 3x to reduce test flakiness.
+ final int expectedSpeedup = 3;
+
+ final BpfMap cachedMap = SingleWriterBpfMap.getSingleton(TETHER2_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+ final BpfMap uncachedMap = new BpfMap(TETHER_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+
+ // Ensure the maps are not empty.
+ for (int i = 0; i < mTestData.size(); i++) {
+ cachedMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ uncachedMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ }
+
+ final CompletableFuture<Integer> cachedResult = new CompletableFuture<>();
+ final CompletableFuture<Integer> uncachedResult = new CompletableFuture<>();
+
+ new Thread(() -> runBenchmarkThread(uncachedMap, uncachedResult, benchmarkTimeMs)).start();
+ new Thread(() -> runBenchmarkThread(cachedMap, cachedResult, benchmarkTimeMs)).start();
+
+ final int cached = cachedResult.get(timeoutMs, TimeUnit.MILLISECONDS);
+ final int uncached = uncachedResult.get(timeoutMs, TimeUnit.MILLISECONDS);
+
+ // Uncomment to see benchmark results.
+ // fail("Cached " + cached + ", uncached " + uncached + ": " + cached / uncached +"x");
+
+ assertTrue("Less than " + minReads + "cached reads observed", cached > minReads);
+ assertTrue("Less than " + minReads + "uncached reads observed", uncached > minReads);
+ assertTrue("Cached map not at least " + expectedSpeedup + "x faster",
+ cached > expectedSpeedup * uncached);
+ }
}
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 1958aa8..9e67415 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -85,6 +85,16 @@
}
bpf {
+ name: "gentle.o",
+ srcs: ["gentle.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ sub_dir: "net_shared",
+}
+
+bpf {
name: "offload.o",
srcs: ["offload.c"],
cflags: [
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index 152dda6..353525e 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -20,7 +20,7 @@
#include <stdint.h>
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
#include "bpf_helpers.h"
@@ -76,3 +76,4 @@
LICENSE("Apache 2.0");
CRITICAL("ConnectivityNative");
DISABLE_BTF_ON_USER_BUILDS();
+DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index f3c7de5..1511ee5 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -35,6 +35,7 @@
// this returns 0 iff skb->sk is NULL
static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_cookie;
+static uint64_t (*bpf_get_sk_cookie)(struct bpf_sock* sk) = (void*)BPF_FUNC_get_socket_cookie;
static uint32_t (*bpf_get_socket_uid)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_uid;
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index f83e5ae..95e9a4c 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -31,7 +31,7 @@
#include <linux/udp.h>
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
@@ -431,3 +431,4 @@
LICENSE("Apache 2.0");
CRITICAL("Connectivity");
DISABLE_BTF_ON_USER_BUILDS();
+DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index ed114e4..8bb8ad5 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -28,7 +28,7 @@
#include <string.h>
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
#include "bpf_helpers.h"
#include "dscpPolicy.h"
@@ -239,3 +239,4 @@
LICENSE("Apache 2.0");
CRITICAL("Connectivity");
DISABLE_BTF_ON_USER_BUILDS();
+DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/bpf_progs/gentle.c b/bpf_progs/gentle.c
new file mode 100644
index 0000000..ab9620e
--- /dev/null
+++ b/bpf_progs/gentle.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+
+#include "bpf_helpers.h"
+#include "bpf_net_helpers.h"
+
+DEFINE_BPF_MAP_GRW(test, ARRAY, int, uint64_t, 1, AID_SYSTEM)
+
+DEFINE_BPF_PROG("skfilter/accept", AID_ROOT, AID_SYSTEM, accept)
+(struct __sk_buff *skb) {
+ return 1;
+}
+
+LICENSE("Apache 2.0");
+DISABLE_BTF_ON_USER_BUILDS();
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index c3acaad..5ce2227 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -15,7 +15,7 @@
*/
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
#include <bpf_helpers.h>
#include <linux/bpf.h>
@@ -97,19 +97,22 @@
DEFINE_BPF_MAP_NO_NETD(ingress_discard_map, HASH, IngressDiscardKey, IngressDiscardValue,
INGRESS_DISCARD_MAP_SIZE)
+DEFINE_BPF_MAP_RW_NETD(lock_array_test_map, ARRAY, uint32_t, bool, 1)
+DEFINE_BPF_MAP_RW_NETD(lock_hash_test_map, HASH, uint32_t, bool, 1)
+
/* never actually used from ebpf */
DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE)
// A single-element configuration array, packet tracing is enabled when 'true'.
DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG)
// A ring buffer on which packet information is pushed.
DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG);
DEFINE_BPF_MAP_RO_NETD(data_saver_enabled_map, ARRAY, uint32_t, bool,
@@ -139,6 +142,11 @@
#define DEFINE_NETD_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
DEFINE_NETD_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE)
+#define DEFINE_NETD_V_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV) \
+ DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV, \
+ KVER_INF, BPFLOADER_MAINLINE_V_VERSION, BPFLOADER_MAX_VER, MANDATORY, \
+ "fs_bpf_netd_readonly", "", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
+
// programs that only need to be usable by the system server
#define DEFINE_SYS_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE, KVER_INF, \
@@ -519,7 +527,7 @@
// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace_user", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
"fs_bpf_netd_readonly", "",
IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -529,7 +537,7 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace, KVER_5_8, KVER_INF,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -551,7 +559,7 @@
// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace_user", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
"fs_bpf_netd_readonly", "",
IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -561,7 +569,7 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace, KVER_5_8, KVER_INF,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -666,13 +674,87 @@
return permissions ? *permissions : BPF_PERMISSION_INTERNET;
}
-DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
+DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet_create", AID_ROOT, AID_ROOT, inet_socket_create,
KVER_4_14)
(struct bpf_sock* sk) {
// A return value of 1 means allow, everything else means deny.
return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? 1 : 0;
}
+DEFINE_NETD_V_BPF_PROG_KVER("cgroupsockrelease/inet_release", AID_ROOT, AID_ROOT,
+ inet_socket_release, KVER_5_10)
+(struct bpf_sock* sk) {
+ uint64_t cookie = bpf_get_sk_cookie(sk);
+ if (cookie) bpf_cookie_tag_map_delete_elem(&cookie);
+
+ return 1;
+}
+
+static __always_inline inline int check_localhost(struct bpf_sock_addr *ctx) {
+ // See include/uapi/linux/bpf.h:
+ //
+ // struct bpf_sock_addr {
+ // __u32 user_family; // R: 4 byte
+ // __u32 user_ip4; // BE, R: 1,2,4-byte, W: 4-byte
+ // __u32 user_ip6[4]; // BE, R: 1,2,4,8-byte, W: 4,8-byte
+ // __u32 user_port; // BE, R: 1,2,4-byte, W: 4-byte
+ // __u32 family; // R: 4 byte
+ // __u32 type; // R: 4 byte
+ // __u32 protocol; // R: 4 byte
+ // __u32 msg_src_ip4; // BE, R: 1,2,4-byte, W: 4-byte
+ // __u32 msg_src_ip6[4]; // BE, R: 1,2,4,8-byte, W: 4,8-byte
+ // __bpf_md_ptr(struct bpf_sock *, sk);
+ // };
+ return 1;
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("connect4/inet4_connect", AID_ROOT, AID_ROOT, inet4_connect, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("connect6/inet6_connect", AID_ROOT, AID_ROOT, inet6_connect, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("recvmsg4/udp4_recvmsg", AID_ROOT, AID_ROOT, udp4_recvmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("recvmsg6/udp6_recvmsg", AID_ROOT, AID_ROOT, udp6_recvmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("sendmsg4/udp4_sendmsg", AID_ROOT, AID_ROOT, udp4_sendmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("sendmsg6/udp6_sendmsg", AID_ROOT, AID_ROOT, udp6_sendmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("getsockopt/prog", AID_ROOT, AID_ROOT, getsockopt_prog, KVER_5_4)
+(struct bpf_sockopt *ctx) {
+ // Tell kernel to return 'original' kernel reply (instead of the bpf modified buffer)
+ // This is important if the answer is larger than PAGE_SIZE (max size this bpf hook can provide)
+ ctx->optlen = 0;
+ return 1; // ALLOW
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("setsockopt/prog", AID_ROOT, AID_ROOT, setsockopt_prog, KVER_5_4)
+(struct bpf_sockopt *ctx) {
+ // Tell kernel to use/process original buffer provided by userspace.
+ // This is important if it is larger than PAGE_SIZE (max size this bpf hook can handle).
+ ctx->optlen = 0;
+ return 1; // ALLOW
+}
+
LICENSE("Apache 2.0");
CRITICAL("Connectivity and netd");
DISABLE_BTF_ON_USER_BUILDS();
+DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 8a56b4a..332979b 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -155,7 +155,16 @@
ASSERT_STRING_EQUAL(XT_BPF_ALLOWLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilter_allowlist_xtbpf");
ASSERT_STRING_EQUAL(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 CGROUP_INET_CREATE_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
+#define CGROUP_INET_RELEASE_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsockrelease_inet_release"
+#define CGROUP_CONNECT4_PROG_PATH BPF_NETD_PATH "prog_netd_connect4_inet4_connect"
+#define CGROUP_CONNECT6_PROG_PATH BPF_NETD_PATH "prog_netd_connect6_inet6_connect"
+#define CGROUP_UDP4_RECVMSG_PROG_PATH BPF_NETD_PATH "prog_netd_recvmsg4_udp4_recvmsg"
+#define CGROUP_UDP6_RECVMSG_PROG_PATH BPF_NETD_PATH "prog_netd_recvmsg6_udp6_recvmsg"
+#define CGROUP_UDP4_SENDMSG_PROG_PATH BPF_NETD_PATH "prog_netd_sendmsg4_udp4_sendmsg"
+#define CGROUP_UDP6_SENDMSG_PROG_PATH BPF_NETD_PATH "prog_netd_sendmsg6_udp6_sendmsg"
+#define CGROUP_GETSOCKOPT_PROG_PATH BPF_NETD_PATH "prog_netd_getsockopt_prog"
+#define CGROUP_SETSOCKOPT_PROG_PATH BPF_NETD_PATH "prog_netd_setsockopt_prog"
#define TC_BPF_INGRESS_ACCOUNT_PROG_NAME "prog_netd_schedact_ingress_account"
#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_NETD_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 4f152bf..c7ac059 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -28,11 +28,11 @@
// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
// ship a different file than for later versions, but we need bpfloader v0.25+
// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
#else /* MAINLINE */
-// The resulting .o needs to load on the Android S bpfloader
+// The resulting .o needs to load on the Android S & T bpfloaders
#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
+#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
#endif /* MAINLINE */
// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
@@ -878,3 +878,4 @@
LICENSE("Apache 2.0");
CRITICAL("Connectivity (Tethering)");
DISABLE_BTF_ON_USER_BUILDS();
+DISABLE_ON_MAINLINE_BEFORE_U_QPR3();
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index fff3512..6a4471c 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -45,6 +45,10 @@
// Used only by TetheringPrivilegedTests, not by production code.
DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
TETHERING_GID)
+DEFINE_BPF_MAP_GRW(tether2_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
+ TETHERING_GID)
+DEFINE_BPF_MAP_GRW(tether3_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
+ TETHERING_GID)
// Used only by BpfBitmapTest, not by production code.
DEFINE_BPF_MAP_GRW(bitmap, ARRAY, int, uint64_t, 2, TETHERING_GID)
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 6c3e89d..b320b61 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -107,3 +107,19 @@
description: "Flag for BLOCKED_REASON_NETWORK_RESTRICTED API"
bug: "339559837"
}
+
+flag {
+ name: "net_capability_not_bandwidth_constrained"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED API"
+ bug: "343823469"
+}
+
+flag {
+ name: "tethering_request_virtual"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for introducing TETHERING_VIRTUAL type"
+ bug: "340376953"
+}
diff --git a/framework/api/current.txt b/framework/api/current.txt
index ef8415c..7bc0cf3 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -337,6 +337,7 @@
field public static final int NET_CAPABILITY_MCX = 23; // 0x17
field public static final int NET_CAPABILITY_MMS = 0; // 0x0
field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21
+ field @FlaggedApi("com.android.net.flags.net_capability_not_bandwidth_constrained") public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37; // 0x25
field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14
field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 0b37fa5..4eaf973 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -28,6 +28,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresApi;
@@ -1207,6 +1208,16 @@
})
public @interface FirewallRule {}
+ /** @hide */
+ public static final long FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS = 1L;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, prefix = "FEATURE_", value = {
+ FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS
+ })
+ public @interface ConnectivityManagerFeature {}
+
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
* case of the static set/getProcessDefaultNetwork methods and from the Network class.
@@ -1220,6 +1231,14 @@
@GuardedBy("mTetheringEventCallbacks")
private TetheringManager mTetheringManager;
+ private final Object mEnabledConnectivityManagerFeaturesLock = new Object();
+ // mEnabledConnectivityManagerFeatures is lazy-loaded in this ConnectivityManager instance, but
+ // fetched from ConnectivityService, where it is loaded in ConnectivityService startup, so it
+ // should have consistent values.
+ @GuardedBy("sEnabledConnectivityManagerFeaturesLock")
+ @ConnectivityManagerFeature
+ private Long mEnabledConnectivityManagerFeatures = null;
+
private TetheringManager getTetheringManager() {
synchronized (mTetheringEventCallbacks) {
if (mTetheringManager == null) {
@@ -4529,6 +4548,20 @@
return request;
}
+ private boolean isFeatureEnabled(@ConnectivityManagerFeature long connectivityManagerFeature) {
+ synchronized (mEnabledConnectivityManagerFeaturesLock) {
+ if (mEnabledConnectivityManagerFeatures == null) {
+ try {
+ mEnabledConnectivityManagerFeatures =
+ mService.getEnabledConnectivityManagerFeatures();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ return (mEnabledConnectivityManagerFeatures & connectivityManagerFeature) != 0;
+ }
+ }
+
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) {
return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType,
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 55c7085..f9de8ed 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -259,4 +259,6 @@
void setTestLowTcpPollingTimerForKeepalive(long timeMs);
IBinder getRoutingCoordinatorService();
+
+ long getEnabledConnectivityManagerFeatures();
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 45efbfe..6a14bde 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -36,9 +36,11 @@
import android.os.Process;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Range;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BitUtils;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkCapabilitiesUtils;
@@ -134,6 +136,8 @@
"com.android.net.flags.request_restricted_wifi";
static final String SUPPORT_TRANSPORT_SATELLITE =
"com.android.net.flags.support_transport_satellite";
+ static final String NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED =
+ "com.android.net.flags.net_capability_not_bandwidth_constrained";
}
/**
@@ -458,6 +462,7 @@
NET_CAPABILITY_PRIORITIZE_LATENCY,
NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
NET_CAPABILITY_LOCAL_NETWORK,
+ NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED,
})
public @interface NetCapability { }
@@ -740,7 +745,26 @@
@FlaggedApi(Flags.FLAG_NET_CAPABILITY_LOCAL_NETWORK)
public static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_LOCAL_NETWORK;
+ /**
+ * Indicates that this is not a bandwidth-constrained network.
+ *
+ * Starting from {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this capability is by default
+ * set in {@link NetworkRequest}s and true for most networks.
+ *
+ * If a network lacks this capability, it is bandwidth-constrained. Bandwidth constrained
+ * networks cannot support high-bandwidth data transfers and applications that request and use
+ * them must ensure that they limit bandwidth usage to below the values returned by
+ * {@link #getLinkDownstreamBandwidthKbps()} and {@link #getLinkUpstreamBandwidthKbps()} and
+ * limit the frequency of their network usage. If applications perform high-bandwidth data
+ * transfers on constrained networks or perform network access too frequently, the system may
+ * block the app's access to the network. The system may take other measures to reduce network
+ * usage on constrained networks, such as disabling network access to apps that are not in the
+ * foreground.
+ */
+ @FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37;
+
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
// Set all bits up to the MAX_NET_CAPABILITY-th bit
private static final long ALL_VALID_CAPABILITIES = (2L << MAX_NET_CAPABILITY) - 1;
@@ -784,10 +808,17 @@
/**
* Capabilities that are set by default when the object is constructed.
*/
- private static final long DEFAULT_CAPABILITIES =
- (1L << NET_CAPABILITY_NOT_RESTRICTED) |
- (1L << NET_CAPABILITY_TRUSTED) |
- (1L << NET_CAPABILITY_NOT_VPN);
+ private static final long DEFAULT_CAPABILITIES;
+ static {
+ long defaultCapabilities =
+ (1L << NET_CAPABILITY_NOT_RESTRICTED)
+ | (1L << NET_CAPABILITY_TRUSTED)
+ | (1L << NET_CAPABILITY_NOT_VPN);
+ if (SdkLevel.isAtLeastV()) {
+ defaultCapabilities |= (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+ }
+ DEFAULT_CAPABILITIES = defaultCapabilities;
+ }
/**
* Capabilities that are managed by ConnectivityService.
@@ -814,7 +845,9 @@
(1L << NET_CAPABILITY_NOT_ROAMING) |
(1L << NET_CAPABILITY_NOT_CONGESTED) |
(1L << NET_CAPABILITY_NOT_SUSPENDED) |
- (1L << NET_CAPABILITY_NOT_VCN_MANAGED);
+ (1L << NET_CAPABILITY_NOT_VCN_MANAGED) |
+ (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+
/**
* Extra allowed capabilities for test networks that do not have TRANSPORT_CELLULAR. Test
@@ -843,7 +876,10 @@
// If the given capability was previously added to the list of forbidden capabilities
// then the capability will also be removed from the list of forbidden capabilities.
// TODO: Add forbidden capabilities to the public API
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "addCapability is called with invalid capability: " + capability);
+ return this;
+ }
mNetworkCapabilities |= 1L << capability;
// remove from forbidden capability list
mForbiddenNetworkCapabilities &= ~(1L << capability);
@@ -864,7 +900,10 @@
* @hide
*/
public void addForbiddenCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "addForbiddenCapability is called with invalid capability: " + capability);
+ return;
+ }
mForbiddenNetworkCapabilities |= 1L << capability;
mNetworkCapabilities &= ~(1L << capability); // remove from requested capabilities
}
@@ -878,7 +917,10 @@
* @hide
*/
public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "removeCapability is called with invalid capability: " + capability);
+ return this;
+ }
final long mask = ~(1L << capability);
mNetworkCapabilities &= mask;
return this;
@@ -893,7 +935,11 @@
* @hide
*/
public @NonNull NetworkCapabilities removeForbiddenCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG,
+ "removeForbiddenCapability is called with invalid capability: " + capability);
+ return this;
+ }
mForbiddenNetworkCapabilities &= ~(1L << capability);
return this;
}
@@ -2589,6 +2635,7 @@
case NET_CAPABILITY_PRIORITIZE_LATENCY: return "PRIORITIZE_LATENCY";
case NET_CAPABILITY_PRIORITIZE_BANDWIDTH: return "PRIORITIZE_BANDWIDTH";
case NET_CAPABILITY_LOCAL_NETWORK: return "LOCAL_NETWORK";
+ case NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED: return "NOT_BANDWIDTH_CONSTRAINED";
default: return Integer.toString(capability);
}
}
@@ -2632,12 +2679,6 @@
return capability >= 0 && capability <= MAX_NET_CAPABILITY;
}
- private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
- if (!isValidCapability(capability)) {
- throw new IllegalArgumentException("NetworkCapability " + capability + " out of range");
- }
- }
-
private static boolean isValidEnterpriseId(
@NetworkCapabilities.EnterpriseId int enterpriseId) {
return enterpriseId >= NET_ENTERPRISE_ID_1
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index f7600b2..502ac6f 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -20,6 +20,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -286,7 +287,8 @@
NET_CAPABILITY_PARTIAL_CONNECTIVITY,
NET_CAPABILITY_TEMPORARILY_NOT_METERED,
NET_CAPABILITY_TRUSTED,
- NET_CAPABILITY_VALIDATED);
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
private final NetworkCapabilities mNetworkCapabilities;
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index 6b18629..f92cdbb 100644
--- a/framework/src/android/net/apf/ApfCapabilities.java
+++ b/framework/src/android/net/apf/ApfCapabilities.java
@@ -106,6 +106,8 @@
@Override
public int hashCode() {
+ // hashCode it is not implemented in R. Therefore it would be dangerous for
+ // NetworkStack to depend on it.
return Objects.hash(apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
}
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index c726dab..51df8ab 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -121,6 +121,20 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
public static final long NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION = 333340911L;
+ /**
+ * Enable caching for TrafficStats#get* APIs.
+ *
+ * Apps targeting Android V or later or running on Android V or later may take up to several
+ * seconds to see the updated results.
+ * Apps targeting lower android SDKs do not see cached result for backward compatibility,
+ * results of TrafficStats#get* APIs are reflecting network statistics immediately.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE = 74210811L;
+
private ConnectivityCompatChanges() {
}
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index 576e806..1e36676 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -76,7 +76,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public class NearbyManagerTest {
- @ClassRule static final EnableBluetoothRule sEnableBluetooth = new EnableBluetoothRule();
+ @ClassRule public static final EnableBluetoothRule sEnableBluetooth = new EnableBluetoothRule();
private static final byte[] SALT = new byte[]{1, 2};
private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index f278695..908bb13 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -70,8 +70,8 @@
// For details of versioned rc files see:
// https://android.googlesource.com/platform/system/core/+/HEAD/init/README.md#versioned-rc-files-within-apexs
prebuilt_etc {
- name: "netbpfload.mainline.rc",
- src: "netbpfload.mainline.rc",
+ name: "netbpfload.33rc",
+ src: "netbpfload.33rc",
filename: "netbpfload.33rc",
installable: false,
}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index f42e8a5..e9c6d8a 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -224,11 +224,6 @@
return 0;
}
-static bool isGSI() {
- // From //system/gsid/libgsi.cpp IsGsiRunning()
- return !access("/metadata/gsi/dsu/booted", F_OK);
-}
-
static bool hasGSM() {
static string ph = base::GetProperty("gsm.current.phone-type", "");
static bool gsm = (ph != "");
@@ -254,19 +249,55 @@
}
static int doLoad(char** argv, char * const envp[]) {
- const int device_api_level = android_get_device_api_level();
- const bool isAtLeastT = (device_api_level >= __ANDROID_API_T__);
- const bool isAtLeastU = (device_api_level >= __ANDROID_API_U__);
- const bool isAtLeastV = (device_api_level >= __ANDROID_API_V__);
+ const bool runningAsRoot = !getuid(); // true iff U QPR3 or V+
+
+ // Any released device will have codename REL instead of a 'real' codename.
+ // For safety: default to 'REL' so we default to unreleased=false on failure.
+ const bool unreleased = (base::GetProperty("ro.build.version.codename", "REL") != "REL");
+
+ // goog/main device_api_level is bumped *way* before aosp/main api level
+ // (the latter only gets bumped during the push of goog/main to aosp/main)
+ //
+ // Since we develop in AOSP, we want it to behave as if it was bumped too.
+ //
+ // Note that AOSP doesn't really have a good api level (for example during
+ // early V dev cycle, it would have *all* of T, some but not all of U, and some V).
+ // One could argue that for our purposes AOSP api level should be infinite or 10000.
+ //
+ // This could also cause api to be increased in goog/main or other branches,
+ // but I can't imagine a case where this would be a problem: the problem
+ // is rather a too low api level, rather than some ill defined high value.
+ // For example as I write this aosp is 34/U, and goog is 35/V,
+ // we want to treat both goog & aosp as 35/V, but it's harmless if we
+ // treat goog as 36 because that value isn't yet defined to mean anything,
+ // and we thus never compare against it.
+ //
+ // Also note that 'android_get_device_api_level()' is what the
+ // //system/core/init/apex_init_util.cpp
+ // apex init .XXrc parsing code uses for XX filtering.
+ //
+ // That code has a hack to bump <35 to 35 (to force aosp/main to parse .35rc),
+ // but could (should?) perhaps be adjusted to match this.
+ const int effective_api_level = android_get_device_api_level() + (int)unreleased;
+ const bool isAtLeastT = (effective_api_level >= __ANDROID_API_T__);
+ const bool isAtLeastU = (effective_api_level >= __ANDROID_API_U__);
+ const bool isAtLeastV = (effective_api_level >= __ANDROID_API_V__);
// last in U QPR2 beta1
const bool has_platform_bpfloader_rc = exists("/system/etc/init/bpfloader.rc");
// first in U QPR2 beta~2
const bool has_platform_netbpfload_rc = exists("/system/etc/init/netbpfload.rc");
- ALOGI("NetBpfLoad (%s) api:%d/%d kver:%07x (%s) rc:%d%d",
- argv[0], android_get_application_target_sdk_version(), device_api_level,
- kernelVersion(), describeArch(),
+ // Version of Network BpfLoader depends on the Android OS version
+ unsigned int bpfloader_ver = 42u; // [42] BPFLOADER_MAINLINE_VERSION
+ if (isAtLeastT) ++bpfloader_ver; // [43] BPFLOADER_MAINLINE_T_VERSION
+ if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
+ if (runningAsRoot) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_U_QPR3_VERSION
+ if (isAtLeastV) ++bpfloader_ver; // [46] BPFLOADER_MAINLINE_V_VERSION
+
+ ALOGI("NetBpfLoad v0.%u (%s) api:%d/%d kver:%07x (%s) uid:%d rc:%d%d",
+ bpfloader_ver, argv[0], android_get_device_api_level(), effective_api_level,
+ kernelVersion(), describeArch(), getuid(),
has_platform_bpfloader_rc, has_platform_netbpfload_rc);
if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
@@ -286,22 +317,34 @@
return 1;
}
+ // both S and T require kernel 4.9 (and eBpf support)
if (isAtLeastT && !isAtLeastKernelVersion(4, 9, 0)) {
ALOGE("Android T requires kernel 4.9.");
return 1;
}
+ // U bumps the kernel requirement up to 4.14
if (isAtLeastU && !isAtLeastKernelVersion(4, 14, 0)) {
ALOGE("Android U requires kernel 4.14.");
return 1;
}
+ // V bumps the kernel requirement up to 4.19
+ // see also: //system/netd/tests/kernel_test.cpp TestKernel419
if (isAtLeastV && !isAtLeastKernelVersion(4, 19, 0)) {
ALOGE("Android V requires kernel 4.19.");
return 1;
}
- if (isAtLeastV && isX86() && !isKernel64Bit()) {
+ // Technically already required by U, but only enforce on V+
+ // see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
+ if (isAtLeastV && isKernel32Bit() && isAtLeastKernelVersion(5, 16, 0)) {
+ ALOGE("Android V+ platform with 32 bit kernel version >= 5.16.0 is unsupported");
+ if (!isTV()) return 1;
+ }
+
+ // Various known ABI layout issues, particularly wrt. bpf and ipsec/xfrm.
+ if (isAtLeastV && isKernel32Bit() && isX86()) {
ALOGE("Android V requires X86 kernel to be 64-bit.");
if (!isTV()) return 1;
}
@@ -329,7 +372,7 @@
#undef REQUIRE
- if (bad && !isGSI()) {
+ if (bad) {
ALOGE("Unsupported kernel version (%07x).", kernelVersion());
}
}
@@ -350,6 +393,10 @@
* Some of these have userspace or kernel workarounds/hacks.
* Some of them don't...
* We're going to be removing the hacks.
+ * (for example "ANDROID: xfrm: remove in_compat_syscall() checks").
+ * Note: this check/enforcement only applies to *system* userspace code,
+ * it does not affect unprivileged apps, the 32-on-64 compatibility
+ * problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
*
* Additionally the 32-bit kernel jit support is poor,
* and 32-bit userspace on 64-bit kernel bpf ringbuffer compatibility is broken.
@@ -365,7 +412,9 @@
return 1;
}
- if (isAtLeastV) {
+ if (runningAsRoot) {
+ // Note: writing this proc file requires being root (always the case on V+)
+
// Linux 5.16-rc1 changed the default to 2 (disabled but changeable),
// but we need 0 (enabled)
// (this writeFile is known to fail on at least 4.19, but always defaults to 0 on
@@ -375,6 +424,11 @@
}
if (isAtLeastU) {
+ // Note: writing these proc files requires CAP_NET_ADMIN
+ // and sepolicy which is only present on U+,
+ // on Android T and earlier versions they're written from the 'load_bpf_programs'
+ // trigger (ie. by init itself) instead.
+
// Enable the eBPF JIT -- but do note that on 64-bit kernels it is likely
// already force enabled by the kernel config option BPF_JIT_ALWAYS_ON.
// (Note: this (open) will fail with ENOENT 'No such file or directory' if
@@ -404,12 +458,6 @@
// Thus we need to manually create the /sys/fs/bpf/loader subdirectory.
if (createSysFsBpfSubDir("loader")) return 1;
- // Version of Network BpfLoader depends on the Android OS version
- unsigned int bpfloader_ver = 42u; // [42] BPFLOADER_MAINLINE_VERSION
- if (isAtLeastT) ++bpfloader_ver; // [43] BPFLOADER_MAINLINE_T_VERSION
- if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
- if (isAtLeastV) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_V_VERSION
-
// Load all ELF objects, create programs and maps, and pin them
for (const auto& location : locations) {
if (loadAllElfObjects(bpfloader_ver, location) != 0) {
@@ -432,17 +480,25 @@
return 1;
}
- if (isAtLeastV) {
- ALOGI("done, transferring control to platform bpfloader.");
+ // leave a flag that we're done
+ if (createSysFsBpfSubDir("netd_shared/mainline_done")) return 1;
- const char * args[] = { platformBpfLoader, NULL, };
- execve(args[0], (char**)args, envp);
- ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
- return 1;
+ // platform bpfloader will only succeed when run as root
+ if (!runningAsRoot) {
+ // unreachable on U QPR3+ which always runs netbpfload as root
+
+ ALOGI("mainline done, no need to transfer control to platform bpf loader.");
+ return 0;
}
- ALOGI("mainline done!");
- return 0;
+ // unreachable before U QPR3
+ ALOGI("done, transferring control to platform bpfloader.");
+
+ // platform BpfLoader *needs* to run as root
+ const char * args[] = { platformBpfLoader, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
+ return 1;
}
} // namespace bpf
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index 289b4d7..bb7c56f 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -165,32 +165,34 @@
* since they are less stable abi/api and may conflict with platform uses of bpf.
*/
sectionType sectionNameTypes[] = {
- {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
- {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
- {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
- {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
- {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
- {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
- {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
- {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
- {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
- {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
- {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
- {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
- {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
- {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
- {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
- {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
- {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
- {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
- {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
- {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
- {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
- {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
- {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
+ {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
+ {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
+ {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupsockcreate/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_CREATE},
+ {"cgroupsockrelease/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_RELEASE},
+ {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
+ {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
+ {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
+ {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
+ {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
+ {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
+ {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
+ {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
+ {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
+ {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
+ {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
+ {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
+ {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
+ {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
+ {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
+ {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
+ {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
+ {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
+ {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
};
typedef struct {
@@ -1122,11 +1124,20 @@
readSectionUint("bpfloader_max_ver", elfFile, DEFAULT_BPFLOADER_MAX_VER);
unsigned int bpfLoaderMinRequiredVer =
readSectionUint("bpfloader_min_required_ver", elfFile, 0);
+ unsigned int netBpfLoadMinVer =
+ readSectionUint("netbpfload_min_ver", elfFile, 0);
size_t sizeOfBpfMapDef =
readSectionUint("size_of_bpf_map_def", elfFile, DEFAULT_SIZEOF_BPF_MAP_DEF);
size_t sizeOfBpfProgDef =
readSectionUint("size_of_bpf_prog_def", elfFile, DEFAULT_SIZEOF_BPF_PROG_DEF);
+ // temporary hack to enable gentle enablement of mainline NetBpfLoad
+ if (bpfloader_ver < netBpfLoadMinVer) {
+ ALOGI("NetBpfLoad version %d ignoring ELF object %s with netbpfload min ver %d",
+ bpfloader_ver, elfPath, netBpfLoadMinVer);
+ return 0;
+ }
+
// inclusive lower bound check
if (bpfloader_ver < bpfLoaderMinVer) {
ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with min ver 0x%05x",
diff --git a/netbpfload/netbpfload.33rc b/netbpfload/netbpfload.33rc
new file mode 100644
index 0000000..d269ce9
--- /dev/null
+++ b/netbpfload/netbpfload.33rc
@@ -0,0 +1,21 @@
+# This file takes effect only on T and U (on V netbpfload.35rc takes priority).
+#
+# The service is started from netd's libnetd_updatable shared library
+# on initial (boot time) startup of netd.
+#
+# However we never start this service on U QPR3.
+#
+# This is due to lack of a need: U QPR2 split the previously single
+# platform bpfloader into platform netbpfload -> platform bpfloader.
+# U QPR3 made the platform netbpfload unconditionally exec apex netbpfload,
+# so by the time U QPR3's netd runs, apex netbpfload is already done.
+
+service mdnsd_netbpfload /apex/com.android.tethering/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
+ user system
+ file /dev/kmsg w
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ # TODO: reboot_on_failure reboot,netbpfload-failed
+ override
diff --git a/netbpfload/netbpfload.mainline.rc b/netbpfload/netbpfload.mainline.rc
deleted file mode 100644
index d38a503..0000000
--- a/netbpfload/netbpfload.mainline.rc
+++ /dev/null
@@ -1,17 +0,0 @@
-service mdnsd_loadbpf /system/bin/bpfloader
- capabilities CHOWN SYS_ADMIN NET_ADMIN
- group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
- user root
- rlimit memlock 1073741824 1073741824
- oneshot
- reboot_on_failure reboot,bpfloader-failed
-
-service bpfloader /apex/com.android.tethering/bin/netbpfload
- capabilities CHOWN SYS_ADMIN NET_ADMIN
- group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
- user system
- file /dev/kmsg w
- rlimit memlock 1073741824 1073741824
- oneshot
- reboot_on_failure reboot,bpfloader-failed
- override
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 91fec90..5d64ae7 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -34,6 +34,7 @@
namespace net {
using base::unique_fd;
+using base::WaitForProperty;
using bpf::getSocketCookie;
using bpf::retrieveProgram;
using netdutils::Status;
@@ -85,31 +86,6 @@
return Status("U+ platform with kernel version < 4.14.0 is unsupported");
}
- if (modules::sdklevel::IsAtLeastV()) {
- // V bumps the kernel requirement up to 4.19
- // see also: //system/netd/tests/kernel_test.cpp TestKernel419
- if (!bpf::isAtLeastKernelVersion(4, 19, 0)) {
- return Status("V+ platform with kernel version < 4.19.0 is unsupported");
- }
-
- // Technically already required by U, but only enforce on V+
- // see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
- if (bpf::isKernel32Bit() && bpf::isAtLeastKernelVersion(5, 16, 0)) {
- return Status("V+ platform with 32 bit kernel, version >= 5.16.0 is unsupported");
- }
- }
-
- // Linux 6.1 is highest version supported by U, starting with V new kernels,
- // ie. 6.2+ we are dropping various kernel/system userspace 32-on-64 hacks
- // (for example "ANDROID: xfrm: remove in_compat_syscall() checks").
- // Note: this check/enforcement only applies to *system* userspace code,
- // it does not affect unprivileged apps, the 32-on-64 compatibility
- // problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
- // see also: //system/bpf/bpfloader/BpfLoader.cpp main()
- if (bpf::isUserspace32bit() && bpf::isAtLeastKernelVersion(6, 2, 0)) {
- return Status("32 bit userspace with Kernel version >= 6.2.0 is unsupported");
- }
-
// U mandates this mount point (though it should also be the case on T)
if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) {
return Status("U+ platform with cg2_path != /sys/fs/cgroup is unsupported");
@@ -134,8 +110,35 @@
// TODO: delete the if statement once all devices should support cgroup
// socket filter (ie. the minimum kernel version required is 4.14).
if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
- RETURN_IF_NOT_OK(
- attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_CREATE_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+ }
+
+ if (modules::sdklevel::IsAtLeastV()) {
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT4_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET4_CONNECT));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT6_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET6_CONNECT));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP4_RECVMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP4_RECVMSG));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP6_RECVMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP6_RECVMSG));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP4_SENDMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP4_SENDMSG));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP6_SENDMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP6_SENDMSG));
+
+ if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_GETSOCKOPT_PROG_PATH,
+ cg_fd, BPF_CGROUP_GETSOCKOPT));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SETSOCKOPT_PROG_PATH,
+ cg_fd, BPF_CGROUP_SETSOCKOPT));
+ }
+
+ if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_RELEASE_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET_SOCK_RELEASE));
+ }
}
if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
@@ -155,6 +158,24 @@
if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_BIND) <= 0) abort();
}
+ if (modules::sdklevel::IsAtLeastV()) {
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET4_CONNECT) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_CONNECT) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_RECVMSG) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_RECVMSG) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_SENDMSG) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_SENDMSG) <= 0) abort();
+
+ if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_GETSOCKOPT) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_SETSOCKOPT) <= 0) abort();
+ }
+
+ if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
+ }
+ }
+
return netdutils::status::ok;
}
@@ -165,39 +186,56 @@
BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
: mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+static bool mainlineNetBpfLoadDone() {
+ return !access("/sys/fs/bpf/netd_shared/mainline_done", F_OK);
+}
+
// copied with minor changes from waitForProgsLoaded()
// p/m/C's staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
static inline void waitForNetProgsLoaded() {
// infinite loop until success with 5/10/20/40/60/60/60... delay
for (int delay = 5;; delay *= 2) {
if (delay > 60) delay = 60;
- if (base::WaitForProperty("init.svc.bpfloader", "stopped", std::chrono::seconds(delay))
- && !access("/sys/fs/bpf/netd_shared", F_OK))
+ if (WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay))
+ && mainlineNetBpfLoadDone())
return;
- ALOGW("Waited %ds for init.svc.bpfloader=stopped, still waiting...", delay);
+ ALOGW("Waited %ds for init.svc.mdnsd_netbpfload=stopped, still waiting...", delay);
}
}
Status BpfHandler::init(const char* cg2_path) {
+ // Note: netd *can* be restarted, so this might get called a second time after boot is complete
+ // at which point we don't need to (and shouldn't) wait for (more importantly start) loading bpf
+
if (base::GetProperty("bpf.progs_loaded", "") != "1") {
- // Make sure BPF programs are loaded before doing anything
- ALOGI("Waiting for BPF programs");
-
- // TODO: use !modules::sdklevel::IsAtLeastV() once api finalized
- if (android_get_device_api_level() < __ANDROID_API_V__) {
- waitForNetProgsLoaded();
- ALOGI("Networking BPF programs are loaded");
-
- if (!base::SetProperty("ctl.start", "mdnsd_loadbpf")) {
- ALOGE("Failed to set property ctl.start=mdnsd_loadbpf, see dmesg for reason.");
- abort();
- }
-
- ALOGI("Waiting for remaining BPF programs");
- }
-
+ // AOSP platform netd & mainline don't need this (at least prior to U QPR3),
+ // but there could be platform provided (xt_)bpf programs that oem/vendor
+ // modified netd (which calls us during init) depends on...
+ ALOGI("Waiting for platform BPF programs");
android::bpf::waitForProgsLoaded();
}
+
+ if (!mainlineNetBpfLoadDone()) {
+ const bool enforce_mainline = false; // TODO: flip to true
+
+ // We're on < U QPR3 & it's the first time netd is starting up (unless crashlooping)
+ //
+ // On U QPR3+ netbpfload is guaranteed to run before the platform bpfloader,
+ // so waitForProgsLoaded() implies mainlineNetBpfLoadDone().
+ if (!base::SetProperty("ctl.start", "mdnsd_netbpfload")) {
+ ALOGE("Failed to set property ctl.start=mdnsd_netbpfload, see dmesg for reason.");
+ if (enforce_mainline) abort();
+ }
+
+ if (enforce_mainline) {
+ ALOGI("Waiting for Networking BPF programs");
+ waitForNetProgsLoaded();
+ ALOGI("Networking BPF programs are loaded");
+ } else {
+ ALOGI("Started mdnsd_netbpfload asynchronously.");
+ }
+ }
+
ALOGI("BPF programs are loaded");
RETURN_IF_NOT_OK(initPrograms(cg2_path));
@@ -206,7 +244,30 @@
return netdutils::status::ok;
}
+static void mapLockTest(void) {
+ // The maps must be R/W, and as yet unopened (or more specifically not yet lock'ed).
+ const char * const m1 = BPF_NETD_PATH "map_netd_lock_array_test_map";
+ const char * const m2 = BPF_NETD_PATH "map_netd_lock_hash_test_map";
+
+ unique_fd fd0(bpf::mapRetrieveExclusiveRW(m1)); if (!fd0.ok()) abort(); // grabs exclusive lock
+
+ unique_fd fd1(bpf::mapRetrieveExclusiveRW(m2)); if (!fd1.ok()) abort(); // no conflict with fd0
+ unique_fd fd2(bpf::mapRetrieveExclusiveRW(m2)); if ( fd2.ok()) abort(); // busy due to fd1
+ unique_fd fd3(bpf::mapRetrieveRO(m2)); if (!fd3.ok()) abort(); // no lock taken
+ unique_fd fd4(bpf::mapRetrieveRW(m2)); if ( fd4.ok()) abort(); // busy due to fd1
+ fd1.reset(); // releases exclusive lock
+ unique_fd fd5(bpf::mapRetrieveRO(m2)); if (!fd5.ok()) abort(); // no lock taken
+ unique_fd fd6(bpf::mapRetrieveRW(m2)); if (!fd6.ok()) abort(); // now ok
+ unique_fd fd7(bpf::mapRetrieveRO(m2)); if (!fd7.ok()) abort(); // no lock taken
+ unique_fd fd8(bpf::mapRetrieveExclusiveRW(m2)); if ( fd8.ok()) abort(); // busy due to fd6
+
+ fd0.reset(); // releases exclusive lock
+ unique_fd fd9(bpf::mapRetrieveWO(m1)); if (!fd9.ok()) abort(); // grabs exclusive lock
+}
+
Status BpfHandler::initMaps() {
+ mapLockTest();
+
RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index f8b0d53..64624ae 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1923,13 +1923,13 @@
mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
.setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING))
- .setIsExpiredServicesRemovalEnabled(mDeps.isFeatureEnabled(
+ .setIsExpiredServicesRemovalEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
.setIsLabelCountLimitEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_LIMIT_LABEL_COUNT))
- .setIsKnownAnswerSuppressionEnabled(mDeps.isFeatureEnabled(
+ .setIsKnownAnswerSuppressionEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
- .setIsUnicastReplyEnabled(mDeps.isFeatureEnabled(
+ .setIsUnicastReplyEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_UNICAST_REPLY_ENABLED))
.setIsAggressiveQueryModeEnabled(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index f4a08ba..c264f25 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -189,10 +189,10 @@
public Builder() {
mIsMdnsOffloadFeatureEnabled = false;
mIncludeInetAddressRecordsInProbing = false;
- mIsExpiredServicesRemovalEnabled = false;
+ mIsExpiredServicesRemovalEnabled = true; // Default enabled.
mIsLabelCountLimitEnabled = true; // Default enabled.
- mIsKnownAnswerSuppressionEnabled = false;
- mIsUnicastReplyEnabled = true;
+ mIsKnownAnswerSuppressionEnabled = true; // Default enabled.
+ mIsUnicastReplyEnabled = true; // Default enabled.
mIsAggressiveQueryModeEnabled = false;
mIsQueryWithKnownAnswerEnabled = false;
mOverrideProvider = null;
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 8305c1e..114cf2e 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -57,6 +57,7 @@
import static android.net.TrafficStats.TYPE_TX_PACKETS;
import static android.net.TrafficStats.UID_TETHERING;
import static android.net.TrafficStats.UNSUPPORTED;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE;
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;
@@ -91,6 +92,7 @@
import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
import android.app.usage.NetworkStatsManager;
import android.content.ApexEnvironment;
import android.content.BroadcastReceiver;
@@ -472,7 +474,9 @@
private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
static final String TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_rate_limit_cache_enabled_flag";
- private final boolean mSupportTrafficStatsRateLimitCache;
+ private final boolean mAlwaysUseTrafficStatsRateLimitCache;
+ private final int mTrafficStatsRateLimitCacheExpiryDuration;
+ private final int mTrafficStatsRateLimitCacheMaxEntries;
private final Object mOpenSessionCallsLock = new Object();
@@ -663,15 +667,18 @@
mEventLogger = null;
}
- final long cacheExpiryDurationMs = mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
- final int cacheMaxEntries = mDeps.getTrafficStatsRateLimitCacheMaxEntries();
- mSupportTrafficStatsRateLimitCache = mDeps.supportTrafficStatsRateLimitCache(mContext);
+ mAlwaysUseTrafficStatsRateLimitCache =
+ mDeps.alwaysUseTrafficStatsRateLimitCache(mContext);
+ mTrafficStatsRateLimitCacheExpiryDuration =
+ mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
+ mTrafficStatsRateLimitCacheMaxEntries =
+ mDeps.getTrafficStatsRateLimitCacheMaxEntries();
mTrafficStatsTotalCache = new TrafficStatsRateLimitCache(mClock,
- cacheExpiryDurationMs, cacheMaxEntries);
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
mTrafficStatsIfaceCache = new TrafficStatsRateLimitCache(mClock,
- cacheExpiryDurationMs, cacheMaxEntries);
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
mTrafficStatsUidCache = new TrafficStatsRateLimitCache(mClock,
- cacheExpiryDurationMs, cacheMaxEntries);
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
// TODO: Remove bpfNetMaps creation and always start SkDestroyListener
// Following code is for the experiment to verify the SkDestroyListener refactoring. Based
@@ -920,13 +927,14 @@
}
/**
- * Get whether TrafficStats rate-limit cache is supported.
+ * Get whether TrafficStats rate-limit cache is always applied.
*
* This method should only be called once in the constructor,
* to ensure that the code does not need to deal with flag values changing at runtime.
*/
- public boolean supportTrafficStatsRateLimitCache(@NonNull Context ctx) {
- return false;
+ public boolean alwaysUseTrafficStatsRateLimitCache(@NonNull Context ctx) {
+ return SdkLevel.isAtLeastV() && DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
+ ctx, TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG);
}
/**
@@ -954,6 +962,13 @@
}
/**
+ * Wrapper method for {@link CompatChanges#isChangeEnabled(long, int)}
+ */
+ public boolean isChangeEnabled(final long changeId, final int uid) {
+ return CompatChanges.isChangeEnabled(changeId, uid);
+ }
+
+ /**
* Retrieves native network total statistics.
*
* @return A NetworkStats.Entry containing the native statistics, or
@@ -1529,7 +1544,11 @@
}
@Override
- public INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage) {
+ public INetworkStatsSession openSessionForUsageStats(
+ int flags, @NonNull String callingPackage) {
+ Objects.requireNonNull(callingPackage);
+ PermissionUtils.enforcePackageNameMatchesUid(
+ mContext, Binder.getCallingUid(), callingPackage);
return openSessionInternal(flags, callingPackage);
}
@@ -2046,6 +2065,7 @@
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
+ PermissionUtils.enforcePackageNameMatchesUid(mContext, callingUid, callingPackage);
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
DataUsageRequest normalizedRequest;
final long token = Binder.clearCallingIdentity();
@@ -2084,14 +2104,14 @@
}
if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
- if (!mSupportTrafficStatsRateLimitCache) {
- return getEntryValueForType(mDeps.nativeGetUidStat(uid), type);
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, callingUid)) {
+ final NetworkStats.Entry entry = mTrafficStatsUidCache.getOrCompute(IFACE_ALL, uid,
+ () -> mDeps.nativeGetUidStat(uid));
+ return getEntryValueForType(entry, type);
}
- final NetworkStats.Entry entry = mTrafficStatsUidCache.getOrCompute(IFACE_ALL, uid,
- () -> mDeps.nativeGetUidStat(uid));
-
- return getEntryValueForType(entry, type);
+ return getEntryValueForType(mDeps.nativeGetUidStat(uid), type);
}
@Nullable
@@ -2113,14 +2133,15 @@
Objects.requireNonNull(iface);
if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
- if (!mSupportTrafficStatsRateLimitCache) {
- return getEntryValueForType(getIfaceStatsInternal(iface), type);
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(
+ ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, Binder.getCallingUid())) {
+ final NetworkStats.Entry entry = mTrafficStatsIfaceCache.getOrCompute(iface, UID_ALL,
+ () -> getIfaceStatsInternal(iface));
+ return getEntryValueForType(entry, type);
}
- final NetworkStats.Entry entry = mTrafficStatsIfaceCache.getOrCompute(iface, UID_ALL,
- () -> getIfaceStatsInternal(iface));
-
- return getEntryValueForType(entry, type);
+ return getEntryValueForType(getIfaceStatsInternal(iface), type);
}
private long getEntryValueForType(@Nullable NetworkStats.Entry entry, int type) {
@@ -2166,14 +2187,15 @@
@Override
public long getTotalStats(int type) {
if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
- if (!mSupportTrafficStatsRateLimitCache) {
- return getEntryValueForType(getTotalStatsInternal(), type);
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(
+ ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, Binder.getCallingUid())) {
+ final NetworkStats.Entry entry = mTrafficStatsTotalCache.getOrCompute(
+ IFACE_ALL, UID_ALL, () -> getTotalStatsInternal());
+ return getEntryValueForType(entry, type);
}
- final NetworkStats.Entry entry = mTrafficStatsTotalCache.getOrCompute(IFACE_ALL, UID_ALL,
- () -> getTotalStatsInternal());
-
- return getEntryValueForType(entry, type);
+ return getEntryValueForType(getTotalStatsInternal(), type);
}
@Override
@@ -2937,13 +2959,12 @@
} catch (IOException e) {
pw.println("(failed to dump FastDataInput counters)");
}
- pw.print("trafficstats.cache.supported", mSupportTrafficStatsRateLimitCache);
+ pw.print("trafficstats.cache.alwaysuse", mAlwaysUseTrafficStatsRateLimitCache);
pw.println();
pw.print(TRAFFIC_STATS_CACHE_EXPIRY_DURATION_NAME,
- mDeps.getTrafficStatsRateLimitCacheExpiryDuration());
+ mTrafficStatsRateLimitCacheExpiryDuration);
pw.println();
- pw.print(TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME,
- mDeps.getTrafficStatsRateLimitCacheMaxEntries());
+ pw.print(TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME, mTrafficStatsRateLimitCacheMaxEntries);
pw.println();
pw.decreaseIndent();
diff --git a/service/ServiceConnectivityResources/res/values-de/strings.xml b/service/ServiceConnectivityResources/res/values-de/strings.xml
index 536ebda..f58efb0 100644
--- a/service/ServiceConnectivityResources/res/values-de/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-de/strings.xml
@@ -29,7 +29,7 @@
<string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Für Optionen tippen"</string>
<string name="mobile_no_internet" msgid="4087718456753201450">"Mobiles Netzwerk hat keinen Internetzugriff"</string>
<string name="other_networks_no_internet" msgid="5693932964749676542">"Netzwerk hat keinen Internetzugriff"</string>
- <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string>
+ <string name="private_dns_broken_detailed" msgid="2677123850463207823">"Auf den Server des privaten DNS kann nicht zugegriffen werden"</string>
<string name="network_partial_connectivity" msgid="5549503845834993258">"Schlechte Verbindung mit <xliff:g id="NETWORK_SSID">%1$s</xliff:g>"</string>
<string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"Tippen, um die Verbindung trotzdem herzustellen"</string>
<string name="network_switch_metered" msgid="5016937523571166319">"Zu <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> gewechselt"</string>
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 4783f2b..02a9ce6 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -53,4 +53,10 @@
UTF-8 bytes.
-->
<string translatable="false" name="config_thread_model_name">Thread Border Router</string>
+
+ <!-- Whether the Thread network will be managed by the Google Home ecosystem. When this value
+ is set, a TXT entry "vgh=0" or "vgh=1" will be added to the "_mehscop._udp" mDNS service
+ respectively (The TXT value is a string).
+ -->
+ <bool name="config_thread_managed_by_google_home">false</bool>
</resources>
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index c07d050..c0082bb 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -80,7 +80,8 @@
case VERIFY_BIN: return;
case VERIFY_PROG: fd = bpf::retrieveProgram(path); break;
case VERIFY_MAP_RO: fd = bpf::mapRetrieveRO(path); break;
- case VERIFY_MAP_RW: fd = bpf::mapRetrieveRW(path); break;
+ // lockless: we're just checking access rights and will immediately close the fd
+ case VERIFY_MAP_RW: fd = bpf::mapRetrieveLocklessRW(path); break;
}
if (fd < 0) ALOGF("bpf_obj_get '%s' failed, errno=%d", path, errno);
@@ -114,12 +115,7 @@
V("/sys/fs/bpf", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf", DIR);
- // TODO: use modules::sdklevel::IsAtLeastV() once api finalized
- if (android_get_device_api_level() >= __ANDROID_API_V__) {
- V("/sys/fs/bpf/net_shared", S_IFDIR|01777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
- } else {
- V("/sys/fs/bpf/net_shared", S_IFDIR|01777, SYSTEM, SYSTEM, "fs_bpf_net_shared", DIR);
- }
+ V("/sys/fs/bpf/net_shared", S_IFDIR|01777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
// pre-U we do not have selinux privs to getattr on bpf maps/progs
// so while the below *should* be as listed, we have no way to actually verify
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 1047232..b3e7d8c 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -72,6 +72,7 @@
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U32;
@@ -188,7 +189,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U32> getConfigurationMap() {
try {
- return new BpfMap<>(
+ return SingleWriterBpfMap.getSingleton(
CONFIGURATION_MAP_PATH, S32.class, U32.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open netd configuration map", e);
@@ -198,7 +199,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
try {
- return new BpfMap<>(
+ return SingleWriterBpfMap.getSingleton(
UID_OWNER_MAP_PATH, S32.class, UidOwnerValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid owner map", e);
@@ -208,7 +209,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getUidPermissionMap() {
try {
- return new BpfMap<>(
+ return SingleWriterBpfMap.getSingleton(
UID_PERMISSION_MAP_PATH, S32.class, U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid permission map", e);
@@ -218,6 +219,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
try {
+ // Cannot use SingleWriterBpfMap because it's written by ClatCoordinator as well.
return new BpfMap<>(COOKIE_TAG_MAP_PATH,
CookieTagMapKey.class, CookieTagMapValue.class);
} catch (ErrnoException e) {
@@ -228,7 +230,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getDataSaverEnabledMap() {
try {
- return new BpfMap<>(
+ return SingleWriterBpfMap.getSingleton(
DATA_SAVER_ENABLED_MAP_PATH, S32.class, U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open data saver enabled map", e);
@@ -238,7 +240,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<IngressDiscardKey, IngressDiscardValue> getIngressDiscardMap() {
try {
- return new BpfMap<>(INGRESS_DISCARD_MAP_PATH,
+ return SingleWriterBpfMap.getSingleton(INGRESS_DISCARD_MAP_PATH,
IngressDiscardKey.class, IngressDiscardValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open ingress discard map", e);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index bb77a9d..be1d3c7 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -77,6 +77,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -108,14 +109,24 @@
import static android.os.Process.INVALID_UID;
import static android.os.Process.VPN_UID;
import static android.system.OsConstants.ETH_P_ALL;
+import static android.system.OsConstants.F_OK;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_GETSOCKOPT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_CONNECT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_CONNECT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_EGRESS;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_INGRESS;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_CREATE;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_RELEASE;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_SETSOCKOPT;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP4_RECVMSG;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP4_SENDMSG;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_RECVMSG;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_SENDMSG;
import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
@@ -132,6 +143,7 @@
import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresApi;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@@ -267,6 +279,7 @@
import android.stats.connectivity.ValidatedState;
import android.sysprop.NetworkProperties;
import android.system.ErrnoException;
+import android.system.Os;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -280,8 +293,6 @@
import android.util.SparseIntArray;
import android.util.StatsEvent;
-import androidx.annotation.RequiresApi;
-
import com.android.connectivity.resources.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -382,6 +393,7 @@
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Objects;
+import java.util.Random;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringJoiner;
@@ -489,6 +501,8 @@
private final boolean mRequestRestrictedWifiEnabled;
private final boolean mBackgroundFirewallChainEnabled;
+ private final boolean mUseDeclaredMethodsForCallbacksEnabled;
+
/**
* Uids ConnectivityService tracks blocked status of to send blocked status callbacks.
* Key is uid based on mAsUid of registered networkRequestInfo
@@ -1831,6 +1845,33 @@
new PermissionMonitor(mContext, mNetd, mBpfNetMaps, mHandlerThread);
mHandlerThread.start();
mHandler = new InternalHandler(mHandlerThread.getLooper());
+ // Temporary hack to report netbpfload result.
+ // TODO: remove in 2024-09 when netbpfload starts loading mainline bpf programs.
+ mHandler.postDelayed(() -> {
+ // Test Pitot pipeline, ignore this Log.wtf if it shows up in the logs.
+ final Random r = new Random();
+ if (Build.TYPE.equals("user") && r.nextInt(1000) == 0) {
+ Log.wtf(TAG, "NOT A FAILURE, PLEASE IGNORE! Test Pitot pipeline works correctly");
+ }
+ // Did netbpfload create the map?
+ try {
+ Os.access("/sys/fs/bpf/net_shared/map_gentle_test", F_OK);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "netbpfload did not create map", e);
+ }
+ // Did netbpfload create the program?
+ try {
+ Os.access("/sys/fs/bpf/net_shared/prog_gentle_skfilter_accept", F_OK);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "netbpfload did not create program", e);
+ }
+ // Did netbpfload run to completion?
+ try {
+ Os.access("/sys/fs/bpf/netd_shared/mainline_done", F_OK);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "netbpfload did not run to completion", e);
+ }
+ }, 30_000 /* delayMillis */);
mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
mConnectivityDiagnosticsHandler =
new ConnectivityDiagnosticsHandler(mHandlerThread.getLooper());
@@ -1850,6 +1891,8 @@
&& mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI);
mBackgroundFirewallChainEnabled = mDeps.isAtLeastV() && mDeps.isFeatureNotChickenedOut(
context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN);
+ mUseDeclaredMethodsForCallbacksEnabled = mDeps.isFeatureEnabled(context,
+ ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS);
mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
this::handleUidCarrierPrivilegesLost, mHandler);
@@ -3561,6 +3604,7 @@
pw.decreaseIndent();
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private void dumpBpfProgramStatus(IndentingPrintWriter pw) {
pw.println("Bpf Program Status:");
pw.increaseIndent();
@@ -3569,12 +3613,37 @@
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_INGRESS));
pw.print("CGROUP_INET_EGRESS: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_EGRESS));
+
pw.print("CGROUP_INET_SOCK_CREATE: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_SOCK_CREATE));
+
pw.print("CGROUP_INET4_BIND: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET4_BIND));
pw.print("CGROUP_INET6_BIND: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET6_BIND));
+
+ pw.print("CGROUP_INET4_CONNECT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET4_CONNECT));
+ pw.print("CGROUP_INET6_CONNECT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET6_CONNECT));
+
+ pw.print("CGROUP_UDP4_SENDMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP4_SENDMSG));
+ pw.print("CGROUP_UDP6_SENDMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP6_SENDMSG));
+
+ pw.print("CGROUP_UDP4_RECVMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP4_RECVMSG));
+ pw.print("CGROUP_UDP6_RECVMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP6_RECVMSG));
+
+ pw.print("CGROUP_GETSOCKOPT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_GETSOCKOPT));
+ pw.print("CGROUP_SETSOCKOPT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_SETSOCKOPT));
+
+ pw.print("CGROUP_INET_SOCK_RELEASE: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_SOCK_RELEASE));
} catch (IOException e) {
pw.println(" IOException");
}
@@ -4224,8 +4293,19 @@
pw.println();
dumpDestroySockets(pw);
- pw.println();
- dumpBpfProgramStatus(pw);
+ if (mDeps.isAtLeastT()) {
+ // R: https://android.googlesource.com/platform/system/core/+/refs/heads/android11-release/rootdir/init.rc
+ // shows /dev/cg2_bpf
+ // S: https://android.googlesource.com/platform/system/core/+/refs/heads/android12-release/rootdir/init.rc
+ // does not
+ // Thus cgroups are mounted at /dev/cg2_bpf on R and not on /sys/fs/cgroup
+ // so the following won't work (on R) anyway.
+ // The /sys/fs/cgroup path is only actually enforced/required starting with U,
+ // but it is very likely to already be the case (though not guaranteed) on T.
+ // I'm not at all sure about S - let's just skip it to get rid of lint warnings.
+ pw.println();
+ dumpBpfProgramStatus(pw);
+ }
if (null != mCarrierPrivilegeAuthenticator) {
pw.println();
@@ -13282,11 +13362,12 @@
requests.add(createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
- // request: restricted Satellite internet
+ // request: Satellite internet, satellite network could be restricted or constrained
final NetworkCapabilities cap = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
.build();
requests.add(createNetworkRequest(NetworkRequest.Type.REQUEST, cap));
@@ -14045,4 +14126,16 @@
enforceNetworkStackPermission(mContext);
return mRoutingCoordinatorService;
}
+
+ @Override
+ public long getEnabledConnectivityManagerFeatures() {
+ long features = 0;
+ // The bitmask must be built based on final properties initialized in the constructor, to
+ // ensure that it does not change over time and is always consistent between
+ // ConnectivityManager and ConnectivityService.
+ if (mUseDeclaredMethodsForCallbacksEnabled) {
+ features |= ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS;
+ }
+ return features;
+ }
}
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 843b7b3..4d39d7d 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -267,6 +267,7 @@
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
nc.setNetworkSpecifier(new TestNetworkSpecifier(iface));
nc.setAdministratorUids(administratorUids);
if (!isMetered) {
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index d886182..b1c770b 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -45,6 +45,7 @@
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.TcUtils;
import com.android.net.module.util.bpf.ClatEgress4Key;
import com.android.net.module.util.bpf.ClatEgress4Value;
@@ -256,7 +257,7 @@
@Nullable
public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
try {
- return new BpfMap<>(CLAT_INGRESS6_MAP_PATH,
+ return SingleWriterBpfMap.getSingleton(CLAT_INGRESS6_MAP_PATH,
ClatIngress6Key.class, ClatIngress6Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create ingress6 map: " + e);
@@ -268,7 +269,7 @@
@Nullable
public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
try {
- return new BpfMap<>(CLAT_EGRESS4_MAP_PATH,
+ return SingleWriterBpfMap.getSingleton(CLAT_EGRESS4_MAP_PATH,
ClatEgress4Key.class, ClatEgress4Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create egress4 map: " + e);
@@ -280,6 +281,7 @@
@Nullable
public IBpfMap<CookieTagMapKey, CookieTagMapValue> getBpfCookieTagMap() {
try {
+ // also read and written from other locations
return new BpfMap<>(COOKIE_TAG_MAP_PATH,
CookieTagMapKey.class, CookieTagMapValue.class);
} catch (ErrnoException e) {
@@ -683,13 +685,25 @@
throw new IOException("Failed to start clat ", e);
} finally {
if (tunFd != null) {
- tunFd.close();
+ try {
+ tunFd.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close tun file descriptor " + e);
+ }
}
if (readSock6 != null) {
- readSock6.close();
+ try {
+ readSock6.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close read socket " + e);
+ }
}
if (writeSock6 != null) {
- writeSock6.close();
+ try {
+ writeSock6.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close write socket " + e);
+ }
}
}
}
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 7ea7f95..1ee1ed7 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -46,6 +46,9 @@
public static final String DELAY_DESTROY_SOCKETS = "delay_destroy_sockets";
+ public static final String USE_DECLARED_METHODS_FOR_CALLBACKS =
+ "use_declared_methods_for_callbacks";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 7707122..fd41ee6 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -170,9 +170,11 @@
&& !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData()
.getVenueFriendlyName())) {
name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName();
+ } else if (!TextUtils.isEmpty(extraInfo)) {
+ name = extraInfo;
} else {
- name = TextUtils.isEmpty(extraInfo)
- ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo;
+ final String ssid = WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid());
+ name = ssid == null ? "" : ssid;
}
// Only notify for Internet-capable networks.
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index ede6d3f..e2834b0 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -135,6 +135,7 @@
"device/com/android/net/module/util/BpfUtils.java",
"device/com/android/net/module/util/IBpfMap.java",
"device/com/android/net/module/util/JniUtil.java",
+ "device/com/android/net/module/util/SingleWriterBpfMap.java",
"device/com/android/net/module/util/TcUtils.java",
],
sdk_version: "module_current",
diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
index da77ae8..0fbc25d 100644
--- a/staticlibs/device/com/android/net/module/util/BpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -15,6 +15,7 @@
*/
package com.android.net.module.util;
+import static android.system.OsConstants.EBUSY;
import static android.system.OsConstants.EEXIST;
import static android.system.OsConstants.ENOENT;
@@ -52,6 +53,9 @@
public static final int BPF_F_RDONLY = 1 << 3;
public static final int BPF_F_WRONLY = 1 << 4;
+ // magic value for jni consumption, invalid from kernel point of view
+ public static final int BPF_F_RDWR_EXCLUSIVE = BPF_F_RDONLY | BPF_F_WRONLY;
+
public static final int BPF_MAP_TYPE_HASH = 1;
private static final int BPF_F_NO_PREALLOC = 1;
@@ -69,6 +73,12 @@
private static ConcurrentHashMap<Pair<String, Integer>, ParcelFileDescriptor> sFdCache =
new ConcurrentHashMap<>();
+ private static ParcelFileDescriptor checkModeExclusivity(ParcelFileDescriptor fd, int mode)
+ throws ErrnoException {
+ if (mode == BPF_F_RDWR_EXCLUSIVE) throw new ErrnoException("cachedBpfFdGet", EBUSY);
+ return fd;
+ }
+
private static ParcelFileDescriptor cachedBpfFdGet(String path, int mode,
int keySize, int valueSize)
throws ErrnoException, NullPointerException {
@@ -79,12 +89,12 @@
var key = Pair.create(path, (mode << 26) ^ (keySize << 16) ^ valueSize);
// unlocked fetch is safe: map is concurrent read capable, and only inserted into
ParcelFileDescriptor fd = sFdCache.get(key);
- if (fd != null) return fd;
+ if (fd != null) return checkModeExclusivity(fd, mode);
// ok, no cached fd present, need to grab a lock
synchronized (BpfMap.class) {
// need to redo the check
fd = sFdCache.get(key);
- if (fd != null) return fd;
+ if (fd != null) return checkModeExclusivity(fd, mode);
// okay, we really haven't opened this before...
fd = ParcelFileDescriptor.adoptFd(nativeBpfFdGet(path, mode, keySize, valueSize));
sFdCache.put(key, fd);
diff --git a/staticlibs/device/com/android/net/module/util/BpfUtils.java b/staticlibs/device/com/android/net/module/util/BpfUtils.java
index cdd6fd7..a41eeba 100644
--- a/staticlibs/device/com/android/net/module/util/BpfUtils.java
+++ b/staticlibs/device/com/android/net/module/util/BpfUtils.java
@@ -39,6 +39,15 @@
public static final int BPF_CGROUP_INET_SOCK_CREATE = 2;
public static final int BPF_CGROUP_INET4_BIND = 8;
public static final int BPF_CGROUP_INET6_BIND = 9;
+ public static final int BPF_CGROUP_INET4_CONNECT = 10;
+ public static final int BPF_CGROUP_INET6_CONNECT = 11;
+ public static final int BPF_CGROUP_UDP4_SENDMSG = 14;
+ public static final int BPF_CGROUP_UDP6_SENDMSG = 15;
+ public static final int BPF_CGROUP_UDP4_RECVMSG = 19;
+ public static final int BPF_CGROUP_UDP6_RECVMSG = 20;
+ public static final int BPF_CGROUP_GETSOCKOPT = 21;
+ public static final int BPF_CGROUP_SETSOCKOPT = 22;
+ public static final int BPF_CGROUP_INET_SOCK_RELEASE = 34;
// Note: This is only guaranteed to be accurate on U+ devices. It is likely to be accurate
// on T+ devices as well, but this is not guaranteed.
diff --git a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
new file mode 100644
index 0000000..cd6bfec
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.net.module.util;
+
+import android.os.Build;
+import android.system.ErrnoException;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.HashMap;
+import java.util.NoSuchElementException;
+
+/**
+ * Subclass of BpfMap for maps that are only ever written by one userspace writer.
+ *
+ * This class stores all map data in a userspace HashMap in addition to in the BPF map. This makes
+ * reads (but not iterations) much faster because they do not require a system call or converting
+ * the raw map read to the Value struct. See, e.g., b/343166906 .
+ *
+ * Users of this class must ensure that no BPF program ever writes to the map, and that all
+ * userspace writes to the map occur through this object. Other userspace code may still read from
+ * the map; only writes are required to go through this object.
+ *
+ * Reads and writes to this object are thread-safe and internally synchronized. The read and write
+ * methods are synchronized to ensure that current writers always result in a consistent internal
+ * state (without synchronization, two concurrent writes might update the underlying map and the
+ * cache in the opposite order, resulting in the cache being out of sync with the map).
+ *
+ * getNextKey and iteration over the map are not synchronized or cached and always access the
+ * isunderlying map. The values returned by these calls may be temporarily out of sync with the
+ * values read and written through this object.
+ *
+ * TODO: consider caching reads on iterations as well. This is not trivial because the semantics for
+ * iterating BPF maps require passing in the previously-returned key, and Java iterators only
+ * support iterating from the beginning. It could be done by implementing forEach and possibly by
+ * making getFirstKey and getNextKey private (if no callers are using them). Because HashMap is not
+ * thread-safe, implementing forEach would require either making that method synchronized (and
+ * block reads and updates from other threads until iteration is complete) or switching the
+ * internal HashMap to ConcurrentHashMap.
+ *
+ * @param <K> the key of the map.
+ * @param <V> the value of the map.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
+ // HashMap instead of ArrayMap because it performs better on larger maps, and many maps used in
+ // our code can contain hundreds of items.
+ private final HashMap<K, V> mCache = new HashMap<>();
+
+ // This should only ever be called (hence private) once for a given 'path'.
+ // Java-wise what matters is the entire {path, key, value} triplet,
+ // but of course the kernel exclusive lock is just on the path (fd),
+ // and any BpfMap has (or should have...) well defined key/value types
+ // (or at least their sizes) so in practice it doesn't really matter.
+ private SingleWriterBpfMap(@NonNull final String path, final Class<K> key,
+ final Class<V> value) throws ErrnoException, NullPointerException {
+ super(path, BPF_F_RDWR_EXCLUSIVE, key, value);
+
+ // Populate cache with the current map contents.
+ K currentKey = super.getFirstKey();
+ while (currentKey != null) {
+ mCache.put(currentKey, super.getValue(currentKey));
+ currentKey = super.getNextKey(currentKey);
+ }
+ }
+
+ // This allows reuse of SingleWriterBpfMap objects for the same {path, keyClass, valueClass}.
+ // These are never destroyed, so once created the lock is (effectively) held till process death
+ // (even if fixed, there would still be a write-only fd cache in underlying BpfMap base class).
+ private static final HashMap<Pair<String, Pair<Class, Class>>, SingleWriterBpfMap>
+ singletonCache = new HashMap<>();
+
+ // This is the public 'factory method' that (creates if needed and) returns a singleton instance
+ // for a given map. This holds an exclusive lock and has a permanent write-through cache.
+ // It will not be released until process death (or at least unload of the relevant class loader)
+ public synchronized static <KK extends Struct, VV extends Struct> SingleWriterBpfMap<KK,VV>
+ getSingleton(@NonNull final String path, final Class<KK> key, final Class<VV> value)
+ throws ErrnoException, NullPointerException {
+ var cacheKey = new Pair<>(path, new Pair<Class,Class>(key, value));
+ if (!singletonCache.containsKey(cacheKey))
+ singletonCache.put(cacheKey, new SingleWriterBpfMap(path, key, value));
+ return singletonCache.get(cacheKey);
+ }
+
+ @Override
+ public synchronized void updateEntry(K key, V value) throws ErrnoException {
+ super.updateEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized void insertEntry(K key, V value)
+ throws ErrnoException, IllegalStateException {
+ super.insertEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized void replaceEntry(K key, V value)
+ throws ErrnoException, NoSuchElementException {
+ super.replaceEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized boolean insertOrReplaceEntry(K key, V value) throws ErrnoException {
+ final boolean ret = super.insertOrReplaceEntry(key, value);
+ mCache.put(key, value);
+ return ret;
+ }
+
+ @Override
+ public synchronized boolean deleteEntry(K key) throws ErrnoException {
+ final boolean ret = super.deleteEntry(key);
+ mCache.remove(key);
+ return ret;
+ }
+
+ @Override
+ public synchronized boolean containsKey(@NonNull K key) throws ErrnoException {
+ return mCache.containsKey(key);
+ }
+
+ @Override
+ public synchronized V getValue(@NonNull K key) throws ErrnoException {
+ return mCache.get(key);
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 19d8bbe..319d51a 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -180,6 +180,7 @@
public static final int ICMPV6_NS_HEADER_LEN = 24;
public static final int ICMPV6_NA_HEADER_LEN = 24;
public static final int ICMPV6_ND_OPTION_TLLA_LEN = 8;
+ public static final int ICMPV6_ND_OPTION_SLLA_LEN = 8;
public static final int NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER = 1 << 31;
public static final int NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED = 1 << 30;
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
index d716358..cd51004 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -151,7 +151,7 @@
inline base::Result<void> BpfRingbufBase::Init(const char* path) {
- mRingFd.reset(mapRetrieveRW(path));
+ mRingFd.reset(mapRetrieveExclusiveRW(path));
if (!mRingFd.ok()) {
return android::base::ErrnoError()
<< "failed to retrieve ringbuffer at " << path;
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index dc7925e..e11a9e2 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -39,11 +39,12 @@
// Android U / 14 (api level 34) - various new program types added
#define BPFLOADER_U_VERSION 38u
-// Android V / 15 (api level 35) - platform only
+// Android U QPR2 / 14 (api level 34) - platform only
// (note: the platform bpfloader in V isn't really versioned at all,
// as there is no need as it can only load objects compiled at the
// same time as itself and the rest of the platform)
-#define BPFLOADER_PLATFORM_VERSION 41u
+#define BPFLOADER_U_QPR2_VERSION 41u
+#define BPFLOADER_PLATFORM_VERSION BPFLOADER_U_QPR2_VERSION
// Android Mainline - this bpfloader should eventually go back to T (or even S)
// Note: this value (and the following +1u's) are hardcoded in NetBpfLoad.cpp
@@ -55,8 +56,11 @@
// Android Mainline BpfLoader when running on Android U
#define BPFLOADER_MAINLINE_U_VERSION (BPFLOADER_MAINLINE_T_VERSION + 1u)
+// Android Mainline BpfLoader when running on Android U QPR3
+#define BPFLOADER_MAINLINE_U_QPR3_VERSION (BPFLOADER_MAINLINE_U_VERSION + 1u)
+
// Android Mainline BpfLoader when running on Android V
-#define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_VERSION + 1u)
+#define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_QPR3_VERSION + 1u)
/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
* before #include "bpf_helpers.h" to change which bpfloaders will
@@ -113,6 +117,9 @@
unsigned _btf_min_bpfloader_ver SECTION("btf_min_bpfloader_ver") = 39u; \
unsigned _btf_user_min_bpfloader_ver SECTION("btf_user_min_bpfloader_ver") = 0xFFFFFFFFu
+#define DISABLE_ON_MAINLINE_BEFORE_U_QPR3() \
+ unsigned _netbpfload_min_ver SECTION("netbpfload_min_ver") = BPFLOADER_MAINLINE_U_QPR3_VERSION;
+
/* flag the resulting bpf .o file as critical to system functionality,
* loading all kernel version appropriate programs in it must succeed
* for bpfloader success
@@ -133,6 +140,7 @@
#define KVER_5_4 KVER(5, 4, 0)
#define KVER_5_8 KVER(5, 8, 0)
#define KVER_5_9 KVER(5, 9, 0)
+#define KVER_5_10 KVER(5, 10, 0)
#define KVER_5_15 KVER(5, 15, 0)
#define KVER_6_1 KVER(6, 1, 0)
#define KVER_6_6 KVER(6, 6, 0)
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index cb02de8..73cef89 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -16,8 +16,11 @@
#pragma once
+#include <stdlib.h>
+#include <unistd.h>
#include <linux/bpf.h>
#include <linux/unistd.h>
+#include <sys/file.h>
#ifdef BPF_FD_JUST_USE_INT
#define BPF_FD_TYPE int
@@ -128,20 +131,61 @@
});
}
-inline int mapRetrieve(const char* pathname, uint32_t flag) {
- return bpfFdGet(pathname, flag);
+int bpfGetFdMapId(const BPF_FD_TYPE map_fd);
+
+inline int bpfLock(int fd, short type) {
+ if (fd < 0) return fd; // pass any errors straight through
+#ifdef BPF_MAP_LOCKLESS_FOR_TEST
+ return fd;
+#endif
+#ifdef BPF_FD_JUST_USE_INT
+ int mapId = bpfGetFdMapId(fd);
+ int saved_errno = errno;
+#else
+ base::unique_fd ufd(fd);
+ int mapId = bpfGetFdMapId(ufd);
+ int saved_errno = errno;
+ (void)ufd.release();
+#endif
+ // 4.14+ required to fetch map id, but we don't want to call isAtLeastKernelVersion
+ if (mapId == -1 && saved_errno == EINVAL) return fd;
+ if (mapId <= 0) abort(); // should not be possible
+
+ // on __LP64__ (aka. 64-bit userspace) 'struct flock64' is the same as 'struct flock'
+ struct flock64 fl = {
+ .l_type = type, // short: F_{RD,WR,UN}LCK
+ .l_whence = SEEK_SET, // short: SEEK_{SET,CUR,END}
+ .l_start = mapId, // off_t: start offset
+ .l_len = 1, // off_t: number of bytes
+ };
+
+ // see: bionic/libc/bionic/fcntl.cpp: iff !__LP64__ this uses fcntl64
+ int ret = fcntl(fd, F_OFD_SETLK, &fl);
+ if (!ret) return fd; // success
+ close(fd);
+ return ret; // most likely -1 with errno == EAGAIN, due to already held lock
+}
+
+inline int mapRetrieveLocklessRW(const char* pathname) {
+ return bpfFdGet(pathname, 0);
+}
+
+inline int mapRetrieveExclusiveRW(const char* pathname) {
+ return bpfLock(mapRetrieveLocklessRW(pathname), F_WRLCK);
}
inline int mapRetrieveRW(const char* pathname) {
- return mapRetrieve(pathname, 0);
+ return bpfLock(mapRetrieveLocklessRW(pathname), F_RDLCK);
}
inline int mapRetrieveRO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_RDONLY);
+ return bpfFdGet(pathname, BPF_F_RDONLY);
}
+// WARNING: it's impossible to grab a shared (ie. read) lock on a write-only fd,
+// so we instead choose to grab an exclusive (ie. write) lock.
inline int mapRetrieveWO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_WRONLY);
+ return bpfLock(bpfFdGet(pathname, BPF_F_WRONLY), F_WRLCK);
}
inline int retrieveProgram(const char* pathname) {
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
index b92f107..1923ceb 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -35,7 +35,24 @@
jstring path, jint mode, jint keySize, jint valueSize) {
ScopedUtfChars pathname(env, path);
- jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast<unsigned>(mode));
+ jint fd = -1;
+ switch (mode) {
+ case 0:
+ fd = bpf::mapRetrieveRW(pathname.c_str());
+ break;
+ case BPF_F_RDONLY:
+ fd = bpf::mapRetrieveRO(pathname.c_str());
+ break;
+ case BPF_F_WRONLY:
+ fd = bpf::mapRetrieveWO(pathname.c_str());
+ break;
+ case BPF_F_RDONLY|BPF_F_WRONLY:
+ fd = bpf::mapRetrieveExclusiveRW(pathname.c_str());
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
if (fd < 0) {
jniThrowErrnoException(env, "nativeBpfFdGet", errno);
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
index 4c3fde6..b5e3dff 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
@@ -69,11 +69,11 @@
}
@Test
- public void testGetValueAsIntger() {
+ public void testGetValueAsInteger() {
final StructNlAttr attr1 = new StructNlAttr(IFA_FLAGS, TEST_ADDR_FLAGS);
final Integer integer1 = attr1.getValueAsInteger();
final int int1 = attr1.getValueAsInt(0x08 /* default value */);
- assertEquals(integer1, new Integer(TEST_ADDR_FLAGS));
+ assertEquals(integer1, Integer.valueOf(TEST_ADDR_FLAGS));
assertEquals(int1, TEST_ADDR_FLAGS);
// Malformed attribute.
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index df6067d..e634f0e 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -34,17 +34,13 @@
private val connectUtil by lazy { ConnectUtil(context) }
@Test
- fun testCheckConnectivity() {
- checkWifiSetup()
- checkTelephonySetup()
- }
-
- private fun checkWifiSetup() {
+ fun testCheckWifiSetup() {
if (!pm.hasSystemFeature(FEATURE_WIFI)) return
connectUtil.ensureWifiValidated()
}
- private fun checkTelephonySetup() {
+ @Test
+ fun testCheckTelephonySetup() {
if (!pm.hasSystemFeature(FEATURE_TELEPHONY)) return
val tm = context.getSystemService(TelephonyManager::class.java)
?: fail("Could not get telephony service")
@@ -52,7 +48,7 @@
val commonError = "Check the test bench. To run the tests anyway for quick & dirty local " +
"testing, you can use atest X -- " +
"--test-arg com.android.testutils.ConnectivityTestTargetPreparer" +
- ":ignore-connectivity-check:true"
+ ":ignore-mobile-data-check:true"
// Do not use assertEquals: it outputs "expected X, was Y", which looks like a test failure
if (tm.simState == TelephonyManager.SIM_STATE_ABSENT) {
fail("The device has no SIM card inserted. $commonError")
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
index 600a623..435fdd8 100644
--- a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
+++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -28,9 +28,11 @@
private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk"
private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer"
private const val CONNECTIVITY_CHECK_CLASS = "$CONNECTIVITY_PKG_NAME.ConnectivityCheckTest"
+
// As per the <instrumentation> defined in the checker manifest
private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner"
-private const val IGNORE_CONN_CHECK_OPTION = "ignore-connectivity-check"
+private const val IGNORE_WIFI_CHECK = "ignore-wifi-check"
+private const val IGNORE_MOBILE_DATA_CHECK = "ignore-mobile-data-check"
// The default updater package names, which might be updating packages while the CTS
// are running
@@ -41,14 +43,23 @@
*
* For quick and dirty local testing, the connectivity check can be disabled by running tests with
* "atest -- \
- * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-connectivity-check:true".
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-mobile-data-check:true". \
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-wifi-check:true".
*/
open class ConnectivityTestTargetPreparer : BaseTargetPreparer() {
private val installer = SuiteApkInstaller()
- @Option(name = IGNORE_CONN_CHECK_OPTION,
- description = "Disables the check for mobile data and wifi")
- private var ignoreConnectivityCheck = false
+ @Option(
+ name = IGNORE_WIFI_CHECK,
+ description = "Disables the check for wifi"
+ )
+ private var ignoreWifiCheck = false
+ @Option(
+ name = IGNORE_MOBILE_DATA_CHECK,
+ description = "Disables the check for mobile data"
+ )
+ private var ignoreMobileDataCheck = false
+
// The default value is never used, but false is a reasonable default
private var originalTestChainEnabled = false
private val originalUpdaterPkgsStatus = HashMap<String, Boolean>()
@@ -58,43 +69,62 @@
disableGmsUpdate(testInfo)
originalTestChainEnabled = getTestChainEnabled(testInfo)
originalUpdaterPkgsStatus.putAll(getUpdaterPkgsStatus(testInfo))
- setUpdaterNetworkingEnabled(testInfo, enableChain = true,
- enablePkgs = UPDATER_PKGS.associateWith { false })
- runPreparerApk(testInfo)
+ setUpdaterNetworkingEnabled(
+ testInfo,
+ enableChain = true,
+ enablePkgs = UPDATER_PKGS.associateWith { false }
+ )
+ runConnectivityCheckApk(testInfo)
+ refreshTime(testInfo)
}
- private fun runPreparerApk(testInfo: TestInformation) {
+ private fun runConnectivityCheckApk(testInfo: TestInformation) {
installer.setCleanApk(true)
installer.addTestFileName(CONNECTIVITY_CHECKER_APK)
installer.setShouldGrantPermission(true)
installer.setUp(testInfo)
+ val testMethods = mutableListOf<String>()
+ if (!ignoreWifiCheck) {
+ testMethods.add("testCheckWifiSetup")
+ }
+ if (!ignoreMobileDataCheck) {
+ testMethods.add("testCheckTelephonySetup")
+ }
+
+ testMethods.forEach {
+ runTestMethod(testInfo, it)
+ }
+ }
+
+ private fun runTestMethod(testInfo: TestInformation, method: String) {
val runner = DefaultRemoteAndroidTestRunner(
- CONNECTIVITY_PKG_NAME,
- CONNECTIVITY_CHECK_RUNNER_NAME,
- testInfo.device.iDevice)
+ CONNECTIVITY_PKG_NAME,
+ CONNECTIVITY_CHECK_RUNNER_NAME,
+ testInfo.device.iDevice
+ )
runner.runOptions = "--no-hidden-api-checks"
+ runner.setMethodName(CONNECTIVITY_CHECK_CLASS, method)
val receiver = CollectingTestListener()
if (!testInfo.device.runInstrumentationTests(runner, receiver)) {
- throw TargetSetupError("Device state check failed to complete",
- testInfo.device.deviceDescriptor)
+ throw TargetSetupError(
+ "Device state check failed to complete",
+ testInfo.device.deviceDescriptor
+ )
}
val runResult = receiver.currentRunResults
if (runResult.isRunFailure) {
- throw TargetSetupError("Failed to check device state before the test: " +
- runResult.runFailureMessage, testInfo.device.deviceDescriptor)
- }
-
- val ignoredTestClasses = mutableSetOf<String>()
- if (ignoreConnectivityCheck) {
- ignoredTestClasses.add(CONNECTIVITY_CHECK_CLASS)
+ throw TargetSetupError(
+ "Failed to check device state before the test: " +
+ runResult.runFailureMessage,
+ testInfo.device.deviceDescriptor
+ )
}
val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) ->
- if (TestResult.TestStatus.FAILURE != testResult.status ||
- ignoredTestClasses.contains(testDescription.className)) {
+ if (TestResult.TestStatus.FAILURE != testResult.status) {
null
} else {
"$testDescription: ${testResult.stackTrace}"
@@ -102,21 +132,27 @@
}.joinToString("\n")
if (errorMsg.isBlank()) return
- throw TargetSetupError("Device setup checks failed. Check the test bench: \n$errorMsg",
- testInfo.device.deviceDescriptor)
+ throw TargetSetupError(
+ "Device setup checks failed. Check the test bench: \n$errorMsg",
+ testInfo.device.deviceDescriptor
+ )
}
private fun disableGmsUpdate(testInfo: TestInformation) {
// This will be a no-op on devices without root (su) or not using gservices, but that's OK.
- testInfo.exec("su 0 am broadcast " +
+ testInfo.exec(
+ "su 0 am broadcast " +
"-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
- "-e finsky.play_services_auto_update_enabled false")
+ "-e finsky.play_services_auto_update_enabled false"
+ )
}
private fun clearGmsUpdateOverride(testInfo: TestInformation) {
- testInfo.exec("su 0 am broadcast " +
+ testInfo.exec(
+ "su 0 am broadcast " +
"-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
- "--esn finsky.play_services_auto_update_enabled")
+ "--esn finsky.play_services_auto_update_enabled"
+ )
}
private fun setUpdaterNetworkingEnabled(
@@ -136,17 +172,27 @@
testInfo.exec("cmd connectivity get-chain3-enabled").contains("chain:enabled")
private fun getUpdaterPkgsStatus(testInfo: TestInformation) =
- UPDATER_PKGS.associateWith { pkg ->
- !testInfo.exec("cmd connectivity get-package-networking-enabled $pkg")
- .contains(":deny")
- }
+ UPDATER_PKGS.associateWith { pkg ->
+ !testInfo.exec("cmd connectivity get-package-networking-enabled $pkg")
+ .contains(":deny")
+ }
+
+ private fun refreshTime(testInfo: TestInformation,) {
+ // Forces a synchronous time refresh using the network. Time is fetched synchronously but
+ // this does not guarantee that system time is updated when it returns.
+ // This avoids flakes where the system clock rolls back, for example when using test
+ // settings like test_url_expiration_time in NetworkMonitor.
+ testInfo.exec("cmd network_time_update_service force_refresh")
+ }
override fun tearDown(testInfo: TestInformation, e: Throwable?) {
if (isTearDownDisabled) return
installer.tearDown(testInfo, e)
- setUpdaterNetworkingEnabled(testInfo,
- enableChain = originalTestChainEnabled,
- enablePkgs = originalUpdaterPkgsStatus)
+ setUpdaterNetworkingEnabled(
+ testInfo,
+ enableChain = originalTestChainEnabled,
+ enablePkgs = originalUpdaterPkgsStatus
+ )
clearGmsUpdateOverride(testInfo)
}
}
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 6eb56c7b..0f0e2f1 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -61,6 +61,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.os.Process.INVALID_UID;
+
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
@@ -68,6 +69,7 @@
import static com.android.testutils.MiscAsserts.assertEmpty;
import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -83,21 +85,25 @@
import android.os.Build;
import android.util.ArraySet;
import android.util.Range;
+
import androidx.test.filters.SmallTest;
+
import com.android.testutils.CompatUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
// NetworkCapabilities is only updatable on S+, and this test covers behavior which implementation
@@ -1465,4 +1471,26 @@
assertEquals("-SUPL-VALIDATED-CAPTIVE_PORTAL+MMS+OEM_PAID",
nc1.describeCapsDifferencesFrom(nc2));
}
+
+ @Test
+ public void testInvalidCapability() {
+ final int invalidCapability = Integer.MAX_VALUE;
+ // Passing invalid capability does not throw
+ final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING)
+ .removeCapability(invalidCapability)
+ .removeForbiddenCapability(invalidCapability)
+ .addCapability(invalidCapability)
+ .addForbiddenCapability(invalidCapability)
+ .build();
+
+ final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING)
+ .build();
+
+ // nc1 and nc2 are the same since invalid capability is ignored
+ assertEquals(nc1, nc2);
+ }
}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
index da633c0..00f67f4 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
@@ -16,7 +16,7 @@
package com.android.cts.netpolicy.hostside;
-import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static org.junit.Assume.assumeTrue;
@@ -46,14 +46,15 @@
public final void tearDown() throws Exception {
super.tearDown();
+ stopApp();
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
}
@Test
public void testFgsNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
@@ -61,8 +62,8 @@
@Test
public void testActivityNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
@@ -70,23 +71,23 @@
@Test
public void testBackgroundNetworkAccess_inFullAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
assertNetworkAccess(true, null);
}
@Test
public void testBackgroundNetworkAccess_inExceptIdleAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
assertNetworkAccess(true, null);
}
}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index d0203c5..0f5f58c 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -96,7 +96,12 @@
protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
// TODO(b/321797685): Configure it via device-config once it is available.
- protected static final long PROCESS_STATE_TRANSITION_DELAY_MS = TimeUnit.SECONDS.toMillis(5);
+ protected final long mProcessStateTransitionLongDelayMs =
+ useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(20)
+ : TimeUnit.SECONDS.toMillis(5);
+ protected final long mProcessStateTransitionShortDelayMs =
+ useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(2)
+ : TimeUnit.SECONDS.toMillis(5);
private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
@@ -241,6 +246,22 @@
return Boolean.parseBoolean(output);
}
+ /**
+ * Check if the flag to use different delays for sensitive proc-states is enabled.
+ * This is a manual check because the feature flag infrastructure may not be available
+ * in all the branches that will get this code.
+ * TODO: b/322115994 - Use @RequiresFlagsEnabled with
+ * Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN once the tests are moved to cts.
+ */
+ private boolean useDifferentDelaysForBackgroundChain() {
+ if (!SdkLevel.isAtLeastV()) {
+ return false;
+ }
+ final String output = executeShellCommand("device_config get backstage_power"
+ + " com.android.server.net.use_different_delays_for_background_chain");
+ return Boolean.parseBoolean(output);
+ }
+
protected int getUid(String packageName) throws Exception {
return mContext.getPackageManager().getPackageUid(packageName, 0);
}
@@ -824,6 +845,10 @@
assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
}
+ protected void stopApp() {
+ executeSilentShellCommand("am stop-app " + TEST_APP2_PKG);
+ }
+
protected void setAppIdle(boolean isIdle) throws Exception {
setAppIdleNoAssert(isIdle);
assertAppIdle(isIdle);
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
index 811190f..bfccce9 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
@@ -53,7 +53,7 @@
@After
public final void tearDown() throws Exception {
super.tearDown();
- finishActivity();
+ stopApp();
resetDeviceState();
}
@@ -108,7 +108,7 @@
assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
assertLaunchedActivityHasNetworkAccess("testStartActivity_default", () -> {
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkAccess(false, null);
});
}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
index 7038d02..3934cfa 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
@@ -268,6 +268,7 @@
setRestrictBackground(false);
setBatterySaverMode(false);
unregisterNetworkCallback();
+ stopApp();
if (SdkLevel.isAtLeastT() && (mCtsNetUtils != null)) {
mCtsNetUtils.restorePrivateDnsSetting();
@@ -387,7 +388,7 @@
finishActivity();
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkAccess(false, null);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
@@ -413,7 +414,7 @@
finishActivity();
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkAccess(false, null);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
index 9b3fe9f..6c5f2ff 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
@@ -17,6 +17,7 @@
package com.android.cts.netpolicy.hostside;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.os.Process.SYSTEM_UID;
@@ -65,6 +66,7 @@
setRestrictBackground(false);
setRestrictedNetworkingMode(false);
unregisterNetworkCallback();
+ stopApp();
}
@Test
@@ -248,8 +250,8 @@
assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
try {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
@@ -260,7 +262,7 @@
finishActivity();
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
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 e88c105..e186c6b 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
@@ -971,8 +971,12 @@
final TestableNetworkCallback otherUidCallback = new TestableNetworkCallback();
final TestableNetworkCallback myUidCallback = new TestableNetworkCallback();
if (SdkLevel.isAtLeastS()) {
- final int otherUid =
- UserHandle.of(5 /* userId */).getUid(Process.FIRST_APPLICATION_UID);
+ // Using the same appId with the test to make sure otherUid has the internet permission.
+ // This works because the UID permission map only stores the app ID and not the whole
+ // UID. If the otherUid does not have the internet permission, network access from
+ // otherUid could be considered blocked on V+.
+ final int appId = UserHandle.getAppId(Process.myUid());
+ final int otherUid = UserHandle.of(5 /* userId */).getUid(appId);
final Handler h = new Handler(Looper.getMainLooper());
runWithShellPermissionIdentity(() -> {
registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index ad25562..12ea23b 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -35,4 +35,5 @@
"general-tests",
"sts",
],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
index 100b6e4..c220000 100644
--- a/tests/cts/hostside/networkslicingtestapp/Android.bp
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -44,6 +44,7 @@
"CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithoutProperty.xml",
+ sdk_version: "test_current",
}
android_test_helper_app {
@@ -53,6 +54,7 @@
"CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithProperty.xml",
+ sdk_version: "test_current",
}
android_test_helper_app {
@@ -63,4 +65,5 @@
],
target_sdk_version: "33",
manifest: "AndroidManifestWithoutProperty.xml",
+ sdk_version: "test_current",
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index f0a87af..cea60f9 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -18,8 +18,6 @@
import android.platform.test.annotations.RequiresDevice;
-import com.android.testutils.SkipPresubmit;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -37,13 +35,11 @@
uninstallPackage(TEST_APP2_PKG, true);
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testChangeUnderlyingNetworks() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testChangeUnderlyingNetworks");
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testDefault() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
@@ -166,7 +162,6 @@
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testSetVpnDefaultForUids() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetVpnDefaultForUids");
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 768ba12..ae85701 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -180,4 +180,5 @@
"cts",
"general-tests",
],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 38f26d8..077c3ef 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -24,6 +24,11 @@
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk" />
<option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.tethering.apex" />
<option name="not-shardable" value="true" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+ <option name="target" value="device" />
+ <option name="config-filename" value="{MODULE}" />
+ <option name="version" value="1.0" />
+ </target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="{MODULE}.apk" />
@@ -38,6 +43,7 @@
<option name="runtime-hint" value="9m4s" />
<option name="hidden-api-checks" value="false" />
<option name="isolated-storage" value="false" />
+ <option name="instrumentation-arg" key="test-module-name" value="{MODULE}" />
<!-- Test filter that allows test APKs to select which tests they want to run by annotating
those tests with an annotation matching the name of the APK.
diff --git a/tests/cts/net/DynamicConfig.xml b/tests/cts/net/DynamicConfig.xml
new file mode 100644
index 0000000..af019c2
--- /dev/null
+++ b/tests/cts/net/DynamicConfig.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<dynamicConfig>
+ <entry key="remote_config_required">
+ <value>false</value>
+ </entry>
+ <entry key="IP_ADDRESS_ECHO_URL">
+ <value>https://google-ipv6test.appspot.com/ip.js?fmt=text</value>
+ </entry>
+</dynamicConfig>
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 2ec3a70..587d5a5 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -56,4 +56,5 @@
":CtsNetTestAppForApi23",
],
per_testcase_directory: true,
+ sdk_version: "test_current",
}
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index 8e24fba..de4a3bf 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -3,6 +3,11 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+filegroup {
+ name: "dns_async_test_default_map",
+ srcs: ["dns_async_test_default.map"],
+}
+
cc_defaults {
name: "dns_async_defaults",
@@ -20,6 +25,14 @@
srcs: [
"NativeDnsAsyncTest.cpp",
],
+ // This test runs on older platform versions, so many libraries (such as libbase and libc++)
+ // need to be linked statically. The test also needs to be linked with a version script to
+ // ensure that the statically-linked library isn't exported from the executable, where it
+ // would override the shared libraries that the platform itself uses.
+ // See http://b/333438055 for an example of what goes wrong when libc++ is partially exported
+ // from an executable.
+ version_script: ":dns_async_test_default_map",
+ stl: "libc++_static",
shared_libs: [
"libandroid",
"liblog",
diff --git a/tests/cts/net/native/dns/dns_async_test_default.map b/tests/cts/net/native/dns/dns_async_test_default.map
new file mode 100644
index 0000000..e342e43
--- /dev/null
+++ b/tests/cts/net/native/dns/dns_async_test_default.map
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+{
+ local:
+ *;
+};
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 90fb7a9..d233fee 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -55,6 +55,7 @@
import com.android.compatibility.common.util.PropertyUtil.getVsrApiLevel
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.compatibility.common.util.VsrTest
import com.android.internal.util.HexDump
import com.android.net.module.util.PacketReader
import com.android.testutils.DevSdkIgnoreRule
@@ -300,6 +301,10 @@
}
}
+ @VsrTest(
+ requirements = ["VSR-5.3.12-001", "VSR-5.3.12-003", "VSR-5.3.12-004", "VSR-5.3.12-009",
+ "VSR-5.3.12-012"]
+ )
@Test
fun testApfCapabilities() {
// APF became mandatory in Android 14 VSR.
@@ -350,10 +355,14 @@
return HexDump.hexStringToByteArray(progHexString)
}
+ @VsrTest(
+ requirements = ["VSR-5.3.12-007", "VSR-5.3.12-008", "VSR-5.3.12-010", "VSR-5.3.12-011"]
+ )
@SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.")
// APF integration is mostly broken before V, only run the full read / write test on V+.
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @Test
+ // Increase timeout for test to 15 minutes to accommodate device with large APF RAM.
+ @Test(timeout = 15 * 60 * 1000)
fun testReadWriteProgram() {
assumeApfVersionSupportAtLeast(4)
@@ -400,6 +409,7 @@
}
// APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun testDropPingReply() {
@@ -448,6 +458,7 @@
fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize))
// APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun testPrefilledMemorySlotsV4() {
@@ -503,6 +514,7 @@
}
// APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun testFilterAgeIncreasesBetweenPackets() {
@@ -542,6 +554,7 @@
buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
val filterAgeSeconds = buffer.getInt()
// Assert that filter age has increased, but not too much.
- assertThat(filterAgeSeconds - filterAgeSecondsOrig).isEqualTo(5)
+ val timeDiff = filterAgeSeconds - filterAgeSecondsOrig
+ assertThat(timeDiff).isAnyOf(5, 6)
}
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index bf40462..21eb90f 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -174,6 +174,7 @@
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
@@ -192,10 +193,11 @@
import android.util.Log;
import android.util.Range;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
@@ -213,7 +215,6 @@
import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
-import com.android.testutils.SkipMainlinePresubmit;
import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
@@ -249,6 +250,7 @@
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -309,6 +311,7 @@
// Airplane Mode BroadcastReceiver Timeout
private static final long AIRPLANE_MODE_CHANGE_TIMEOUT_MS = 10_000L;
+ private static final long CELL_DATA_AVAILABLE_TIMEOUT_MS = 120_000L;
// Timeout for applying uids allowed on restricted networks
private static final long APPLYING_UIDS_ALLOWED_ON_RESTRICTED_NETWORKS_TIMEOUT_MS = 3_000L;
@@ -334,6 +337,11 @@
private static final String TEST_HTTPS_URL_PATH = "/https_path";
private static final String TEST_HTTP_URL_PATH = "/http_path";
private static final String LOCALHOST_HOSTNAME = "localhost";
+ private static final String TEST_MODULE_NAME_OPTION = "test-module-name";
+ private static final String IP_ADDRESS_ECHO_URL_KEY = "IP_ADDRESS_ECHO_URL";
+ private static final List<String> ALLOWED_IP_ADDRESS_ECHO_URLS = Arrays.asList(
+ "https://google-ipv6test.appspot.com/ip.js?fmt=text",
+ "https://ipv6test.googleapis-cn.com/ip.js?fmt=text");
// Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
private static final long WIFI_CONNECT_TIMEOUT_MS = 60_000L;
@@ -854,7 +862,7 @@
* Tests that connections can be opened on WiFi and cellphone networks,
* and that they are made from different IP addresses.
*/
- @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @AppModeFull(reason = "Cannot get WifiManager or access the SD card in instant app mode")
@Test
@RequiresDevice // Virtual devices use a single internet connection for all networks
public void testOpenConnection() throws Exception {
@@ -864,7 +872,8 @@
Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
Network cellNetwork = networkCallbackRule.requestCell();
// This server returns the requestor's IP address as the response body.
- URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
+ String ipAddressEchoUrl = getIpAddressEchoUrlFromConfig();
+ URL url = new URL(ipAddressEchoUrl);
String wifiAddressString = httpGet(wifiNetwork, url);
String cellAddressString = httpGet(cellNetwork, url);
@@ -881,6 +890,19 @@
}
/**
+ * Gets IP address echo url from dynamic config.
+ */
+ private static String getIpAddressEchoUrlFromConfig() throws Exception {
+ Bundle instrumentationArgs = InstrumentationRegistry.getArguments();
+ String testModuleName = instrumentationArgs.getString(TEST_MODULE_NAME_OPTION);
+ // Get the DynamicConfig.xml contents and extract the ipv6 test URL.
+ DynamicConfigDeviceSide dynamicConfig = new DynamicConfigDeviceSide(testModuleName);
+ String ipAddressEchoUrl = dynamicConfig.getValue(IP_ADDRESS_ECHO_URL_KEY);
+ assertContains(ALLOWED_IP_ADDRESS_ECHO_URLS, ipAddressEchoUrl);
+ return ipAddressEchoUrl;
+ }
+
+ /**
* Performs a HTTP GET to the specified URL on the specified Network, and returns
* the response body decoded as UTF-8.
*/
@@ -1031,7 +1053,6 @@
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
- @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testIsPrivateDnsBroken() throws InterruptedException {
final String invalidPrivateDnsServer = "invalidhostname.example.com";
final String goodPrivateDnsServer = "dns.google";
@@ -1236,13 +1257,14 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(broadcastAction);
+ final CompletableFuture<NetworkRequest> requestFuture = new CompletableFuture<>();
final CompletableFuture<Network> networkFuture = new CompletableFuture<>();
final AtomicInteger receivedCount = new AtomicInteger(0);
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final NetworkRequest request = intent.getParcelableExtra(EXTRA_NETWORK_REQUEST);
- assertPendingIntentRequestMatches(request, secondRequest, useListen);
+ requestFuture.complete(request);
receivedCount.incrementAndGet();
networkFuture.complete(intent.getParcelableExtra(EXTRA_NETWORK));
}
@@ -1257,6 +1279,9 @@
} catch (TimeoutException e) {
throw new AssertionError("PendingIntent not received for " + secondRequest, e);
}
+ assertPendingIntentRequestMatches(
+ requestFuture.get(NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS),
+ secondRequest, useListen);
// Sleep for a small amount of time to try to check that only one callback is ever
// received (so the first callback was really unregistered). This does not guarantee
@@ -2242,7 +2267,10 @@
// connectToCell only registers a request, it cannot / does not need to be called twice
mCtsNetUtils.ensureWifiConnected();
if (verifyWifi) waitForAvailable(wifiCb);
- if (supportTelephony) waitForAvailable(telephonyCb);
+ if (supportTelephony) {
+ telephonyCb.eventuallyExpect(
+ CallbackEntry.AVAILABLE, CELL_DATA_AVAILABLE_TIMEOUT_MS);
+ }
} finally {
// Restore the previous state of airplane mode and permissions:
runShellCommand("cmd connectivity airplane-mode "
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 6fa2812..61ebd8f 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -303,18 +303,6 @@
fun expectOnAvailable(timeout: Long = TIMEOUT_MS): String {
return available.get(timeout, TimeUnit.MILLISECONDS)
}
-
- fun expectOnUnavailable() {
- // Assert that the future fails with the IllegalStateException from the
- // completeExceptionally() call inside onUnavailable.
- assertFailsWith(IllegalStateException::class) {
- try {
- available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
- } catch (e: ExecutionException) {
- throw e.cause!!
- }
- }
- }
}
private class EthernetOutcomeReceiver :
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 5b53839..ff10e1a 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -19,19 +19,19 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static com.android.testutils.DevSdkIgnoreRuleKt.VANILLA_ICE_CREAM;
-
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
@@ -66,9 +66,9 @@
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -512,30 +512,20 @@
assertArrayEquals(netCapabilities, nr.getCapabilities());
}
- @Test @IgnoreUpTo(VANILLA_ICE_CREAM) @Ignore("b/338200742")
+ // Default capabilities and default forbidden capabilities must not be changed on U- because
+ // this could cause the system server crash when there is a module rollback (b/313030307)
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R) @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testDefaultCapabilities() {
final NetworkRequest defaultNR = new NetworkRequest.Builder().build();
- assertTrue(defaultNR.hasForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK));
- assertFalse(defaultNR.hasCapability(NET_CAPABILITY_LOCAL_NETWORK));
+
+ assertEquals(4, defaultNR.getCapabilities().length);
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_TRUSTED));
assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VPN));
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
- final NetworkCapabilities emptyNC =
- NetworkCapabilities.Builder.withoutDefaultCapabilities().build();
- assertFalse(defaultNR.canBeSatisfiedBy(emptyNC));
-
- // defaultNC represent the capabilities of a network agent, so they must not contain
- // forbidden capabilities by default.
- final NetworkCapabilities defaultNC = new NetworkCapabilities.Builder().build();
- assertArrayEquals(new int[0], defaultNC.getForbiddenCapabilities());
- // A default NR can be satisfied by default NC.
- assertTrue(defaultNR.canBeSatisfiedBy(defaultNC));
-
- // Conversely, network requests have forbidden capabilities by default to manage
- // backward compatibility, so test that these forbidden capabilities are in place.
- // Starting in V, NET_CAPABILITY_LOCAL_NETWORK is introduced but is not seen by
- // default, thanks to a default forbidden capability in NetworkRequest.
- defaultNC.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
- assertFalse(defaultNR.canBeSatisfiedBy(defaultNC));
+ assertEquals(0, defaultNR.getForbiddenCapabilities().length);
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
index 1b1f367..a3c3f45 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -72,7 +72,7 @@
tryTest {
downstreamIface = createTestInterface()
- val iface = tetheredInterface
+ val iface = mTetheredInterfaceRequester.getInterface()
assertEquals(iface, downstreamIface?.interfaceName)
val request = TetheringRequest.Builder(TETHERING_ETHERNET)
.setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build()
@@ -115,7 +115,7 @@
tryTest {
downstreamIface = createTestInterface()
- val iface = tetheredInterface
+ val iface = mTetheredInterfaceRequester.getInterface()
assertEquals(iface, downstreamIface?.interfaceName)
val localAddr = LinkAddress("192.0.2.3/28")
diff --git a/tests/cts/netpermission/internetpermission/Android.bp b/tests/cts/netpermission/internetpermission/Android.bp
index 7d5ca2f..e0424ac 100644
--- a/tests/cts/netpermission/internetpermission/Android.bp
+++ b/tests/cts/netpermission/internetpermission/Android.bp
@@ -31,4 +31,5 @@
"general-tests",
],
host_required: ["net-tests-utils-host-common"],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/netpermission/updatestatspermission/Android.bp b/tests/cts/netpermission/updatestatspermission/Android.bp
index 2fde1ce..689ce74 100644
--- a/tests/cts/netpermission/updatestatspermission/Android.bp
+++ b/tests/cts/netpermission/updatestatspermission/Android.bp
@@ -19,11 +19,17 @@
android_test {
name: "CtsNetTestCasesUpdateStatsPermission",
- defaults: ["cts_defaults"],
+ defaults: [
+ "cts_defaults",
+ "framework-connectivity-test-defaults",
+ ],
srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
+ platform_apis: true,
+ static_libs: [
+ "ctstestrunner-axt",
+ "net-tests-utils",
+ ],
// Tag this module as a cts test artifact
test_suites: [
diff --git a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
index bea843c..56bad31 100644
--- a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
+++ b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
@@ -16,6 +16,10 @@
package android.net.cts.networkpermission.updatestatspermission;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -25,6 +29,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.build.SdkLevel;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,6 +73,11 @@
out.write(buf);
out.close();
socket.close();
+ // Clear TrafficStats cache is needed to avoid rate-limit caching for
+ // TrafficStats API results on V+ devices.
+ if (SdkLevel.isAtLeastV()) {
+ runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
+ }
long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta="
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 51a4eca..b02c61c 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -68,6 +68,8 @@
TETHERING "map_offload_tether_upstream6_map",
TETHERING "map_test_bitmap",
TETHERING "map_test_tether_downstream6_map",
+ TETHERING "map_test_tether2_downstream6_map",
+ TETHERING "map_test_tether3_downstream6_map",
TETHERING "prog_offload_schedcls_tether_downstream4_ether",
TETHERING "prog_offload_schedcls_tether_downstream4_rawip",
TETHERING "prog_offload_schedcls_tether_downstream6_ether",
@@ -91,6 +93,8 @@
SHARED "map_dscpPolicy_ipv4_dscp_policies_map",
SHARED "map_dscpPolicy_ipv6_dscp_policies_map",
SHARED "map_dscpPolicy_socket_policy_cache_map",
+ SHARED "map_gentle_test",
+ SHARED "prog_gentle_skfilter_accept",
NETD "map_netd_app_uid_stats_map",
NETD "map_netd_configuration_map",
NETD "map_netd_cookie_tag_map",
@@ -141,6 +145,27 @@
NETD "map_netd_packet_trace_ringbuf",
};
+// Provided by *current* mainline module for V+ devices
+static const set<string> MAINLINE_FOR_V_PLUS = {
+ NETD "prog_netd_connect4_inet4_connect",
+ NETD "prog_netd_connect6_inet6_connect",
+ NETD "prog_netd_recvmsg4_udp4_recvmsg",
+ NETD "prog_netd_recvmsg6_udp6_recvmsg",
+ NETD "prog_netd_sendmsg4_udp4_sendmsg",
+ NETD "prog_netd_sendmsg6_udp6_sendmsg",
+};
+
+// Provided by *current* mainline module for V+ devices with 5.4+ kernels
+static const set<string> MAINLINE_FOR_V_5_4_PLUS = {
+ NETD "prog_netd_getsockopt_prog",
+ NETD "prog_netd_setsockopt_prog",
+};
+
+// Provided by *current* mainline module for U+ devices with 5.10+ kernels
+static const set<string> MAINLINE_FOR_V_5_10_PLUS = {
+ NETD "prog_netd_cgroupsockrelease_inet_release",
+};
+
static void addAll(set<string>& a, const set<string>& b) {
a.insert(b.begin(), b.end());
}
@@ -188,6 +213,9 @@
// V requires Linux Kernel 4.19+, but nothing (as yet) added or removed in V.
if (IsAtLeastV()) ASSERT_TRUE(isAtLeastKernelVersion(4, 19, 0));
+ DO_EXPECT(IsAtLeastV(), MAINLINE_FOR_V_PLUS);
+ DO_EXPECT(IsAtLeastV() && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_V_5_4_PLUS);
+ DO_EXPECT(IsAtLeastV() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_V_5_10_PLUS);
for (const auto& file : mustExist) {
EXPECT_EQ(0, access(file.c_str(), R_OK)) << file << " does not exist";
diff --git a/tests/native/utilities/firewall.h b/tests/native/utilities/firewall.h
index b3d69bf..a5cb0b9 100644
--- a/tests/native/utilities/firewall.h
+++ b/tests/native/utilities/firewall.h
@@ -18,6 +18,7 @@
#pragma once
#include <android-base/thread_annotations.h>
+#define BPF_MAP_LOCKLESS_FOR_TEST
#include <bpf/BpfMap.h>
#include "netd.h"
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 44a8222..8526a9a 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -2178,9 +2178,8 @@
switch (name) {
case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER:
case ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK:
- return true;
case ConnectivityFlags.REQUEST_RESTRICTED_WIFI:
- return true;
+ case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS:
case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
return true;
default:
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 7121ed4..727db58 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -113,6 +113,7 @@
private static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
private static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
+ private static final NetworkCapabilities BT_CAPABILITIES = new NetworkCapabilities();
static {
CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
@@ -128,6 +129,9 @@
VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
VPN_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+
+ BT_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
+ BT_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
}
/**
@@ -159,7 +163,9 @@
@Mock NetworkAgentInfo mWifiNai;
@Mock NetworkAgentInfo mCellNai;
@Mock NetworkAgentInfo mVpnNai;
+ @Mock NetworkAgentInfo mBluetoothNai;
@Mock NetworkInfo mNetworkInfo;
+ @Mock NetworkInfo mEmptyNetworkInfo;
ArgumentCaptor<Notification> mCaptor;
NetworkNotificationManager mManager;
@@ -174,6 +180,8 @@
mCellNai.networkInfo = mNetworkInfo;
mVpnNai.networkCapabilities = VPN_CAPABILITIES;
mVpnNai.networkInfo = mNetworkInfo;
+ mBluetoothNai.networkCapabilities = BT_CAPABILITIES;
+ mBluetoothNai.networkInfo = mEmptyNetworkInfo;
mDisplayMetrics.density = 2.275f;
doReturn(true).when(mVpnNai).isVPN();
doReturn(mResources).when(mCtx).getResources();
@@ -542,10 +550,11 @@
R.string.wifi_no_internet_detailed);
}
- private void runTelephonySignInNotificationTest(String testTitle, String testContents) {
+ private void runSignInNotificationTest(NetworkAgentInfo nai, String testTitle,
+ String testContents) {
final int id = 101;
final String tag = NetworkNotificationManager.tagFor(id);
- mManager.showNotification(id, SIGN_IN, mCellNai, null, null, false);
+ mManager.showNotification(id, SIGN_IN, nai, null, null, false);
final ArgumentCaptor<Notification> noteCaptor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(eq(tag), eq(SIGN_IN.eventId), noteCaptor.capture());
@@ -565,7 +574,7 @@
doReturn(testContents).when(mResources).getString(
R.string.mobile_network_available_no_internet_detailed, TEST_OPERATOR_NAME);
- runTelephonySignInNotificationTest(testTitle, testContents);
+ runSignInNotificationTest(mCellNai, testTitle, testContents);
}
@Test
@@ -579,6 +588,21 @@
doReturn(testContents).when(mResources).getString(
R.string.mobile_network_available_no_internet_detailed_unknown_carrier);
- runTelephonySignInNotificationTest(testTitle, testContents);
+ runSignInNotificationTest(mCellNai, testTitle, testContents);
+ }
+
+ @Test
+ public void testBluetoothSignInNotification_EmptyNotificationContents() {
+ final String testTitle = "Test title";
+ final String testContents = "Test contents";
+ doReturn(testTitle).when(mResources).getString(
+ R.string.network_available_sign_in, 0);
+ doReturn(testContents).when(mResources).getString(
+ eq(R.string.network_available_sign_in_detailed), any());
+
+ runSignInNotificationTest(mBluetoothNai, testTitle, testContents);
+ // The details should be queried with an empty string argument. In practice the notification
+ // contents may just be an empty string, since the default translation just outputs the arg.
+ verify(mResources).getString(eq(R.string.network_available_sign_in_detailed), eq(""));
}
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
index 0590fbb..3ad8de8 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
@@ -387,6 +387,10 @@
BLOCKED_REASON_NETWORK_RESTRICTED or BLOCKED_REASON_APP_BACKGROUND
)
}
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // ConnectivityService might haven't finished checking blocked status for all requests.
+ waitForIdle()
// Disable background firewall chain
doReturn(BLOCKED_REASON_NONE)
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
similarity index 69%
rename from tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
rename to tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
index 9024641..88c2738 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
@@ -24,6 +24,7 @@
import android.net.NativeNetworkType
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
@@ -41,10 +42,13 @@
import android.util.ArraySet
import com.android.net.module.util.CollectionUtils
import com.android.server.ConnectivityService.PREFERENCE_ORDER_SATELLITE_FALLBACK
+import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.TestableNetworkCallback
import com.android.testutils.visibleOnHandlerThread
import org.junit.Assert
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
@@ -61,7 +65,10 @@
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
-class CSSatelliteNetworkPreferredTest : CSTest() {
+class CSSatelliteNetworkTest : CSTest() {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
/**
* Test createMultiLayerNrisFromSatelliteNetworkPreferredUids returns correct
* NetworkRequestInfo.
@@ -80,54 +87,81 @@
}
/**
- * Test that SATELLITE_NETWORK_PREFERENCE_UIDS changes will send correct net id and uid ranges
- * to netd.
+ * Test that satellite network satisfies satellite fallback per-app default network request and
+ * send correct net id and uid ranges to netd.
*/
- @Test
- fun testSatelliteNetworkPreferredUidsChanged() {
+ private fun doTestSatelliteNetworkFallbackUids(restricted: Boolean) {
val netdInOrder = inOrder(netd)
- val satelliteAgent = createSatelliteAgent("satellite0")
+ val satelliteAgent = createSatelliteAgent("satellite0", restricted)
satelliteAgent.connect()
val satelliteNetId = satelliteAgent.network.netId
+ val permission = if (restricted) {INetd.PERMISSION_SYSTEM} else {INetd.PERMISSION_NONE}
netdInOrder.verify(netd).networkCreate(
- nativeNetworkConfigPhysical(satelliteNetId, INetd.PERMISSION_NONE))
+ nativeNetworkConfigPhysical(satelliteNetId, permission))
val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
- // Initial satellite network preferred uids status.
- setAndUpdateSatelliteNetworkPreferredUids(setOf())
+ // Initial satellite network fallback uids status.
+ updateSatelliteNetworkFallbackUids(setOf())
netdInOrder.verify(netd, never()).networkAddUidRangesParcel(any())
netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
- // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting and verify that net id and uid ranges
- // send to netd
+ // Update satellite network fallback uids and verify that net id and uid ranges send to netd
var uids = mutableSetOf(uid1, uid2, uid3)
val uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids))
val config1 = NativeUidRangeConfig(
satelliteNetId, uidRanges1,
PREFERENCE_ORDER_SATELLITE_FALLBACK
)
- setAndUpdateSatelliteNetworkPreferredUids(uids)
+ updateSatelliteNetworkFallbackUids(uids)
netdInOrder.verify(netd).networkAddUidRangesParcel(config1)
netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
- // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting again and verify that old rules are removed
- // and new rules are added.
+ // Update satellite network fallback uids and verify that net id and uid ranges send to netd
uids = mutableSetOf(uid1)
val uidRanges2: Array<UidRangeParcel?> = toUidRangeStableParcels(uidRangesForUids(uids))
val config2 = NativeUidRangeConfig(
satelliteNetId, uidRanges2,
PREFERENCE_ORDER_SATELLITE_FALLBACK
)
- setAndUpdateSatelliteNetworkPreferredUids(uids)
+ updateSatelliteNetworkFallbackUids(uids)
netdInOrder.verify(netd).networkRemoveUidRangesParcel(config1)
netdInOrder.verify(netd).networkAddUidRangesParcel(config2)
}
+ @Test
+ fun testSatelliteNetworkFallbackUids_restricted() {
+ doTestSatelliteNetworkFallbackUids(restricted = true)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSatelliteNetworkFallbackUids_nonRestricted() {
+ doTestSatelliteNetworkFallbackUids(restricted = false)
+ }
+
+ private fun doTestSatelliteNeverBecomeDefaultNetwork(restricted: Boolean) {
+ val agent = createSatelliteAgent("satellite0", restricted)
+ agent.connect()
+ val defaultCb = TestableNetworkCallback()
+ cm.registerDefaultNetworkCallback(defaultCb)
+ // Satellite network must not become the default network
+ defaultCb.assertNoCallback()
+ }
+
+ @Test
+ fun testSatelliteNeverBecomeDefaultNetwork_restricted() {
+ doTestSatelliteNeverBecomeDefaultNetwork(restricted = true)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSatelliteNeverBecomeDefaultNetwork_notRestricted() {
+ doTestSatelliteNeverBecomeDefaultNetwork(restricted = false)
+ }
+
private fun assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(uids: Set<Int>) {
val nris: Set<ConnectivityService.NetworkRequestInfo> =
service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(uids)
@@ -140,7 +174,7 @@
assertEquals(PREFERENCE_ORDER_SATELLITE_FALLBACK, nri.mPreferenceOrder)
}
- private fun setAndUpdateSatelliteNetworkPreferredUids(uids: Set<Int>) {
+ private fun updateSatelliteNetworkFallbackUids(uids: Set<Int>) {
visibleOnHandlerThread(csHandler) {
deps.satelliteNetworkFallbackUidUpdate!!.accept(uids)
}
@@ -150,9 +184,9 @@
NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission,
false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
- private fun createSatelliteAgent(name: String): CSAgentWrapper {
+ private fun createSatelliteAgent(name: String, restricted: Boolean = true): CSAgentWrapper {
return Agent(score = keepScore(), lp = lp(name),
- nc = nc(TRANSPORT_SATELLITE, NET_CAPABILITY_INTERNET)
+ nc = satelliteNc(restricted)
)
}
@@ -176,17 +210,19 @@
return uidRangesForUids(*CollectionUtils.toIntArray(uids))
}
- private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
- addTransportType(transport)
- caps.forEach {
- addCapability(it)
- }
- // Useful capabilities for everybody
- addCapability(NET_CAPABILITY_NOT_RESTRICTED)
- addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- addCapability(NET_CAPABILITY_NOT_ROAMING)
- addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- }.build()
+ private fun satelliteNc(restricted: Boolean) =
+ NetworkCapabilities.Builder().apply {
+ addTransportType(TRANSPORT_SATELLITE)
+
+ addCapability(NET_CAPABILITY_INTERNET)
+ addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ if (restricted) {
+ removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ }
+ removeCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ }.build()
private fun lp(iface: String) = LinkProperties().apply {
interfaceName = iface
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 47a6763..ed72fd2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -164,6 +164,7 @@
it[ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING] = true
it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true
it[ConnectivityFlags.DELAY_DESTROY_SOCKETS] = true
+ it[ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS] = true
}
fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 9026481..7e0a225 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -56,6 +56,7 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE;
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;
@@ -64,6 +65,7 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.PollEvent.pollReasonNameOf;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
@@ -101,6 +103,7 @@
import android.app.AlarmManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.DataUsageRequest;
@@ -131,6 +134,7 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.SimpleClock;
+import android.os.UserHandle;
import android.provider.Settings;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
@@ -255,6 +259,7 @@
private static @Mock WifiInfo sWifiInfo;
private @Mock INetd mNetd;
private @Mock TetheringManager mTetheringManager;
+ private @Mock PackageManager mPm;
private @Mock NetworkStatsFactory mStatsFactory;
@NonNull
private final TestNetworkStatsSettings mSettings =
@@ -306,6 +311,7 @@
private HandlerThread mObserverHandlerThread;
final TestDependencies mDeps = new TestDependencies();
final HashMap<String, Boolean> mFeatureFlags = new HashMap<>();
+ final HashMap<Long, Boolean> mCompatChanges = new HashMap<>();
// This will set feature flags from @FeatureFlag annotations
// into the map before setUp() runs.
@@ -325,6 +331,16 @@
}
@Override
+ public PackageManager getPackageManager() {
+ return mPm;
+ }
+
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return this;
+ }
+
+ @Override
public Object getSystemService(String name) {
if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
if (Context.TETHERING_SERVICE.equals(name)) return mTetheringManager;
@@ -445,6 +461,9 @@
any(), tetheringEventCbCaptor.capture());
mTetheringEventCallback = tetheringEventCbCaptor.getValue();
+ doReturn(Process.myUid()).when(mPm)
+ .getPackageUid(eq(mServiceContext.getPackageName()), anyInt());
+
mUsageCallback = new TestableUsageCallback(mUsageCallbackBinder);
}
@@ -594,7 +613,7 @@
}
@Override
- public boolean supportTrafficStatsRateLimitCache(Context ctx) {
+ public boolean alwaysUseTrafficStatsRateLimitCache(Context ctx) {
return mFeatureFlags.getOrDefault(TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, false);
}
@@ -608,6 +627,14 @@
return DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES;
}
+ @Override
+ public boolean isChangeEnabled(long changeId, int uid) {
+ return mCompatChanges.getOrDefault(changeId, true);
+ }
+
+ public void setChangeEnabled(long changeId, boolean enabled) {
+ mCompatChanges.put(changeId, enabled);
+ }
@Nullable
@Override
public NetworkStats.Entry nativeGetTotalStat() {
@@ -1704,7 +1731,7 @@
// Register and verify request and that binder was called
DataUsageRequest request = mService.registerUsageCallback(
- mServiceContext.getOpPackageName(), inputRequest, mUsageCallback);
+ mServiceContext.getPackageName(), inputRequest, mUsageCallback);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
@@ -2413,17 +2440,33 @@
@FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
@Test
- public void testTrafficStatsRateLimitCache_disabled() throws Exception {
- doTestTrafficStatsRateLimitCache(false /* cacheEnabled */);
+ public void testTrafficStatsRateLimitCache_disabledWithCompatChangeEnabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
}
@FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG)
@Test
- public void testTrafficStatsRateLimitCache_enabled() throws Exception {
- doTestTrafficStatsRateLimitCache(true /* cacheEnabled */);
+ public void testTrafficStatsRateLimitCache_enabledWithCompatChangeEnabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
}
- private void doTestTrafficStatsRateLimitCache(boolean cacheEnabled) throws Exception {
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ public void testTrafficStatsRateLimitCache_disabledWithCompatChangeDisabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
+ doTestTrafficStatsRateLimitCache(false /* expectCached */);
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ public void testTrafficStatsRateLimitCache_enabledWithCompatChangeDisabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
+ }
+
+ private void doTestTrafficStatsRateLimitCache(boolean expectCached) throws Exception {
mockDefaultSettings();
// Calling uid is not injected into the service, use the real uid to pass the caller check.
final int myUid = Process.myUid();
@@ -2433,7 +2476,7 @@
// Verify the values are cached.
incrementCurrentTime(DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS / 2);
mockTrafficStatsValues(65L, 8L, 1055L, 9L);
- if (cacheEnabled) {
+ if (expectCached) {
assertTrafficStatsValues(TEST_IFACE, myUid, 64L, 3L, 1024L, 8L);
} else {
assertTrafficStatsValues(TEST_IFACE, myUid, 65L, 8L, 1055L, 9L);
@@ -2979,6 +3022,38 @@
}
@Test
+ public void testEnforcePackageNameMatchesUid() throws Exception {
+ final String testMyPackageName = "test.package.myname";
+ final String testRedPackageName = "test.package.red";
+ final String testInvalidPackageName = "test.package.notfound";
+
+ doReturn(UID_RED).when(mPm).getPackageUid(eq(testRedPackageName), anyInt());
+ doReturn(Process.myUid()).when(mPm).getPackageUid(eq(testMyPackageName), anyInt());
+ doThrow(new PackageManager.NameNotFoundException()).when(mPm)
+ .getPackageUid(eq(testInvalidPackageName), anyInt());
+
+ assertThrows(SecurityException.class, () ->
+ mService.openSessionForUsageStats(0 /* flags */, testRedPackageName));
+ assertThrows(SecurityException.class, () ->
+ mService.openSessionForUsageStats(0 /* flags */, testInvalidPackageName));
+ assertThrows(NullPointerException.class, () ->
+ mService.openSessionForUsageStats(0 /* flags */, null));
+ // Verify package name belongs to ourselves does not throw.
+ mService.openSessionForUsageStats(0 /* flags */, testMyPackageName);
+
+ long thresholdInBytes = 10 * 1024 * 1024; // 10 MB
+ DataUsageRequest request = new DataUsageRequest(
+ 2 /* requestId */, sTemplateImsi1, thresholdInBytes);
+ assertThrows(SecurityException.class, () ->
+ mService.registerUsageCallback(testRedPackageName, request, mUsageCallback));
+ assertThrows(SecurityException.class, () ->
+ mService.registerUsageCallback(testInvalidPackageName, request, mUsageCallback));
+ assertThrows(NullPointerException.class, () ->
+ mService.registerUsageCallback(null, request, mUsageCallback));
+ mService.registerUsageCallback(testMyPackageName, request, mUsageCallback);
+ }
+
+ @Test
public void testDumpSkDestroyListenerLogs() throws ErrnoException {
doAnswer((invocation) -> {
final IndentingPrintWriter ipw = (IndentingPrintWriter) invocation.getArgument(0);
diff --git a/thread/README.md b/thread/README.md
index f50e0cd..41b73ac 100644
--- a/thread/README.md
+++ b/thread/README.md
@@ -1,3 +1,18 @@
# Thread
Bring the [Thread](https://www.threadgroup.org/) networking protocol to Android.
+
+## Try Thread with Cuttlefish
+
+```
+# Get the code and go to the Android source code root directory
+
+source build/envsetup.sh
+lunch aosp_cf_x86_64_phone-trunk_staging-userdebug
+m
+
+launch_cvd
+```
+
+Open `https://localhost:8443/` in your web browser, you can find the Thread
+demoapp (with the Thread logo) in the cuttlefish instance. Open it and have fun with Thread!
diff --git a/thread/service/java/com/android/server/thread/InfraInterfaceController.java b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
index be54cbc..e72c9ee 100644
--- a/thread/service/java/com/android/server/thread/InfraInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
@@ -16,14 +16,30 @@
package com.android.server.thread;
-import android.os.ParcelFileDescriptor;
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_RAW;
+import static android.system.OsConstants.IPV6_CHECKSUM;
+import static android.system.OsConstants.IPV6_MULTICAST_HOPS;
+import static android.system.OsConstants.IPV6_RECVHOPLIMIT;
+import static android.system.OsConstants.IPV6_RECVPKTINFO;
+import static android.system.OsConstants.IPV6_UNICAST_HOPS;
+import android.net.util.SocketUtils;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.FileDescriptor;
import java.io.IOException;
/** Controller for the infrastructure network interface. */
public class InfraInterfaceController {
private static final String TAG = "InfraIfController";
+ private static final int ENABLE = 1;
+ private static final int IPV6_CHECKSUM_OFFSET = 2;
+ private static final int HOP_LIMIT = 255;
+
static {
System.loadLibrary("service-thread-jni");
}
@@ -37,8 +53,21 @@
* @throws IOException when fails to create the socket.
*/
public ParcelFileDescriptor createIcmp6Socket(String infraInterfaceName) throws IOException {
- return ParcelFileDescriptor.adoptFd(nativeCreateIcmp6Socket(infraInterfaceName));
+ ParcelFileDescriptor parcelFd =
+ ParcelFileDescriptor.adoptFd(nativeCreateFilteredIcmp6Socket());
+ FileDescriptor fd = parcelFd.getFileDescriptor();
+ try {
+ Os.setsockoptInt(fd, IPPROTO_RAW, IPV6_CHECKSUM, IPV6_CHECKSUM_OFFSET);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, ENABLE);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, ENABLE);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, HOP_LIMIT);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, HOP_LIMIT);
+ SocketUtils.bindSocketToInterface(fd, infraInterfaceName);
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to setsockopt for the ICMPv6 socket", e);
+ }
+ return parcelFd;
}
- private static native int nativeCreateIcmp6Socket(String interfaceName) throws IOException;
+ private static native int nativeCreateFilteredIcmp6Socket() throws IOException;
}
diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java
index 2c14f1d..1447ff8 100644
--- a/thread/service/java/com/android/server/thread/NsdPublisher.java
+++ b/thread/service/java/com/android/server/thread/NsdPublisher.java
@@ -19,11 +19,15 @@
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.net.DnsResolver;
import android.net.InetAddresses;
+import android.net.Network;
import android.net.nsd.DiscoveryRequest;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.RemoteException;
import android.text.TextUtils;
@@ -34,13 +38,14 @@
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
import com.android.server.thread.openthread.INsdPublisher;
+import com.android.server.thread.openthread.INsdResolveHostCallback;
import com.android.server.thread.openthread.INsdResolveServiceCallback;
import com.android.server.thread.openthread.INsdStatusReceiver;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -56,24 +61,36 @@
* {@code mHandler} itself.
*/
public final class NsdPublisher extends INsdPublisher.Stub {
- // TODO: b/321883491 - specify network for mDNS operations
private static final String TAG = NsdPublisher.class.getSimpleName();
+
+ // TODO: b/321883491 - specify network for mDNS operations
+ @Nullable private Network mNetwork;
private final NsdManager mNsdManager;
+ private final DnsResolver mDnsResolver;
private final Handler mHandler;
private final Executor mExecutor;
private final SparseArray<RegistrationListener> mRegistrationListeners = new SparseArray<>(0);
private final SparseArray<DiscoveryListener> mDiscoveryListeners = new SparseArray<>(0);
private final SparseArray<ServiceInfoListener> mServiceInfoListeners = new SparseArray<>(0);
+ private final SparseArray<HostInfoListener> mHostInfoListeners = new SparseArray<>(0);
@VisibleForTesting
- public NsdPublisher(NsdManager nsdManager, Handler handler) {
+ public NsdPublisher(NsdManager nsdManager, DnsResolver dnsResolver, Handler handler) {
+ mNetwork = null;
mNsdManager = nsdManager;
+ mDnsResolver = dnsResolver;
mHandler = handler;
mExecutor = runnable -> mHandler.post(runnable);
}
public static NsdPublisher newInstance(Context context, Handler handler) {
- return new NsdPublisher(context.getSystemService(NsdManager.class), handler);
+ return new NsdPublisher(
+ context.getSystemService(NsdManager.class), DnsResolver.getInstance(), handler);
+ }
+
+ // TODO: b/321883491 - NsdPublisher should be disabled when mNetwork is null
+ public void setNetworkForHostResolution(@Nullable Network network) {
+ mNetwork = network;
}
@Override
@@ -291,6 +308,53 @@
}
}
+ @Override
+ public void resolveHost(String name, INsdResolveHostCallback callback, int listenerId) {
+ mHandler.post(() -> resolveHostInternal(name, callback, listenerId));
+ }
+
+ private void resolveHostInternal(
+ String name, INsdResolveHostCallback callback, int listenerId) {
+ checkOnHandlerThread();
+
+ String fullHostname = name + ".local";
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ HostInfoListener listener =
+ new HostInfoListener(name, callback, cancellationSignal, listenerId);
+ mDnsResolver.query(
+ mNetwork,
+ fullHostname,
+ DnsResolver.FLAG_NO_CACHE_LOOKUP,
+ mExecutor,
+ cancellationSignal,
+ listener);
+ mHostInfoListeners.append(listenerId, listener);
+
+ Log.i(TAG, "Resolving host." + " Listener ID: " + listenerId + ", hostname: " + name);
+ }
+
+ @Override
+ public void stopHostResolution(int listenerId) {
+ mHandler.post(() -> stopHostResolutionInternal(listenerId));
+ }
+
+ private void stopHostResolutionInternal(int listenerId) {
+ checkOnHandlerThread();
+
+ HostInfoListener listener = mHostInfoListeners.get(listenerId);
+ if (listener == null) {
+ Log.w(
+ TAG,
+ "Failed to stop host resolution. Listener ID: "
+ + listenerId
+ + ". The listener is null.");
+ return;
+ }
+ Log.i(TAG, "Stopping host resolution. Listener: " + listener);
+ listener.cancel();
+ mHostInfoListeners.remove(listenerId);
+ }
+
private void checkOnHandlerThread() {
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException(
@@ -550,9 +614,8 @@
}
List<DnsTxtAttribute> txtList = new ArrayList<>();
for (Map.Entry<String, byte[]> entry : serviceInfo.getAttributes().entrySet()) {
- DnsTxtAttribute attribute = new DnsTxtAttribute();
- attribute.name = entry.getKey();
- attribute.value = Arrays.copyOf(entry.getValue(), entry.getValue().length);
+ DnsTxtAttribute attribute =
+ new DnsTxtAttribute(entry.getKey(), entry.getValue().clone());
txtList.add(attribute);
}
// TODO: b/329018320 - Use the serviceInfo.getExpirationTime to derive TTL.
@@ -586,4 +649,78 @@
return "ID: " + mListenerId + ", service name: " + mName + ", service type: " + mType;
}
}
+
+ class HostInfoListener implements DnsResolver.Callback<List<InetAddress>> {
+ private final String mHostname;
+ private final INsdResolveHostCallback mResolveHostCallback;
+ private final CancellationSignal mCancellationSignal;
+ private final int mListenerId;
+
+ HostInfoListener(
+ @NonNull String hostname,
+ INsdResolveHostCallback resolveHostCallback,
+ CancellationSignal cancellationSignal,
+ int listenerId) {
+ this.mHostname = hostname;
+ this.mResolveHostCallback = resolveHostCallback;
+ this.mCancellationSignal = cancellationSignal;
+ this.mListenerId = listenerId;
+ }
+
+ @Override
+ public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
+ checkOnHandlerThread();
+
+ Log.i(
+ TAG,
+ "Host is resolved."
+ + " Listener ID: "
+ + mListenerId
+ + ", hostname: "
+ + mHostname
+ + ", addresses: "
+ + answerList
+ + ", return code: "
+ + rcode);
+ List<String> addressStrings = new ArrayList<>();
+ for (InetAddress address : answerList) {
+ addressStrings.add(address.getHostAddress());
+ }
+ try {
+ mResolveHostCallback.onHostResolved(mHostname, addressStrings);
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ mHostInfoListeners.remove(mListenerId);
+ }
+
+ @Override
+ public void onError(@NonNull DnsResolver.DnsException error) {
+ checkOnHandlerThread();
+
+ Log.i(
+ TAG,
+ "Failed to resolve host."
+ + " Listener ID: "
+ + mListenerId
+ + ", hostname: "
+ + mHostname,
+ error);
+ try {
+ mResolveHostCallback.onHostResolved(mHostname, Collections.emptyList());
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ mHostInfoListeners.remove(mListenerId);
+ }
+
+ public String toString() {
+ return "ID: " + mListenerId + ", hostname: " + mHostname;
+ }
+
+ void cancel() {
+ mCancellationSignal.cancel();
+ mHostInfoListeners.remove(mListenerId);
+ }
+ }
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index af9abdf..0c77dee 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -61,6 +61,8 @@
import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -117,6 +119,7 @@
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.BackboneRouterState;
import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
+import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.IChannelMasksReceiver;
import com.android.server.thread.openthread.IOtDaemon;
import com.android.server.thread.openthread.IOtDaemonCallback;
@@ -130,7 +133,6 @@
import java.io.IOException;
import java.net.Inet6Address;
-import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Clock;
import java.time.DateTimeException;
@@ -338,9 +340,11 @@
final String modelName = resources.getString(R.string.config_thread_model_name);
final String vendorName = resources.getString(R.string.config_thread_vendor_name);
final String vendorOui = resources.getString(R.string.config_thread_vendor_oui);
+ final boolean managedByGoogle =
+ resources.getBoolean(R.bool.config_thread_managed_by_google_home);
if (!modelName.isEmpty()) {
- if (modelName.getBytes(StandardCharsets.UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
+ if (modelName.getBytes(UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
throw new IllegalStateException(
"Model name is longer than "
+ MAX_MODEL_NAME_UTF8_BYTES
@@ -350,7 +354,7 @@
}
if (!vendorName.isEmpty()) {
- if (vendorName.getBytes(StandardCharsets.UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
+ if (vendorName.getBytes(UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
throw new IllegalStateException(
"Vendor name is longer than "
+ MAX_VENDOR_NAME_UTF8_BYTES
@@ -367,9 +371,21 @@
meshcopTxts.modelName = modelName;
meshcopTxts.vendorName = vendorName;
meshcopTxts.vendorOui = HexEncoding.decode(vendorOui.replace("-", "").replace(":", ""));
+ meshcopTxts.nonStandardTxtEntries = List.of(makeManagedByGoogleTxtAttr(managedByGoogle));
+
return meshcopTxts;
}
+ /**
+ * Creates a DNS-SD TXT entry for indicating whether Thread on this device is managed by Google.
+ *
+ * @return TXT entry "vgh=1" if {@code managedByGoogle} is {@code true}; otherwise, "vgh=0"
+ */
+ private static DnsTxtAttribute makeManagedByGoogleTxtAttr(boolean managedByGoogle) {
+ final byte[] value = (managedByGoogle ? "1" : "0").getBytes(UTF_8);
+ return new DnsTxtAttribute("vgh", value);
+ }
+
private void onOtDaemonDied() {
checkOnHandlerThread();
Log.w(TAG, "OT daemon is dead, clean up...");
@@ -717,6 +733,7 @@
if (mNetworkToInterface.containsKey(mUpstreamNetwork)) {
enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
}
+ mNsdPublisher.setNetworkForHostResolution(mUpstreamNetwork);
}
}
}
diff --git a/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
index 5d24eab..1f260f2 100644
--- a/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
+++ b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
@@ -42,15 +42,8 @@
namespace android {
static jint
-com_android_server_thread_InfraInterfaceController_createIcmp6Socket(JNIEnv *env, jobject clazz,
- jstring interfaceName) {
- ScopedUtfChars ifName(env, interfaceName);
-
- struct icmp6_filter filter;
- constexpr int kEnable = 1;
- constexpr int kIpv6ChecksumOffset = 2;
- constexpr int kHopLimit = 255;
-
+com_android_server_thread_InfraInterfaceController_createFilteredIcmp6Socket(JNIEnv *env,
+ jobject clazz) {
// Initializes the ICMPv6 socket.
int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (sock == -1) {
@@ -59,6 +52,7 @@
return -1;
}
+ struct icmp6_filter filter;
// Only accept Router Advertisements, Router Solicitations and Neighbor
// Advertisements.
ICMP6_FILTER_SETBLOCKALL(&filter);
@@ -73,53 +67,6 @@
return -1;
}
- // We want a source address and interface index.
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &kEnable, sizeof(kEnable)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVPKTINFO (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &kIpv6ChecksumOffset,
- sizeof(kIpv6ChecksumOffset)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_CHECKSUM (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- // We need to be able to reject RAs arriving from off-link.
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &kEnable, sizeof(kEnable)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVHOPLIMIT (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_UNICAST_HOPS (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException",
- "failed to create the setsockopt IPV6_MULTICAST_HOPS (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifName.c_str(), strlen(ifName.c_str()))) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt SO_BINDTODEVICE (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
return sock;
}
@@ -129,8 +76,8 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- {"nativeCreateIcmp6Socket", "(Ljava/lang/String;)I",
- (void *)com_android_server_thread_InfraInterfaceController_createIcmp6Socket},
+ {"nativeCreateFilteredIcmp6Socket", "()I",
+ (void *)com_android_server_thread_InfraInterfaceController_createFilteredIcmp6Socket},
};
int register_com_android_server_thread_InfraInterfaceController(JNIEnv *env) {
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 8cdf38d..c1cf0a0 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -21,6 +21,7 @@
android_test {
name: "CtsThreadNetworkTestCases",
+ defaults: ["cts_defaults"],
min_sdk_version: "33",
sdk_version: "test_current",
manifest: "AndroidManifest.xml",
diff --git a/thread/tests/cts/AndroidTest.xml b/thread/tests/cts/AndroidTest.xml
index ffc181c..6eda1e9 100644
--- a/thread/tests/cts/AndroidTest.xml
+++ b/thread/tests/cts/AndroidTest.xml
@@ -38,6 +38,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<!-- Install test -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="CtsThreadNetworkTestCases.apk" />
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 11c4819..0e95703 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -45,7 +45,6 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
-
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
@@ -859,6 +858,7 @@
assertThat(txtMap.get("rv")).isNotNull();
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
+ assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
@@ -885,6 +885,7 @@
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
assertThat(txtMap.get("id").length).isEqualTo(16);
+ assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
diff --git a/thread/tests/integration/AndroidTest.xml b/thread/tests/integration/AndroidTest.xml
index 152c1c3..8f98941 100644
--- a/thread/tests/integration/AndroidTest.xml
+++ b/thread/tests/integration/AndroidTest.xml
@@ -31,6 +31,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<!-- Install test -->
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index 46cf562..c0a8eea 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -20,10 +20,14 @@
import static com.google.common.io.BaseEncoding.base16;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.nsd.NsdServiceInfo;
import android.net.thread.ActiveOperationalDataset;
+import android.os.Handler;
+import android.os.HandlerThread;
import com.google.errorprone.annotations.FormatMethod;
@@ -39,6 +43,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -60,10 +66,13 @@
private static final float PING_TIMEOUT_0_1_SECOND = 0.1f;
// 1 second timeout should be used when response is expected.
private static final float PING_TIMEOUT_1_SECOND = 1f;
+ private static final int READ_LINE_TIMEOUT_SECONDS = 5;
private final Process mProcess;
private final BufferedReader mReader;
private final BufferedWriter mWriter;
+ private final HandlerThread mReaderHandlerThread;
+ private final Handler mReaderHandler;
private ActiveOperationalDataset mActiveOperationalDataset;
@@ -87,11 +96,15 @@
}
mReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
mWriter = new BufferedWriter(new OutputStreamWriter(mProcess.getOutputStream()));
+ mReaderHandlerThread = new HandlerThread("FullThreadDeviceReader");
+ mReaderHandlerThread.start();
+ mReaderHandler = new Handler(mReaderHandlerThread.getLooper());
mActiveOperationalDataset = null;
}
public void destroy() {
mProcess.destroy();
+ mReaderHandlerThread.quit();
}
/**
@@ -213,7 +226,7 @@
public String udpReceive() throws IOException {
Pattern pattern =
Pattern.compile("> (\\d+) bytes from ([\\da-f:]+) (\\d+) ([\\x00-\\x7F]+)");
- Matcher matcher = pattern.matcher(mReader.readLine());
+ Matcher matcher = pattern.matcher(readLine());
matcher.matches();
return matcher.group(4);
@@ -500,10 +513,27 @@
}
}
+ private String readLine() throws IOException {
+ final CompletableFuture<String> future = new CompletableFuture<>();
+ mReaderHandler.post(
+ () -> {
+ try {
+ future.complete(mReader.readLine());
+ } catch (IOException e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ return future.get(READ_LINE_TIMEOUT_SECONDS, SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ throw new IOException("Failed to read a line from ot-cli-ftd");
+ }
+ }
+
private List<String> readUntilDone() throws IOException {
ArrayList<String> result = new ArrayList<>();
String line;
- while ((line = mReader.readLine()) != null) {
+ while ((line = readLine()) != null) {
if (line.equals("Done")) {
break;
}
diff --git a/thread/tests/unit/AndroidTest.xml b/thread/tests/unit/AndroidTest.xml
index d16e423..58e9bdd 100644
--- a/thread/tests/unit/AndroidTest.xml
+++ b/thread/tests/unit/AndroidTest.xml
@@ -31,6 +31,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="ThreadNetworkUnitTests.apk" />
<option name="check-min-sdk" value="true" />
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
index 8886c73..b32986d 100644
--- a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -16,56 +16,73 @@
package com.android.server.thread;
+import static android.net.DnsResolver.ERROR_SYSTEM;
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
import static com.google.common.truth.Truth.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+import android.net.DnsResolver;
import android.net.InetAddresses;
+import android.net.Network;
import android.net.nsd.DiscoveryRequest;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.test.TestLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
+import com.android.server.thread.openthread.INsdResolveHostCallback;
import com.android.server.thread.openthread.INsdResolveServiceCallback;
import com.android.server.thread.openthread.INsdStatusReceiver;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
/** Unit tests for {@link NsdPublisher}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class NsdPublisherTest {
+ private static final DnsTxtAttribute TEST_TXT_ENTRY_1 =
+ new DnsTxtAttribute("key1", new byte[] {0x01, 0x02});
+ private static final DnsTxtAttribute TEST_TXT_ENTRY_2 =
+ new DnsTxtAttribute("key2", new byte[] {0x03});
+
@Mock private NsdManager mMockNsdManager;
+ @Mock private DnsResolver mMockDnsResolver;
@Mock private INsdStatusReceiver mRegistrationReceiver;
@Mock private INsdStatusReceiver mUnregistrationReceiver;
@Mock private INsdDiscoverServiceCallback mDiscoverServiceCallback;
@Mock private INsdResolveServiceCallback mResolveServiceCallback;
+ @Mock private INsdResolveHostCallback mResolveHostCallback;
+ @Mock private Network mNetwork;
private TestLooper mTestLooper;
private NsdPublisher mNsdPublisher;
@@ -79,19 +96,15 @@
public void registerService_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
-
mTestLooper.dispatchAll();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@@ -118,11 +131,10 @@
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
- assertThat(actualServiceInfo.getAttributes().get("key1"))
- .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
- assertThat(actualServiceInfo.getAttributes().get("key2"))
- .isEqualTo(new byte[] {(byte) 0x03});
-
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
+ .isEqualTo(TEST_TXT_ENTRY_1.value);
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
+ .isEqualTo(TEST_TXT_ENTRY_2.value);
verify(mRegistrationReceiver, times(1)).onSuccess();
}
@@ -130,19 +142,15 @@
public void registerService_nsdManagerFails_serviceRegistrationFails() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
-
mTestLooper.dispatchAll();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@@ -169,21 +177,16 @@
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
- assertThat(actualServiceInfo.getAttributes().get("key1"))
- .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
- assertThat(actualServiceInfo.getAttributes().get("key2"))
- .isEqualTo(new byte[] {(byte) 0x03});
-
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
+ .isEqualTo(TEST_TXT_ENTRY_1.value);
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
+ .isEqualTo(TEST_TXT_ENTRY_2.value);
verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
}
@Test
public void registerService_nsdManagerThrows_serviceRegistrationFails() throws Exception {
prepareTest();
-
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
doThrow(new IllegalArgumentException("NsdManager fails"))
.when(mMockNsdManager)
.registerService(any(), anyInt(), any(Executor.class), any());
@@ -194,7 +197,7 @@
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
@@ -207,16 +210,13 @@
throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
@@ -252,16 +252,13 @@
public void unregisterService_nsdManagerFails_serviceUnregistrationFails() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
@@ -579,8 +576,8 @@
List.of(
InetAddress.parseNumericAddress("2001::1"),
InetAddress.parseNumericAddress("2001::2")));
- serviceInfo.setAttribute("key1", new byte[] {(byte) 0x01, (byte) 0x02});
- serviceInfo.setAttribute("key2", new byte[] {(byte) 0x03});
+ serviceInfo.setAttribute(TEST_TXT_ENTRY_1.name, TEST_TXT_ENTRY_1.value);
+ serviceInfo.setAttribute(TEST_TXT_ENTRY_2.name, TEST_TXT_ENTRY_2.value);
serviceInfoCallbackArgumentCaptor.getValue().onServiceUpdated(serviceInfo);
mTestLooper.dispatchAll();
@@ -591,11 +588,8 @@
eq("_test._tcp"),
eq(12345),
eq(List.of("2001::1", "2001::2")),
- argThat(
- new TxtMatcher(
- List.of(
- makeTxtAttribute("key1", List.of(0x01, 0x02)),
- makeTxtAttribute("key2", List.of(0x03))))),
+ (List<DnsTxtAttribute>)
+ argThat(containsInAnyOrder(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2)),
anyInt());
}
@@ -637,12 +631,86 @@
}
@Test
- public void reset_unregisterAll() {
+ public void resolveHost_hostResolved() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<DnsResolver.Callback<List<InetAddress>>> resolveHostCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DnsResolver.Callback.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ any(CancellationSignal.class),
+ resolveHostCallbackArgumentCaptor.capture());
+ resolveHostCallbackArgumentCaptor
+ .getValue()
+ .onAnswer(
+ List.of(
+ InetAddresses.parseNumericAddress("2001::1"),
+ InetAddresses.parseNumericAddress("2001::2")),
+ 0);
+ mTestLooper.dispatchAll();
+
+ verify(mResolveHostCallback, times(1))
+ .onHostResolved("test", List.of("2001::1", "2001::2"));
+ }
+
+ @Test
+ public void resolveHost_errorReported() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<DnsResolver.Callback<List<InetAddress>>> resolveHostCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DnsResolver.Callback.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ any(CancellationSignal.class),
+ resolveHostCallbackArgumentCaptor.capture());
+ resolveHostCallbackArgumentCaptor
+ .getValue()
+ .onError(new DnsResolver.DnsException(ERROR_SYSTEM, null /* cause */));
+ mTestLooper.dispatchAll();
+
+ verify(mResolveHostCallback, times(1)).onHostResolved("test", Collections.emptyList());
+ }
+
+ @Test
+ public void stopHostResolution() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<CancellationSignal> cancellationSignalArgumentCaptor =
+ ArgumentCaptor.forClass(CancellationSignal.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ cancellationSignalArgumentCaptor.capture(),
+ any(DnsResolver.Callback.class));
+
+ mNsdPublisher.stopHostResolution(10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ assertThat(cancellationSignalArgumentCaptor.getValue().isCanceled()).isTrue();
+ }
+
+ @Test
+ public void reset_unregisterAll() {
+ prepareTest();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
ArgumentCaptor.forClass(NsdServiceInfo.class);
ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
@@ -654,7 +722,7 @@
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
@@ -728,19 +796,6 @@
verify(spyNsdPublisher, times(1)).reset();
}
- private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
- DnsTxtAttribute txtAttribute = new DnsTxtAttribute();
-
- txtAttribute.name = name;
- txtAttribute.value = new byte[value.size()];
-
- for (int i = 0; i < value.size(); ++i) {
- txtAttribute.value[i] = value.get(i).byteValue();
- }
-
- return txtAttribute;
- }
-
private static List<InetAddress> makeAddresses(String... addressStrings) {
List<InetAddress> addresses = new ArrayList<>();
@@ -750,36 +805,13 @@
return addresses;
}
- private static class TxtMatcher implements ArgumentMatcher<List<DnsTxtAttribute>> {
- private final List<DnsTxtAttribute> mAttributes;
-
- TxtMatcher(List<DnsTxtAttribute> attributes) {
- mAttributes = attributes;
- }
-
- @Override
- public boolean matches(List<DnsTxtAttribute> argument) {
- if (argument.size() != mAttributes.size()) {
- return false;
- }
- for (int i = 0; i < argument.size(); ++i) {
- if (!Objects.equals(argument.get(i).name, mAttributes.get(i).name)) {
- return false;
- }
- if (!Arrays.equals(argument.get(i).value, mAttributes.get(i).value)) {
- return false;
- }
- }
- return true;
- }
- }
-
// @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
// thread looper, so TestLooper needs to be created inside each test case to install the
// correct looper.
private void prepareTest() {
mTestLooper = new TestLooper();
Handler handler = new Handler(mTestLooper.getLooper());
- mNsdPublisher = new NsdPublisher(mMockNsdManager, handler);
+ mNsdPublisher = new NsdPublisher(mMockNsdManager, mMockDnsResolver, handler);
+ mNsdPublisher.setNetworkForHostResolution(mNetwork);
}
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 8f60783..2f58943 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -78,6 +78,7 @@
import com.android.connectivity.resources.R;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.connectivity.ConnectivityResources;
+import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.MeshcopTxtAttributes;
import com.android.server.thread.openthread.testing.FakeOtDaemon;
@@ -94,6 +95,7 @@
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
+import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.DateTimeException;
import java.util.concurrent.CompletableFuture;
@@ -145,6 +147,7 @@
private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
private static final String TEST_VENDOR_NAME = "test vendor";
private static final String TEST_MODEL_NAME = "test model";
+ private static final boolean TEST_VGH_VALUE = false;
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@@ -197,6 +200,8 @@
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
+ when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+ .thenReturn(TEST_VGH_VALUE);
final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
@@ -232,13 +237,15 @@
}
@Test
- public void initialize_vendorAndModelNameInResourcesAreSetToOtDaemon() throws Exception {
+ public void initialize_resourceOverlayValuesAreSetToOtDaemon() throws Exception {
when(mResources.getString(eq(R.string.config_thread_vendor_name)))
.thenReturn(TEST_VENDOR_NAME);
when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
+ when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+ .thenReturn(true);
mService.initialize();
mTestLooper.dispatchAll();
@@ -247,6 +254,20 @@
assertThat(meshcopTxts.vendorName).isEqualTo(TEST_VENDOR_NAME);
assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);
assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
+ assertThat(meshcopTxts.nonStandardTxtEntries)
+ .containsExactly(new DnsTxtAttribute("vgh", "1".getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_managedByGoogleIsFalse_vghIsZero() {
+ when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+ .thenReturn(false);
+
+ MeshcopTxtAttributes meshcopTxts =
+ ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
+
+ assertThat(meshcopTxts.nonStandardTxtEntries)
+ .containsExactly(new DnsTxtAttribute("vgh", "0".getBytes(StandardCharsets.UTF_8)));
}
@Test