Merge "Fix testRequestDownstreamAddressWithoutUsingLastAddress flaky" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index fafd3bb..46308af 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -5,6 +5,9 @@
"options": [
{
"exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
}
]
},
@@ -260,7 +263,12 @@
"name": "netd_updatable_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
- "name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ "name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ]
},
{
"name": "traffic_controller_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 4478b1e..e69b872 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -95,6 +95,7 @@
],
static_libs: [
"NetworkStackApiCurrentShims",
+ "net-utils-device-common-struct",
],
apex_available: ["com.android.tethering"],
lint: { strict_updatability_linting: true },
@@ -109,6 +110,7 @@
],
static_libs: [
"NetworkStackApiStableShims",
+ "net-utils-device-common-struct",
],
apex_available: ["com.android.tethering"],
lint: { strict_updatability_linting: true },
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 9132857..cd8eac8 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -96,6 +96,7 @@
},
binaries: [
"clatd",
+ "ethtool",
"netbpfload",
"ot-daemon",
],
diff --git a/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java
index 328e3fb..dac5b63 100644
--- a/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java
+++ b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java
@@ -16,8 +16,6 @@
package android.net.ip;
-import static android.net.RouteInfo.RTN_UNICAST;
-
import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
@@ -42,12 +40,13 @@
import android.net.INetd;
import android.net.IpPrefix;
import android.net.MacAddress;
-import android.net.RouteInfo;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -55,7 +54,6 @@
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.Ipv6Utils;
-import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
@@ -80,7 +78,6 @@
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.HashSet;
-import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -332,10 +329,12 @@
// Add a default route "fe80::/64 -> ::" to local network, otherwise, device will fail to
// send the unicast RA out due to the ENETUNREACH error(No route to the peer's link-local
// address is present).
- final String iface = mTetheredParams.name;
- final RouteInfo linkLocalRoute =
- new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST);
- NetdUtils.addRoutesToLocalNetwork(sNetd, iface, List.of(linkLocalRoute));
+ try {
+ sNetd.networkAddRoute(INetd.LOCAL_NET_ID, mTetheredParams.name,
+ "fe80::/64", INetd.NEXTHOP_NONE);
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
final ByteBuffer rs = createRsPacket("fe80::1122:3344:5566:7788");
mTetheredPacketReader.sendResponse(rs);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt
new file mode 100644
index 0000000..3a57fdd
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt
@@ -0,0 +1,294 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.networkstack.tethering.util
+
+import android.os.Message
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.util.State
+import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo
+import java.util.ArrayDeque
+import java.util.ArrayList
+import kotlin.test.assertFailsWith
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+private const val MSG_INVALID = -1
+private const val MSG_1 = 1
+private const val MSG_2 = 2
+private const val MSG_3 = 3
+private const val MSG_4 = 4
+private const val MSG_5 = 5
+private const val MSG_6 = 6
+private const val MSG_7 = 7
+private const val ARG_1 = 100
+private const val ARG_2 = 200
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SynStateMachineTest {
+ private val mState1 = spy(object : TestState(MSG_1) {})
+ private val mState2 = spy(object : TestState(MSG_2) {})
+ private val mState3 = spy(object : TestState(MSG_3) {})
+ private val mState4 = spy(object : TestState(MSG_4) {})
+ private val mState5 = spy(object : TestState(MSG_5) {})
+ private val mState6 = spy(object : TestState(MSG_6) {})
+ private val mState7 = spy(object : TestState(MSG_7) {})
+ private val mInOrder = inOrder(mState1, mState2, mState3, mState4, mState5, mState6, mState7)
+ // Lazy initialize to make sure running in test thread.
+ private val mSM by lazy {
+ SyncStateMachine("TestSyncStateMachine", Thread.currentThread(), true /* debug */)
+ }
+ private val mAllStates = ArrayList<StateInfo>()
+
+ private val mMsgProcessedResults = ArrayDeque<Pair<State, Int>>()
+
+ open inner class TestState(val expected: Int) : State() {
+ // Control destination state in obj field for testing.
+ override fun processMessage(msg: Message): Boolean {
+ mMsgProcessedResults.add(this to msg.what)
+ assertEquals(ARG_1, msg.arg1)
+ assertEquals(ARG_2, msg.arg2)
+
+ if (msg.what == expected) {
+ msg.obj?.let { mSM.transitionTo(it as State) }
+ return true
+ }
+
+ return false
+ }
+ }
+
+ private fun verifyNoMoreInteractions() {
+ verifyNoMoreInteractions(mState1, mState2, mState3, mState4, mState5, mState6)
+ }
+
+ private fun processMessage(what: Int, toState: State?) {
+ mSM.processMessage(what, ARG_1, ARG_2, toState)
+ }
+
+ private fun verifyMessageProcessedBy(what: Int, vararg processedStates: State) {
+ for (state in processedStates) {
+ // InOrder.verify can't check the Message content here because SyncSM will recycle the
+ // message after it's been processed. SyncSM reuses the same Message instance for all
+ // messages it processes. So, if using InOrder.verify to verify the content of a message
+ // after SyncSM has processed it, the content would be wrong.
+ mInOrder.verify(state).processMessage(any())
+ val (processedState, msgWhat) = mMsgProcessedResults.remove()
+ assertEquals(state, processedState)
+ assertEquals(what, msgWhat)
+ }
+ assertTrue(mMsgProcessedResults.isEmpty())
+ }
+
+ @Test
+ fun testInitialState() {
+ // mState1 -> initial
+ // |
+ // mState2
+ mAllStates.add(StateInfo(mState1, null))
+ mAllStates.add(StateInfo(mState2, mState1))
+ mSM.addAllStates(mAllStates)
+
+ mSM.start(mState1)
+ mInOrder.verify(mState1).enter()
+ verifyNoMoreInteractions()
+ }
+
+ @Test
+ fun testStartFromLeafState() {
+ // mState1 -> initial
+ // |
+ // mState2
+ // |
+ // mState3
+ mAllStates.add(StateInfo(mState1, null))
+ mAllStates.add(StateInfo(mState2, mState1))
+ mAllStates.add(StateInfo(mState3, mState2))
+ mSM.addAllStates(mAllStates)
+
+ mSM.start(mState3)
+ mInOrder.verify(mState1).enter()
+ mInOrder.verify(mState2).enter()
+ mInOrder.verify(mState3).enter()
+ verifyNoMoreInteractions()
+ }
+
+ private fun verifyStart() {
+ mSM.addAllStates(mAllStates)
+ mSM.start(mState1)
+ mInOrder.verify(mState1).enter()
+ verifyNoMoreInteractions()
+ }
+
+ fun addState(state: State, parent: State? = null) {
+ mAllStates.add(StateInfo(state, parent))
+ }
+
+ @Test
+ fun testAddState() {
+ // Add duplicated states.
+ mAllStates.add(StateInfo(mState1, null))
+ mAllStates.add(StateInfo(mState1, null))
+ assertFailsWith(IllegalStateException::class) {
+ mSM.addAllStates(mAllStates)
+ }
+ }
+
+ @Test
+ fun testProcessMessage() {
+ // mState1
+ // |
+ // mState2
+ addState(mState1)
+ addState(mState2, mState1)
+ verifyStart()
+
+ processMessage(MSG_1, null)
+ verifyMessageProcessedBy(MSG_1, mState1)
+ verifyNoMoreInteractions()
+ }
+
+ @Test
+ fun testTwoStates() {
+ // mState1 <-initial, mState2
+ addState(mState1)
+ addState(mState2)
+ verifyStart()
+
+ // Test transition to mState2
+ processMessage(MSG_1, mState2)
+ verifyMessageProcessedBy(MSG_1, mState1)
+ mInOrder.verify(mState1).exit()
+ mInOrder.verify(mState2).enter()
+ verifyNoMoreInteractions()
+
+ // If set destState to mState2 (current state), no state transition.
+ processMessage(MSG_2, mState2)
+ verifyMessageProcessedBy(MSG_2, mState2)
+ verifyNoMoreInteractions()
+ }
+
+ @Test
+ fun testTwoStateTrees() {
+ // mState1 -> initial mState4
+ // / \ / \
+ // mState2 mState3 mState5 mState6
+ addState(mState1)
+ addState(mState2, mState1)
+ addState(mState3, mState1)
+ addState(mState4)
+ addState(mState5, mState4)
+ addState(mState6, mState4)
+ verifyStart()
+
+ // mState1 -> current mState4
+ // / \ / \
+ // mState2 mState3 -> dest mState5 mState6
+ processMessage(MSG_1, mState3)
+ verifyMessageProcessedBy(MSG_1, mState1)
+ mInOrder.verify(mState3).enter()
+ verifyNoMoreInteractions()
+
+ // mState1 mState4
+ // / \ / \
+ // dest <- mState2 mState3 -> current mState5 mState6
+ processMessage(MSG_1, mState2)
+ verifyMessageProcessedBy(MSG_1, mState3, mState1)
+ mInOrder.verify(mState3).exit()
+ mInOrder.verify(mState2).enter()
+ verifyNoMoreInteractions()
+
+ // mState1 mState4
+ // / \ / \
+ // current <- mState2 mState3 mState5 mState6 -> dest
+ processMessage(MSG_2, mState6)
+ verifyMessageProcessedBy(MSG_2, mState2)
+ mInOrder.verify(mState2).exit()
+ mInOrder.verify(mState1).exit()
+ mInOrder.verify(mState4).enter()
+ mInOrder.verify(mState6).enter()
+ verifyNoMoreInteractions()
+ }
+
+ @Test
+ fun testMultiDepthTransition() {
+ // mState1 -> current
+ // | \
+ // mState2 mState6
+ // | \ |
+ // mState3 mState5 mState7
+ // |
+ // mState4
+ addState(mState1)
+ addState(mState2, mState1)
+ addState(mState6, mState1)
+ addState(mState3, mState2)
+ addState(mState5, mState2)
+ addState(mState7, mState6)
+ addState(mState4, mState3)
+ verifyStart()
+
+ // mState1 -> current
+ // | \
+ // mState2 mState6
+ // | \ |
+ // mState3 mState5 mState7
+ // |
+ // mState4 -> dest
+ processMessage(MSG_1, mState4)
+ verifyMessageProcessedBy(MSG_1, mState1)
+ mInOrder.verify(mState2).enter()
+ mInOrder.verify(mState3).enter()
+ mInOrder.verify(mState4).enter()
+ verifyNoMoreInteractions()
+
+ // mState1
+ // / \
+ // mState2 mState6
+ // | \ \
+ // mState3 mState5 -> dest mState7
+ // |
+ // mState4 -> current
+ processMessage(MSG_1, mState5)
+ verifyMessageProcessedBy(MSG_1, mState4, mState3, mState2, mState1)
+ mInOrder.verify(mState4).exit()
+ mInOrder.verify(mState3).exit()
+ mInOrder.verify(mState5).enter()
+ verifyNoMoreInteractions()
+
+ // mState1
+ // / \
+ // mState2 mState6
+ // | \ \
+ // mState3 mState5 -> current mState7 -> dest
+ // |
+ // mState4
+ processMessage(MSG_2, mState7)
+ verifyMessageProcessedBy(MSG_2, mState5, mState2)
+ mInOrder.verify(mState5).exit()
+ mInOrder.verify(mState2).exit()
+ mInOrder.verify(mState6).enter()
+ mInOrder.verify(mState7).enter()
+ verifyNoMoreInteractions()
+ }
+}
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index dd27bf9..4958040 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -190,7 +190,7 @@
OEM_DENY_2_MATCH = (1 << 10),
OEM_DENY_3_MATCH = (1 << 11),
};
-// LINT.ThenChange(packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java)
+// LINT.ThenChange(../framework/src/android/net/BpfNetMapsConstants.java)
enum BpfPermissionMatch {
BPF_PERMISSION_INTERNET = 1 << 2,
diff --git a/common/Android.bp b/common/Android.bp
index c982431..1d73a46 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -25,6 +25,7 @@
// as the above target may not exist
// depending on the branch
+// The library requires the final artifact to contain net-utils-device-common-struct.
java_library {
name: "connectivity-net-module-utils-bpf",
srcs: [
@@ -40,8 +41,9 @@
libs: [
"androidx.annotation_annotation",
"framework-connectivity.stubs.module_lib",
- ],
- static_libs: [
+ // For libraries which are statically linked in framework-connectivity, do not
+ // statically link here because callers of this library might already have a static
+ // version linked.
"net-utils-device-common-struct",
],
apex_available: [
diff --git a/common/flags.aconfig b/common/flags.aconfig
index cadc44f..7235202 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -13,3 +13,17 @@
description: "This flag controls the forbidden capability API"
bug: "302997505"
}
+
+flag {
+ name: "nsd_expired_services_removal"
+ namespace: "android_core_networking"
+ description: "Remove expired services from MdnsServiceCache"
+ bug: "304649384"
+}
+
+flag {
+ name: "set_data_saver_via_cm"
+ namespace: "android_core_networking"
+ description: "Set data saver through ConnectivityManager API"
+ bug: "297836825"
+}
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index f6b5657..06d3238 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -417,12 +417,12 @@
package android.net.thread {
- public class ThreadNetworkController {
+ @FlaggedApi("com.android.net.thread.flags.thread_enabled") public class ThreadNetworkController {
method public int getThreadVersion();
field public static final int THREAD_VERSION_1_3 = 4; // 0x4
}
- public class ThreadNetworkManager {
+ @FlaggedApi("com.android.net.thread.flags.thread_enabled") public class ThreadNetworkManager {
method @NonNull public java.util.List<android.net.thread.ThreadNetworkController> getAllThreadNetworkControllers();
}
diff --git a/framework/Android.bp b/framework/Android.bp
index 182c558..449e652 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -101,7 +101,7 @@
"framework-connectivity-javastream-protos",
],
impl_only_static_libs: [
- "net-utils-device-common-struct",
+ "net-utils-device-common-bpf",
],
libs: [
"androidx.annotation_annotation",
@@ -130,7 +130,7 @@
// to generate the SDK stubs.
// Even if the library is included in "impl_only_static_libs" of defaults. This is still
// needed because java_library which doesn't understand "impl_only_static_libs".
- "net-utils-device-common-struct",
+ "net-utils-device-common-bpf",
],
libs: [
// This cannot be in the defaults clause above because if it were, it would be used
diff --git a/framework/aidl-export/android/net/LocalNetworkConfig.aidl b/framework/aidl-export/android/net/LocalNetworkConfig.aidl
new file mode 100644
index 0000000..e2829a5
--- /dev/null
+++ b/framework/aidl-export/android/net/LocalNetworkConfig.aidl
@@ -0,0 +1,20 @@
+/**
+ *
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+@JavaOnlyStableParcelable parcelable LocalNetworkConfig;
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 193bd92..782e20a 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -24,6 +24,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
+ method @FlaggedApi("com.android.net.flags.set_data_saver_via_cm") @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setDataSaverEnabled(boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setFirewallChainEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean);
diff --git a/framework/src/android/net/BpfNetMapsConstants.java b/framework/src/android/net/BpfNetMapsConstants.java
index 2191682..e0527f5 100644
--- a/framework/src/android/net/BpfNetMapsConstants.java
+++ b/framework/src/android/net/BpfNetMapsConstants.java
@@ -60,7 +60,7 @@
public static final long OEM_DENY_1_MATCH = (1 << 9);
public static final long OEM_DENY_2_MATCH = (1 << 10);
public static final long OEM_DENY_3_MATCH = (1 << 11);
- // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/netd.h)
+ // LINT.ThenChange(../../../../bpf_progs/netd.h)
public static final List<Pair<Long, String>> MATCH_LIST = Arrays.asList(
Pair.create(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"),
diff --git a/framework/src/android/net/BpfNetMapsReader.java b/framework/src/android/net/BpfNetMapsReader.java
new file mode 100644
index 0000000..49e874a
--- /dev/null
+++ b/framework/src/android/net/BpfNetMapsReader.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
+import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
+import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
+import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
+import static android.net.BpfNetMapsUtils.isFirewallAllowList;
+import static android.net.BpfNetMapsUtils.throwIfPreT;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresApi;
+import android.os.Build;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.S32;
+import com.android.net.module.util.Struct.U32;
+
+/**
+ * A helper class to *read* java BpfMaps.
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
+public class BpfNetMapsReader {
+ // Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the
+ // BpfMap implementation.
+
+ // Bpf map to store various networking configurations, the format of the value is different
+ // for different keys. See BpfNetMapsConstants#*_CONFIGURATION_KEY for keys.
+ private final IBpfMap<S32, U32> mConfigurationMap;
+ // Bpf map to store per uid traffic control configurations.
+ // See {@link UidOwnerValue} for more detail.
+ private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap;
+ private final Dependencies mDeps;
+
+ public BpfNetMapsReader() {
+ this(new Dependencies());
+ }
+
+ @VisibleForTesting
+ public BpfNetMapsReader(@NonNull Dependencies deps) {
+ if (!SdkLevel.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ BpfNetMapsReader.class.getSimpleName() + " is not supported below Android T");
+ }
+ mDeps = deps;
+ mConfigurationMap = mDeps.getConfigurationMap();
+ mUidOwnerMap = mDeps.getUidOwnerMap();
+ }
+
+ /**
+ * Dependencies of BpfNetMapReader, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /** Get the configuration map. */
+ public IBpfMap<S32, U32> getConfigurationMap() {
+ try {
+ return new BpfMap<>(CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDONLY,
+ S32.class, U32.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open configuration map", e);
+ }
+ }
+
+ /** Get the uid owner map. */
+ public IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
+ try {
+ return new BpfMap<>(UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDONLY,
+ S32.class, UidOwnerValue.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open uid owner map", e);
+ }
+ }
+ }
+
+ /**
+ * Get the specified firewall chain's status.
+ *
+ * @param chain target chain
+ * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
+ * @throws UnsupportedOperationException if called on pre-T devices.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public boolean isChainEnabled(final int chain) {
+ return isChainEnabled(mConfigurationMap, chain);
+ }
+
+ /**
+ * Get firewall rule of specified firewall chain on specified uid.
+ *
+ * @param chain target chain
+ * @param uid target uid
+ * @return either {@link ConnectivityManager#FIREWALL_RULE_ALLOW} or
+ * {@link ConnectivityManager#FIREWALL_RULE_DENY}.
+ * @throws UnsupportedOperationException if called on pre-T devices.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public int getUidRule(final int chain, final int uid) {
+ return getUidRule(mUidOwnerMap, chain, uid);
+ }
+
+ /**
+ * Get the specified firewall chain's status.
+ *
+ * @param configurationMap target configurationMap
+ * @param chain target chain
+ * @return {@code true} if chain is enabled, {@code false} if chain is not enabled.
+ * @throws UnsupportedOperationException if called on pre-T devices.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public static boolean isChainEnabled(
+ final IBpfMap<Struct.S32, Struct.U32> configurationMap, final int chain) {
+ throwIfPreT("isChainEnabled is not available on pre-T devices");
+
+ final long match = getMatchByFirewallChain(chain);
+ try {
+ final Struct.U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
+ return (config.val & match) != 0;
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno,
+ "Unable to get firewall chain status: " + Os.strerror(e.errno));
+ }
+ }
+
+ /**
+ * Get firewall rule of specified firewall chain on specified uid.
+ *
+ * @param uidOwnerMap target uidOwnerMap.
+ * @param chain target chain.
+ * @param uid target uid.
+ * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+ * @throws UnsupportedOperationException if called on pre-T devices.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public static int getUidRule(final IBpfMap<Struct.S32, UidOwnerValue> uidOwnerMap,
+ final int chain, final int uid) {
+ throwIfPreT("getUidRule is not available on pre-T devices");
+
+ final long match = getMatchByFirewallChain(chain);
+ final boolean isAllowList = isFirewallAllowList(chain);
+ try {
+ final UidOwnerValue uidMatch = uidOwnerMap.getValue(new Struct.S32(uid));
+ final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
+ return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno,
+ "Unable to get uid rule status: " + Os.strerror(e.errno));
+ }
+ }
+}
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index d464e3d..28d5891 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -39,6 +39,8 @@
import android.os.ServiceSpecificException;
import android.util.Pair;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.StringJoiner;
/**
@@ -124,4 +126,15 @@
}
return sj.toString();
}
+
+ public static final boolean PRE_T = !SdkLevel.isAtLeastT();
+
+ /**
+ * Throw UnsupportedOperationException if SdkLevel is before T.
+ */
+ public static void throwIfPreT(final String msg) {
+ if (PRE_T) {
+ throw new UnsupportedOperationException(msg);
+ }
+ }
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 2315521..9e879c2 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -26,6 +26,7 @@
import static android.net.QosCallback.QosCallbackRegistrationException;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -115,6 +116,14 @@
private static final String TAG = "ConnectivityManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is
+ // available here
+ /** @hide */
+ public static class Flags {
+ static final String SET_DATA_SAVER_VIA_CM =
+ "com.android.net.flags.set_data_saver_via_cm";
+ }
+
/**
* A change in network connectivity has occurred. A default connection has either
* been established or lost. The NetworkInfo for the affected network is
@@ -3811,11 +3820,28 @@
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_FACTORY})
- public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp,
- NetworkCapabilities nc, @NonNull NetworkScore score, NetworkAgentConfig config,
- int providerId) {
+ public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni,
+ @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
+ @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId) {
+ return registerNetworkAgent(na, ni, lp, nc, null /* localNetworkConfig */, score, config,
+ providerId);
+ }
+
+ /**
+ * @hide
+ * Register a NetworkAgent with ConnectivityService.
+ * @return Network corresponding to NetworkAgent.
+ */
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_FACTORY})
+ public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni,
+ @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
+ @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
+ @NonNull NetworkAgentConfig config, int providerId) {
try {
- return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId);
+ return mService.registerNetworkAgent(na, ni, lp, nc, score, localNetworkConfig, config,
+ providerId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5941,6 +5967,28 @@
}
/**
+ * Sets data saver switch.
+ *
+ * @param enable True if enable.
+ * @throws IllegalStateException if failed.
+ * @hide
+ */
+ @FlaggedApi(Flags.SET_DATA_SAVER_VIA_CM)
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+ })
+ public void setDataSaverEnabled(final boolean enable) {
+ try {
+ mService.setDataSaverEnabled(enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Adds the specified UID to the list of UIds that are allowed to use data on metered networks
* even when background data is restricted. The deny list takes precedence over the allow list.
*
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index ebe8bca..92e1ea1 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -27,6 +27,7 @@
import android.net.IQosCallback;
import android.net.ISocketKeepaliveCallback;
import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
import android.net.Network;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
@@ -146,7 +147,8 @@
void declareNetworkRequestUnfulfillable(in NetworkRequest request);
Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp,
- in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config,
+ in NetworkCapabilities nc, in NetworkScore score,
+ in LocalNetworkConfig localNetworkConfig, in NetworkAgentConfig config,
in int factorySerialNumber);
NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType,
@@ -238,6 +240,8 @@
void setTestAllowBadWifiUntil(long timeMs);
+ void setDataSaverEnabled(boolean enable);
+
void updateMeteredNetworkAllowList(int uid, boolean add);
void updateMeteredNetworkDenyList(int uid, boolean add);
diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl
index b375b7b..61b27b5 100644
--- a/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -17,6 +17,7 @@
import android.net.DscpPolicy;
import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
@@ -34,6 +35,7 @@
void sendLinkProperties(in LinkProperties lp);
// TODO: consider replacing this by "markConnected()" and removing
void sendNetworkInfo(in NetworkInfo info);
+ void sendLocalNetworkConfig(in LocalNetworkConfig config);
void sendScore(in NetworkScore score);
void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial);
void sendSocketKeepaliveEvent(int slot, int reason);
diff --git a/framework/src/android/net/LocalNetworkConfig.java b/framework/src/android/net/LocalNetworkConfig.java
new file mode 100644
index 0000000..fca7fd1
--- /dev/null
+++ b/framework/src/android/net/LocalNetworkConfig.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class to communicate configuration info about a local network through {@link NetworkAgent}.
+ * @hide
+ */
+// TODO : @SystemApi
+public final class LocalNetworkConfig implements Parcelable {
+ @Nullable
+ private final NetworkRequest mUpstreamSelector;
+
+ @NonNull
+ private final MulticastRoutingConfig mUpstreamMulticastRoutingConfig;
+
+ @NonNull
+ private final MulticastRoutingConfig mDownstreamMulticastRoutingConfig;
+
+ private LocalNetworkConfig(@Nullable final NetworkRequest upstreamSelector,
+ @Nullable final MulticastRoutingConfig upstreamConfig,
+ @Nullable final MulticastRoutingConfig downstreamConfig) {
+ mUpstreamSelector = upstreamSelector;
+ if (null != upstreamConfig) {
+ mUpstreamMulticastRoutingConfig = upstreamConfig;
+ } else {
+ mUpstreamMulticastRoutingConfig = MulticastRoutingConfig.CONFIG_FORWARD_NONE;
+ }
+ if (null != downstreamConfig) {
+ mDownstreamMulticastRoutingConfig = downstreamConfig;
+ } else {
+ mDownstreamMulticastRoutingConfig = MulticastRoutingConfig.CONFIG_FORWARD_NONE;
+ }
+ }
+
+ /**
+ * Get the request choosing which network traffic from this network is forwarded to and from.
+ *
+ * This may be null if the local network doesn't forward the traffic anywhere.
+ */
+ @Nullable
+ public NetworkRequest getUpstreamSelector() {
+ return mUpstreamSelector;
+ }
+
+ public @NonNull MulticastRoutingConfig getUpstreamMulticastRoutingConfig() {
+ return mUpstreamMulticastRoutingConfig;
+ }
+
+ public @NonNull MulticastRoutingConfig getDownstreamMulticastRoutingConfig() {
+ return mDownstreamMulticastRoutingConfig;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+ dest.writeParcelable(mUpstreamSelector, flags);
+ dest.writeParcelable(mUpstreamMulticastRoutingConfig, flags);
+ dest.writeParcelable(mDownstreamMulticastRoutingConfig, flags);
+ }
+
+ public static final @NonNull Creator<LocalNetworkConfig> CREATOR = new Creator<>() {
+ public LocalNetworkConfig createFromParcel(Parcel in) {
+ final NetworkRequest upstreamSelector = in.readParcelable(null);
+ final MulticastRoutingConfig upstreamConfig = in.readParcelable(null);
+ final MulticastRoutingConfig downstreamConfig = in.readParcelable(null);
+ return new LocalNetworkConfig(
+ upstreamSelector, upstreamConfig, downstreamConfig);
+ }
+
+ @Override
+ public LocalNetworkConfig[] newArray(final int size) {
+ return new LocalNetworkConfig[size];
+ }
+ };
+
+
+ public static final class Builder {
+ @Nullable
+ NetworkRequest mUpstreamSelector;
+
+ @Nullable
+ MulticastRoutingConfig mUpstreamMulticastRoutingConfig;
+
+ @Nullable
+ MulticastRoutingConfig mDownstreamMulticastRoutingConfig;
+
+ /**
+ * Create a Builder
+ */
+ public Builder() {
+ }
+
+ /**
+ * Set to choose where this local network should forward its traffic to.
+ *
+ * The system will automatically choose the best network matching the request as an
+ * upstream, and set up forwarding between this local network and the chosen upstream.
+ * If no network matches the request, there is no upstream and the traffic is not forwarded.
+ * The caller can know when this changes by listening to link properties changes of
+ * this network with the {@link android.net.LinkProperties#getForwardedNetwork()} getter.
+ *
+ * Set this to null if the local network shouldn't be forwarded. Default is null.
+ */
+ @NonNull
+ public Builder setUpstreamSelector(@Nullable NetworkRequest upstreamSelector) {
+ mUpstreamSelector = upstreamSelector;
+ return this;
+ }
+
+ /**
+ * Set the upstream multicast routing config.
+ *
+ * If null, don't route multicast packets upstream. This is equivalent to a
+ * MulticastRoutingConfig in mode FORWARD_NONE. The default is null.
+ */
+ @NonNull
+ public Builder setUpstreamMulticastRoutingConfig(@Nullable MulticastRoutingConfig cfg) {
+ mUpstreamMulticastRoutingConfig = cfg;
+ return this;
+ }
+
+ /**
+ * Set the downstream multicast routing config.
+ *
+ * If null, don't route multicast packets downstream. This is equivalent to a
+ * MulticastRoutingConfig in mode FORWARD_NONE. The default is null.
+ */
+ @NonNull
+ public Builder setDownstreamMulticastRoutingConfig(@Nullable MulticastRoutingConfig cfg) {
+ mDownstreamMulticastRoutingConfig = cfg;
+ return this;
+ }
+
+ /**
+ * Build the LocalNetworkConfig object.
+ */
+ @NonNull
+ public LocalNetworkConfig build() {
+ return new LocalNetworkConfig(mUpstreamSelector,
+ mUpstreamMulticastRoutingConfig,
+ mDownstreamMulticastRoutingConfig);
+ }
+ }
+}
diff --git a/framework/src/android/net/MulticastRoutingConfig.java b/framework/src/android/net/MulticastRoutingConfig.java
new file mode 100644
index 0000000..ebd9fc5
--- /dev/null
+++ b/framework/src/android/net/MulticastRoutingConfig.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * A class representing a configuration for multicast routing.
+ *
+ * Internal usage to Connectivity
+ * @hide
+ */
+// TODO : @SystemApi
+public class MulticastRoutingConfig implements Parcelable {
+ private static final String TAG = MulticastRoutingConfig.class.getSimpleName();
+
+ /** Do not forward any multicast packets. */
+ public static final int FORWARD_NONE = 0;
+ /**
+ * Forward only multicast packets with destination in the list of listening addresses.
+ * Ignore the min scope.
+ */
+ public static final int FORWARD_SELECTED = 1;
+ /**
+ * Forward all multicast packets with scope greater or equal than the min scope.
+ * Ignore the list of listening addresses.
+ */
+ public static final int FORWARD_WITH_MIN_SCOPE = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "FORWARD_" }, value = {
+ FORWARD_NONE,
+ FORWARD_SELECTED,
+ FORWARD_WITH_MIN_SCOPE
+ })
+ public @interface MulticastForwardingMode {}
+
+ /**
+ * Not a multicast scope, for configurations that do not use the min scope.
+ */
+ public static final int MULTICAST_SCOPE_NONE = -1;
+
+ public static final MulticastRoutingConfig CONFIG_FORWARD_NONE =
+ new MulticastRoutingConfig(FORWARD_NONE, MULTICAST_SCOPE_NONE, null);
+
+ @MulticastForwardingMode
+ private final int mForwardingMode;
+
+ private final int mMinScope;
+
+ @NonNull
+ private final Set<Inet6Address> mListeningAddresses;
+
+ private MulticastRoutingConfig(@MulticastForwardingMode final int mode, final int scope,
+ @Nullable final Set<Inet6Address> addresses) {
+ mForwardingMode = mode;
+ mMinScope = scope;
+ if (null != addresses) {
+ mListeningAddresses = Collections.unmodifiableSet(new ArraySet<>(addresses));
+ } else {
+ mListeningAddresses = Collections.emptySet();
+ }
+ }
+
+ /**
+ * Returns the forwarding mode.
+ */
+ @MulticastForwardingMode
+ public int getForwardingMode() {
+ return mForwardingMode;
+ }
+
+ /**
+ * Returns the minimal group address scope that is allowed for forwarding.
+ * If the forwarding mode is not FORWARD_WITH_MIN_SCOPE, will be MULTICAST_SCOPE_NONE.
+ */
+ public int getMinScope() {
+ return mMinScope;
+ }
+
+ /**
+ * Returns the list of group addresses listened by the outgoing interface.
+ * The list will be empty if the forwarding mode is not FORWARD_SELECTED.
+ */
+ @NonNull
+ public Set<Inet6Address> getMulticastListeningAddresses() {
+ return mListeningAddresses;
+ }
+
+ private MulticastRoutingConfig(Parcel in) {
+ mForwardingMode = in.readInt();
+ mMinScope = in.readInt();
+ final int count = in.readInt();
+ final ArraySet<Inet6Address> listeningAddresses = new ArraySet<>(count);
+ final byte[] buffer = new byte[16]; // Size of an Inet6Address
+ for (int i = 0; i < count; ++i) {
+ in.readByteArray(buffer);
+ try {
+ listeningAddresses.add((Inet6Address) Inet6Address.getByAddress(buffer));
+ } catch (UnknownHostException e) {
+ Log.wtf(TAG, "Can't read inet6address : " + Arrays.toString(buffer));
+ }
+ }
+ mListeningAddresses = Collections.unmodifiableSet(listeningAddresses);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mForwardingMode);
+ dest.writeInt(mMinScope);
+ dest.writeInt(mListeningAddresses.size());
+ for (final Inet6Address addr : mListeningAddresses) {
+ dest.writeByteArray(addr.getAddress());
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<MulticastRoutingConfig> CREATOR = new Creator<>() {
+ @Override
+ public MulticastRoutingConfig createFromParcel(Parcel in) {
+ return new MulticastRoutingConfig(in);
+ }
+
+ @Override
+ public MulticastRoutingConfig[] newArray(int size) {
+ return new MulticastRoutingConfig[size];
+ }
+ };
+
+ public static class Builder {
+ @MulticastForwardingMode
+ private final int mForwardingMode;
+ private int mMinScope;
+ private final ArraySet<Inet6Address> mListeningAddresses;
+
+ private Builder(@MulticastForwardingMode final int mode, int scope) {
+ mForwardingMode = mode;
+ mMinScope = scope;
+ mListeningAddresses = new ArraySet<>();
+ }
+
+ /**
+ * Create a builder that forwards nothing.
+ * No properties can be set on such a builder.
+ */
+ public static Builder newBuilderForwardingNone() {
+ return new Builder(FORWARD_NONE, MULTICAST_SCOPE_NONE);
+ }
+
+ /**
+ * Create a builder that forwards packets above a certain scope
+ *
+ * The scope can be changed on this builder, but not the listening addresses.
+ * @param scope the initial scope
+ */
+ public static Builder newBuilderWithMinScope(final int scope) {
+ return new Builder(FORWARD_WITH_MIN_SCOPE, scope);
+ }
+
+ /**
+ * Create a builder that forwards a specified list of listening addresses.
+ *
+ * Addresses can be added and removed from this builder, but the scope can't be set.
+ */
+ public static Builder newBuilderWithListeningAddresses() {
+ return new Builder(FORWARD_SELECTED, MULTICAST_SCOPE_NONE);
+ }
+
+ /**
+ * Sets the minimum scope for this multicast routing config.
+ * This is only meaningful (indeed, allowed) for configs in FORWARD_WITH_MIN_SCOPE mode.
+ * @return this builder
+ */
+ public Builder setMinimumScope(final int scope) {
+ if (FORWARD_WITH_MIN_SCOPE != mForwardingMode) {
+ throw new IllegalArgumentException("Can't set the scope on a builder in mode "
+ + modeToString(mForwardingMode));
+ }
+ mMinScope = scope;
+ return this;
+ }
+
+ /**
+ * Add an address to the set of listening addresses.
+ *
+ * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode.
+ * If this address was already added, this is a no-op.
+ * @return this builder
+ */
+ public Builder addListeningAddress(@NonNull final Inet6Address address) {
+ if (FORWARD_SELECTED != mForwardingMode) {
+ throw new IllegalArgumentException("Can't add an address on a builder in mode "
+ + modeToString(mForwardingMode));
+ }
+ // TODO : should we check that this is a multicast address ?
+ mListeningAddresses.add(address);
+ return this;
+ }
+
+ /**
+ * Remove an address from the set of listening addresses.
+ *
+ * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode.
+ * If this address was not added, or was already removed, this is a no-op.
+ * @return this builder
+ */
+ public Builder removeListeningAddress(@NonNull final Inet6Address address) {
+ if (FORWARD_SELECTED != mForwardingMode) {
+ throw new IllegalArgumentException("Can't remove an address on a builder in mode "
+ + modeToString(mForwardingMode));
+ }
+ mListeningAddresses.remove(address);
+ return this;
+ }
+
+ /**
+ * Build the config.
+ */
+ public MulticastRoutingConfig build() {
+ return new MulticastRoutingConfig(mForwardingMode, mMinScope, mListeningAddresses);
+ }
+ }
+
+ private static String modeToString(@MulticastForwardingMode final int mode) {
+ switch (mode) {
+ case FORWARD_NONE: return "FORWARD_NONE";
+ case FORWARD_SELECTED: return "FORWARD_SELECTED";
+ case FORWARD_WITH_MIN_SCOPE: return "FORWARD_WITH_MIN_SCOPE";
+ default: return "unknown multicast routing mode " + mode;
+ }
+ }
+}
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 177f7e3..4e9087c 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -151,7 +151,7 @@
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
- * NetworkCapabilties.
+ * NetworkCapabilities.
* obj = NetworkCapabilities
* @hide
*/
@@ -443,6 +443,14 @@
public static final int EVENT_UNREGISTER_AFTER_REPLACEMENT = BASE + 29;
/**
+ * Sent by the NetworkAgent to ConnectivityService to pass the new value of the local
+ * network agent config.
+ * obj = {@code Pair<NetworkAgentInfo, LocalNetworkConfig>}
+ * @hide
+ */
+ public static final int EVENT_LOCAL_NETWORK_CONFIG_CHANGED = BASE + 30;
+
+ /**
* DSCP policy was successfully added.
*/
public static final int DSCP_POLICY_STATUS_SUCCESS = 0;
@@ -517,20 +525,47 @@
@NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
@NonNull NetworkScore score, @NonNull NetworkAgentConfig config,
@Nullable NetworkProvider provider) {
- this(looper, context, logTag, nc, lp, score, config,
+ this(context, looper, logTag, nc, lp, null /* localNetworkConfig */, score, config,
+ provider);
+ }
+
+ /**
+ * Create a new network agent.
+ * @param context a {@link Context} to get system services from.
+ * @param looper the {@link Looper} on which to invoke the callbacks.
+ * @param logTag the tag for logs
+ * @param nc the initial {@link NetworkCapabilities} of this network. Update with
+ * sendNetworkCapabilities.
+ * @param lp the initial {@link LinkProperties} of this network. Update with sendLinkProperties.
+ * @param localNetworkConfig the initial {@link LocalNetworkConfig} of this
+ * network. Update with sendLocalNetworkConfig. Must be
+ * non-null iff the nc have NET_CAPABILITY_LOCAL_NETWORK.
+ * @param score the initial score of this network. Update with sendNetworkScore.
+ * @param config an immutable {@link NetworkAgentConfig} for this agent.
+ * @param provider the {@link NetworkProvider} managing this agent.
+ * @hide
+ */
+ // TODO : expose
+ public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
+ @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
+ @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
+ @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) {
+ this(looper, context, logTag, nc, lp, localNetworkConfig, score, config,
provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(),
getLegacyNetworkInfo(config));
}
private static class InitialConfiguration {
- public final Context context;
- public final NetworkCapabilities capabilities;
- public final LinkProperties properties;
- public final NetworkScore score;
- public final NetworkAgentConfig config;
- public final NetworkInfo info;
+ @NonNull public final Context context;
+ @NonNull public final NetworkCapabilities capabilities;
+ @NonNull public final LinkProperties properties;
+ @NonNull public final NetworkScore score;
+ @NonNull public final NetworkAgentConfig config;
+ @NonNull public final NetworkInfo info;
+ @Nullable public final LocalNetworkConfig localNetworkConfig;
InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities,
- @NonNull LinkProperties properties, @NonNull NetworkScore score,
+ @NonNull LinkProperties properties,
+ @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
@NonNull NetworkAgentConfig config, @NonNull NetworkInfo info) {
this.context = context;
this.capabilities = capabilities;
@@ -538,14 +573,15 @@
this.score = score;
this.config = config;
this.info = info;
+ this.localNetworkConfig = localNetworkConfig;
}
}
private volatile InitialConfiguration mInitialConfiguration;
private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag,
@NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
- @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId,
- @NonNull NetworkInfo ni) {
+ @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
+ @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) {
mHandler = new NetworkAgentHandler(looper);
LOG_TAG = logTag;
mNetworkInfo = new NetworkInfo(ni);
@@ -556,7 +592,7 @@
mInitialConfiguration = new InitialConfiguration(context,
new NetworkCapabilities(nc, NetworkCapabilities.REDACT_NONE),
- new LinkProperties(lp), score, config, ni);
+ new LinkProperties(lp), localNetworkConfig, score, config, ni);
}
private class NetworkAgentHandler extends Handler {
@@ -723,7 +759,8 @@
mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
new NetworkInfo(mInitialConfiguration.info),
mInitialConfiguration.properties, mInitialConfiguration.capabilities,
- mInitialConfiguration.score, mInitialConfiguration.config, providerId);
+ mInitialConfiguration.localNetworkConfig, mInitialConfiguration.score,
+ mInitialConfiguration.config, providerId);
mInitialConfiguration = null; // All this memory can now be GC'd
}
return mNetwork;
@@ -1099,6 +1136,18 @@
}
/**
+ * Must be called by the agent when the network's {@link LocalNetworkConfig} changes.
+ * @param config the new LocalNetworkConfig
+ * @hide
+ */
+ public void sendLocalNetworkConfig(@NonNull LocalNetworkConfig config) {
+ Objects.requireNonNull(config);
+ // If the agent doesn't have NET_CAPABILITY_LOCAL_NETWORK, this will be ignored by
+ // ConnectivityService with a Log.wtf.
+ queueOrSendMessage(reg -> reg.sendLocalNetworkConfig(config));
+ }
+
+ /**
* Must be called by the agent to update the score of this network.
*
* @param score the new score.
diff --git a/service/src/com/android/server/UidOwnerValue.java b/framework/src/android/net/UidOwnerValue.java
similarity index 86%
rename from service/src/com/android/server/UidOwnerValue.java
rename to framework/src/android/net/UidOwnerValue.java
index d6c0e0d..e8ae604 100644
--- a/service/src/com/android/server/UidOwnerValue.java
+++ b/framework/src/android/net/UidOwnerValue.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-package com.android.server;
+package android.net;
import com.android.net.module.util.Struct;
-/** Value type for per uid traffic control configuration map */
+/**
+ * Value type for per uid traffic control configuration map.
+ *
+ * @hide
+ */
public class UidOwnerValue extends Struct {
// Allowed interface index. Only applicable if IIF_MATCH is set in the rule bitmask below.
@Field(order = 0, type = Type.S32)
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index 5480ef7..1f92374 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -45,4 +45,7 @@
// module "netbpfload" variant "android_x86_apex30": should support
// min_sdk_version(30) for "com.android.tethering": newer SDK(34).
min_sdk_version: "30",
+
+ init_rc: ["netbpfload.rc"],
+ required: ["bpfloader"],
}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index b44a0bc..d150373 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -168,7 +168,7 @@
return 0;
}
-int main(int argc, char** argv) {
+int main(int argc, char** argv, char * const envp[]) {
(void)argc;
android::base::InitLogging(argv, &android::base::KernelLogger);
@@ -257,10 +257,12 @@
return 1;
}
- if (android::base::SetProperty("bpf.progs_loaded", "1") == false) {
- ALOGE("Failed to set bpf.progs_loaded property");
- return 1;
+ ALOGI("done, transferring control to platform bpfloader.");
+
+ const char * args[] = { "/system/bin/bpfloader", NULL, };
+ if (execve(args[0], (char**)args, envp)) {
+ ALOGE("FATAL: execve('/system/bin/bpfloader'): %d[%s]", errno, strerror(errno));
}
- return 0;
+ return 1;
}
diff --git a/netbpfload/initrc-doc/README.txt b/netbpfload/initrc-doc/README.txt
new file mode 100644
index 0000000..42e1fc2
--- /dev/null
+++ b/netbpfload/initrc-doc/README.txt
@@ -0,0 +1,62 @@
+This directory contains comment stripped versions of
+ //system/bpf/bpfloader/bpfloader.rc
+from previous versions of Android.
+
+Generated via:
+ (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/android11-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk30-11-R.rc
+ (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/android12-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk31-12-S.rc
+ (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/android13-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk33-13-T.rc
+ (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/android14-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U.rc
+ (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/main:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U-QPR2.rc
+
+this is entirely equivalent to:
+ (cd /android1/system/bpf && git cat-file -p remotes/goog/rvc-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk30-11-R.rc
+ (cd /android1/system/bpf && git cat-file -p remotes/goog/sc-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk31-12-S.rc
+ (cd /android1/system/bpf && git cat-file -p remotes/goog/tm-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk33-13-T.rc
+ (cd /android1/system/bpf && git cat-file -p remotes/goog/udc-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U.rc
+ (cd /android1/system/bpf && git cat-file -p remotes/goog/main:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U-QPR2.rc
+
+it is also equivalent to:
+ (cd /android1/system/bpf && git cat-file -p remotes/goog/rvc-qpr-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk30-11-R.rc
+ (cd /android1/system/bpf && git cat-file -p remotes/goog/sc-v2-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk31-12-S.rc
+ (cd /android1/system/bpf && git cat-file -p remotes/goog/tm-qpr-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk33-13-T.rc
+ (cd /android1/system/bpf && git cat-file -p remotes/goog/udc-qpr-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U.rc
+
+ie. there were no changes between R/S/T and R/S/T QPR3, and no change between U and U QPR1.
+
+Note: Sv2 sdk/api level is actually 32, it just didn't change anything wrt. bpf, so doesn't matter.
+
+
+Key takeaways:
+
+= R bpfloader:
+ - CHOWN + SYS_ADMIN
+ - asynchronous startup
+ - platform only
+ - proc file setup handled by initrc
+
+= S bpfloader
+ - adds NET_ADMIN
+ - synchronous startup
+ - platform + mainline tethering offload
+
+= T bpfloader
+ - platform + mainline networking (including tethering offload)
+ - supported btf for maps via exec of btfloader
+
+= U bpfloader
+ - proc file setup moved into bpfloader binary
+ - explicitly specified user and groups:
+ group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+ user root
+
+= U QPR2 bpfloader
+ - drops support of btf for maps
+ - invocation of /system/bin/netbpfload binary, which after handling *all*
+ networking bpf related things executes the platform /system/bin/bpfloader
+ which handles non-networking bpf.
+
+Note that there is now a copy of 'netbpfload' provided by the tethering apex
+mainline module at /apex/com.android.tethering/bin/netbpfload, which due
+to the use of execve("/system/bin/bpfloader") relies on T+ selinux which was
+added for btf map support (specifically the ability to exec the "btfloader").
diff --git a/netbpfload/initrc-doc/bpfloader-sdk30-11-R.rc b/netbpfload/initrc-doc/bpfloader-sdk30-11-R.rc
new file mode 100644
index 0000000..482a7db
--- /dev/null
+++ b/netbpfload/initrc-doc/bpfloader-sdk30-11-R.rc
@@ -0,0 +1,11 @@
+on load_bpf_programs
+ write /proc/sys/net/core/bpf_jit_enable 1
+ write /proc/sys/net/core/bpf_jit_kallsyms 1
+ start bpfloader
+
+service bpfloader /system/bin/bpfloader
+ capabilities CHOWN SYS_ADMIN
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,bpfloader-failed
+ updatable
diff --git a/netbpfload/initrc-doc/bpfloader-sdk31-12-S.rc b/netbpfload/initrc-doc/bpfloader-sdk31-12-S.rc
new file mode 100644
index 0000000..4117887
--- /dev/null
+++ b/netbpfload/initrc-doc/bpfloader-sdk31-12-S.rc
@@ -0,0 +1,11 @@
+on load_bpf_programs
+ write /proc/sys/net/core/bpf_jit_enable 1
+ write /proc/sys/net/core/bpf_jit_kallsyms 1
+ exec_start bpfloader
+
+service bpfloader /system/bin/bpfloader
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,bpfloader-failed
+ updatable
diff --git a/netbpfload/initrc-doc/bpfloader-sdk33-13-T.rc b/netbpfload/initrc-doc/bpfloader-sdk33-13-T.rc
new file mode 100644
index 0000000..f0b6700
--- /dev/null
+++ b/netbpfload/initrc-doc/bpfloader-sdk33-13-T.rc
@@ -0,0 +1,12 @@
+on load_bpf_programs
+ write /proc/sys/kernel/unprivileged_bpf_disabled 0
+ write /proc/sys/net/core/bpf_jit_enable 1
+ write /proc/sys/net/core/bpf_jit_kallsyms 1
+ exec_start bpfloader
+
+service bpfloader /system/bin/bpfloader
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,bpfloader-failed
+ updatable
diff --git a/netbpfload/initrc-doc/bpfloader-sdk34-14-U-QPR2.rc b/netbpfload/initrc-doc/bpfloader-sdk34-14-U-QPR2.rc
new file mode 100644
index 0000000..8f3f462
--- /dev/null
+++ b/netbpfload/initrc-doc/bpfloader-sdk34-14-U-QPR2.rc
@@ -0,0 +1,11 @@
+on load_bpf_programs
+ exec_start bpfloader
+
+service bpfloader /system/bin/netbpfload
+ 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
+ updatable
diff --git a/netbpfload/initrc-doc/bpfloader-sdk34-14-U.rc b/netbpfload/initrc-doc/bpfloader-sdk34-14-U.rc
new file mode 100644
index 0000000..592303e
--- /dev/null
+++ b/netbpfload/initrc-doc/bpfloader-sdk34-14-U.rc
@@ -0,0 +1,11 @@
+on load_bpf_programs
+ exec_start bpfloader
+
+service bpfloader /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
+ updatable
diff --git a/netbpfload/netbpfload.rc b/netbpfload/netbpfload.rc
new file mode 100644
index 0000000..14181dc
--- /dev/null
+++ b/netbpfload/netbpfload.rc
@@ -0,0 +1,86 @@
+# zygote-start is what officially starts netd (see //system/core/rootdir/init.rc)
+# However, on some hardware it's started from post-fs-data as well, which is just
+# a tad earlier. There's no benefit to that though, since on 4.9+ P+ devices netd
+# will just block until bpfloader finishes and sets the bpf.progs_loaded property.
+#
+# It is important that we start bpfloader after:
+# - /sys/fs/bpf is already mounted,
+# - apex (incl. rollback) is initialized (so that in the future we can load bpf
+# programs shipped as part of apex mainline modules)
+# - logd is ready for us to log stuff
+#
+# At the same time we want to be as early as possible to reduce races and thus
+# failures (before memory is fragmented, and cpu is busy running tons of other
+# stuff) and we absolutely want to be before netd and the system boot slot is
+# considered to have booted successfully.
+#
+on load_bpf_programs
+ exec_start bpfloader
+
+service bpfloader /system/bin/netbpfload
+ # netbpfload will do network bpf loading, then execute /system/bin/bpfloader
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ # The following group memberships are a workaround for lack of DAC_OVERRIDE
+ # and allow us to open (among other things) files that we created and are
+ # no longer root owned (due to CHOWN) but still have group read access to
+ # one of the following groups. This is not perfect, but a more correct
+ # solution requires significantly more effort to implement.
+ group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+ user root
+ #
+ # Set RLIMIT_MEMLOCK to 1GiB for bpfloader
+ #
+ # Actually only 8MiB would be needed if bpfloader ran as its own uid.
+ #
+ # However, while the rlimit is per-thread, the accounting is system wide.
+ # So, for example, if the graphics stack has already allocated 10MiB of
+ # memlock data before bpfloader even gets a chance to run, it would fail
+ # if its memlock rlimit is only 8MiB - since there would be none left for it.
+ #
+ # bpfloader succeeding is critical to system health, since a failure will
+ # cause netd crashloop and thus system server crashloop... and the only
+ # recovery is a full kernel reboot.
+ #
+ # We've had issues where devices would sometimes (rarely) boot into
+ # a crashloop because bpfloader would occasionally lose a boot time
+ # race against the graphics stack's boot time locked memory allocation.
+ #
+ # Thus bpfloader's memlock has to be 8MB higher then the locked memory
+ # consumption of the root uid anywhere else in the system...
+ # But we don't know what that is for all possible devices...
+ #
+ # Ideally, we'd simply grant bpfloader the IPC_LOCK capability and it
+ # would simply ignore it's memlock rlimit... but it turns that this
+ # capability is not even checked by the kernel's bpf system call.
+ #
+ # As such we simply use 1GiB as a reasonable approximation of infinity.
+ #
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ #
+ # How to debug bootloops caused by 'bpfloader-failed'.
+ #
+ # 1. On some lower RAM devices (like wembley) you may need to first enable developer mode
+ # (from the Settings app UI), and change the developer option "Logger buffer sizes"
+ # from the default (wembley: 64kB) to the maximum (1M) per log buffer.
+ # Otherwise buffer will overflow before you manage to dump it and you'll get useless logs.
+ #
+ # 2. comment out 'reboot_on_failure reboot,bpfloader-failed' below
+ # 3. rebuild/reflash/reboot
+ # 4. as the device is booting up capture bpfloader logs via:
+ # adb logcat -s 'bpfloader:*' 'LibBpfLoader:*' 'NetBpfLoad:*' 'NetBpfLoader:*'
+ #
+ # something like:
+ # $ adb reboot; sleep 1; adb wait-for-device; adb root; sleep 1; adb wait-for-device; adb logcat -s 'bpfloader:*' 'LibBpfLoader:*' 'NetBpfLoad:*' 'NetBpfLoader:*'
+ # will take care of capturing logs as early as possible
+ #
+ # 5. look through the logs from the kernel's bpf verifier that bpfloader dumps out,
+ # it usually makes sense to search back from the end and find the particular
+ # bpf verifier failure that caused bpfloader to terminate early with an error code.
+ # This will probably be something along the lines of 'too many jumps' or
+ # 'cannot prove return value is 0 or 1' or 'unsupported / unknown operation / helper',
+ # 'invalid bpf_context access', etc.
+ #
+ reboot_on_failure reboot,bpfloader-failed
+ # we're not really updatable, but want to be able to load bpf programs shipped in apexes
+ updatable
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 93ccb85..cc3f019 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1703,15 +1703,20 @@
am.addOnUidImportanceListener(new UidImportanceListener(handler),
mRunningAppActiveImportanceCutoff);
+ final MdnsFeatureFlags flags = new MdnsFeatureFlags.Builder()
+ .setIsMdnsOffloadFeatureEnabled(mDeps.isTetheringFeatureNotChickenedOut(
+ mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
+ .setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
+ mContext, MdnsFeatureFlags.INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING))
+ .setIsExpiredServicesRemovalEnabled(mDeps.isTrunkStableFeatureEnabled(
+ MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
+ .build();
mMdnsSocketClient =
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
LOGGER.forSubComponent("MdnsMultinetworkSocketClient"));
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
- mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"));
+ mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"), flags);
handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
- MdnsFeatureFlags flags = new MdnsFeatureFlags.Builder().setIsMdnsOffloadFeatureEnabled(
- mDeps.isTetheringFeatureNotChickenedOut(mContext,
- MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD)).build();
mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"), flags);
mClock = deps.makeClock();
@@ -1769,12 +1774,21 @@
}
/**
+ * @see DeviceConfigUtils#isTrunkStableFeatureEnabled
+ */
+ public boolean isTrunkStableFeatureEnabled(String feature) {
+ return DeviceConfigUtils.isTrunkStableFeatureEnabled(feature);
+ }
+
+ /**
* @see MdnsDiscoveryManager
*/
public MdnsDiscoveryManager makeMdnsDiscoveryManager(
@NonNull ExecutorProvider executorProvider,
- @NonNull MdnsMultinetworkSocketClient socketClient, @NonNull SharedLog sharedLog) {
- return new MdnsDiscoveryManager(executorProvider, socketClient, sharedLog);
+ @NonNull MdnsMultinetworkSocketClient socketClient, @NonNull SharedLog sharedLog,
+ @NonNull MdnsFeatureFlags featureFlags) {
+ return new MdnsDiscoveryManager(
+ executorProvider, socketClient, sharedLog, featureFlags);
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index a946bca..28e3924 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -96,10 +96,11 @@
@NonNull Looper looper, @NonNull byte[] packetCreationBuffer,
@NonNull MdnsInterfaceAdvertiser.Callback cb,
@NonNull String[] deviceHostName,
- @NonNull SharedLog sharedLog) {
+ @NonNull SharedLog sharedLog,
+ @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
// Note NetworkInterface is final and not mockable
return new MdnsInterfaceAdvertiser(socket, initialAddresses, looper,
- packetCreationBuffer, cb, deviceHostName, sharedLog);
+ packetCreationBuffer, cb, deviceHostName, sharedLog, mdnsFeatureFlags);
}
/**
@@ -394,7 +395,8 @@
if (advertiser == null) {
advertiser = mDeps.makeAdvertiser(socket, addresses, mLooper, mPacketCreationBuffer,
mInterfaceAdvertiserCb, mDeviceHostName,
- mSharedLog.forSubComponent(socket.getInterface().getName()));
+ mSharedLog.forSubComponent(socket.getInterface().getName()),
+ mMdnsFeatureFlags);
mAllAdvertisers.put(socket, advertiser);
advertiser.start();
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 24e9fa8..766f999 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -53,6 +53,7 @@
@NonNull private final Handler handler;
@Nullable private final HandlerThread handlerThread;
@NonNull private final MdnsServiceCache serviceCache;
+ @NonNull private final MdnsFeatureFlags mdnsFeatureFlags;
private static class PerSocketServiceTypeClients {
private final ArrayMap<Pair<String, SocketKey>, MdnsServiceTypeClient> clients =
@@ -117,20 +118,22 @@
}
public MdnsDiscoveryManager(@NonNull ExecutorProvider executorProvider,
- @NonNull MdnsSocketClientBase socketClient, @NonNull SharedLog sharedLog) {
+ @NonNull MdnsSocketClientBase socketClient, @NonNull SharedLog sharedLog,
+ @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
this.executorProvider = executorProvider;
this.socketClient = socketClient;
this.sharedLog = sharedLog;
this.perSocketServiceTypeClients = new PerSocketServiceTypeClients();
+ this.mdnsFeatureFlags = mdnsFeatureFlags;
if (socketClient.getLooper() != null) {
this.handlerThread = null;
this.handler = new Handler(socketClient.getLooper());
- this.serviceCache = new MdnsServiceCache(socketClient.getLooper());
+ this.serviceCache = new MdnsServiceCache(socketClient.getLooper(), mdnsFeatureFlags);
} else {
this.handlerThread = new HandlerThread(MdnsDiscoveryManager.class.getSimpleName());
this.handlerThread.start();
this.handler = new Handler(handlerThread.getLooper());
- this.serviceCache = new MdnsServiceCache(handlerThread.getLooper());
+ this.serviceCache = new MdnsServiceCache(handlerThread.getLooper(), mdnsFeatureFlags);
}
}
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 9840409..6f7645e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -20,18 +20,39 @@
*/
public class MdnsFeatureFlags {
/**
- * The feature flag for control whether the mDNS offload is enabled or not.
+ * A feature flag to control whether the mDNS offload is enabled or not.
*/
public static final String NSD_FORCE_DISABLE_MDNS_OFFLOAD = "nsd_force_disable_mdns_offload";
+ /**
+ * A feature flag to control whether the probing question should include
+ * InetAddressRecords or not.
+ */
+ public static final String INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING =
+ "include_inet_address_records_in_probing";
+ /**
+ * A feature flag to control whether expired services removal should be enabled.
+ */
+ public static final String NSD_EXPIRED_SERVICES_REMOVAL =
+ "nsd_expired_services_removal";
+
// Flag for offload feature
public final boolean mIsMdnsOffloadFeatureEnabled;
+ // Flag for including InetAddressRecords in probing questions.
+ public final boolean mIncludeInetAddressRecordsInProbing;
+
+ // Flag for expired services removal
+ public final boolean mIsExpiredServicesRemovalEnabled;
+
/**
* The constructor for {@link MdnsFeatureFlags}.
*/
- public MdnsFeatureFlags(boolean isOffloadFeatureEnabled) {
+ public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
+ boolean includeInetAddressRecordsInProbing, boolean isExpiredServicesRemovalEnabled) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
+ mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
+ mIsExpiredServicesRemovalEnabled = isExpiredServicesRemovalEnabled;
}
@@ -44,16 +65,22 @@
public static final class Builder {
private boolean mIsMdnsOffloadFeatureEnabled;
+ private boolean mIncludeInetAddressRecordsInProbing;
+ private boolean mIsExpiredServicesRemovalEnabled;
/**
* The constructor for {@link Builder}.
*/
public Builder() {
mIsMdnsOffloadFeatureEnabled = false;
+ mIncludeInetAddressRecordsInProbing = false;
+ mIsExpiredServicesRemovalEnabled = true; // Default enabled.
}
/**
- * Set if the mDNS offload feature is enabled.
+ * Set whether the mDNS offload feature is enabled.
+ *
+ * @see #NSD_FORCE_DISABLE_MDNS_OFFLOAD
*/
public Builder setIsMdnsOffloadFeatureEnabled(boolean isMdnsOffloadFeatureEnabled) {
mIsMdnsOffloadFeatureEnabled = isMdnsOffloadFeatureEnabled;
@@ -61,11 +88,32 @@
}
/**
+ * Set whether the probing question should include InetAddressRecords.
+ *
+ * @see #INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING
+ */
+ public Builder setIncludeInetAddressRecordsInProbing(
+ boolean includeInetAddressRecordsInProbing) {
+ mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
+ return this;
+ }
+
+ /**
+ * Set whether the expired services removal is enabled.
+ *
+ * @see #NSD_EXPIRED_SERVICES_REMOVAL
+ */
+ public Builder setIsExpiredServicesRemovalEnabled(boolean isExpiredServicesRemovalEnabled) {
+ mIsExpiredServicesRemovalEnabled = isExpiredServicesRemovalEnabled;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
public MdnsFeatureFlags build() {
- return new MdnsFeatureFlags(mIsMdnsOffloadFeatureEnabled);
+ return new MdnsFeatureFlags(mIsMdnsOffloadFeatureEnabled,
+ mIncludeInetAddressRecordsInProbing, mIsExpiredServicesRemovalEnabled);
}
-
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
index dd8a526..973fd96 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
@@ -18,7 +18,7 @@
import android.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
import java.io.IOException;
import java.net.Inet4Address;
@@ -29,7 +29,7 @@
import java.util.Objects;
/** An mDNS "AAAA" or "A" record, which holds an IPv6 or IPv4 address. */
-@VisibleForTesting
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class MdnsInetAddressRecord extends MdnsRecord {
@Nullable private Inet6Address inet6Address;
@Nullable private Inet4Address inet4Address;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 37e9743..e07d380 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -150,8 +150,8 @@
/** @see MdnsRecordRepository */
@NonNull
public MdnsRecordRepository makeRecordRepository(@NonNull Looper looper,
- @NonNull String[] deviceHostName) {
- return new MdnsRecordRepository(looper, deviceHostName);
+ @NonNull String[] deviceHostName, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
+ return new MdnsRecordRepository(looper, deviceHostName, mdnsFeatureFlags);
}
/** @see MdnsReplySender */
@@ -187,16 +187,18 @@
public MdnsInterfaceAdvertiser(@NonNull MdnsInterfaceSocket socket,
@NonNull List<LinkAddress> initialAddresses, @NonNull Looper looper,
@NonNull byte[] packetCreationBuffer, @NonNull Callback cb,
- @NonNull String[] deviceHostName, @NonNull SharedLog sharedLog) {
+ @NonNull String[] deviceHostName, @NonNull SharedLog sharedLog,
+ @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
this(socket, initialAddresses, looper, packetCreationBuffer, cb,
- new Dependencies(), deviceHostName, sharedLog);
+ new Dependencies(), deviceHostName, sharedLog, mdnsFeatureFlags);
}
public MdnsInterfaceAdvertiser(@NonNull MdnsInterfaceSocket socket,
@NonNull List<LinkAddress> initialAddresses, @NonNull Looper looper,
@NonNull byte[] packetCreationBuffer, @NonNull Callback cb, @NonNull Dependencies deps,
- @NonNull String[] deviceHostName, @NonNull SharedLog sharedLog) {
- mRecordRepository = deps.makeRecordRepository(looper, deviceHostName);
+ @NonNull String[] deviceHostName, @NonNull SharedLog sharedLog,
+ @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
+ mRecordRepository = deps.makeRecordRepository(looper, deviceHostName, mdnsFeatureFlags);
mRecordRepository.updateAddresses(initialAddresses);
mSocket = socket;
mCb = cb;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
index c88ead0..41cc380 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
@@ -18,14 +18,15 @@
import android.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
+
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.util.Arrays;
/** An mDNS "PTR" record, which holds a name (the "pointer"). */
-@VisibleForTesting
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class MdnsPointerRecord extends MdnsRecord {
private String[] pointer;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 130ff48..73c1758 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -92,16 +92,19 @@
private final Looper mLooper;
@NonNull
private final String[] mDeviceHostname;
+ private final MdnsFeatureFlags mMdnsFeatureFlags;
- public MdnsRecordRepository(@NonNull Looper looper, @NonNull String[] deviceHostname) {
- this(looper, new Dependencies(), deviceHostname);
+ public MdnsRecordRepository(@NonNull Looper looper, @NonNull String[] deviceHostname,
+ @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
+ this(looper, new Dependencies(), deviceHostname, mdnsFeatureFlags);
}
@VisibleForTesting
public MdnsRecordRepository(@NonNull Looper looper, @NonNull Dependencies deps,
- @NonNull String[] deviceHostname) {
+ @NonNull String[] deviceHostname, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
mDeviceHostname = deviceHostname;
mLooper = looper;
+ mMdnsFeatureFlags = mdnsFeatureFlags;
}
/**
@@ -351,7 +354,8 @@
}
private MdnsProber.ProbingInfo makeProbingInfo(int serviceId,
- @NonNull MdnsServiceRecord srvRecord) {
+ @NonNull MdnsServiceRecord srvRecord,
+ @NonNull List<MdnsInetAddressRecord> inetAddressRecords) {
final List<MdnsRecord> probingRecords = new ArrayList<>();
// Probe with cacheFlush cleared; it is set when announcing, as it was verified unique:
// RFC6762 10.2
@@ -363,6 +367,15 @@
srvRecord.getServicePort(),
srvRecord.getServiceHost()));
+ for (MdnsInetAddressRecord inetAddressRecord : inetAddressRecords) {
+ probingRecords.add(new MdnsInetAddressRecord(inetAddressRecord.getName(),
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ inetAddressRecord.getTtl(),
+ inetAddressRecord.getInet4Address() == null
+ ? inetAddressRecord.getInet6Address()
+ : inetAddressRecord.getInet4Address()));
+ }
return new MdnsProber.ProbingInfo(serviceId, probingRecords);
}
@@ -824,6 +837,18 @@
return conflicting;
}
+ private List<MdnsInetAddressRecord> makeProbingInetAddressRecords() {
+ final List<MdnsInetAddressRecord> records = new ArrayList<>();
+ if (mMdnsFeatureFlags.mIncludeInetAddressRecordsInProbing) {
+ for (RecordInfo<?> record : mGeneralRecords) {
+ if (record.record instanceof MdnsInetAddressRecord) {
+ records.add((MdnsInetAddressRecord) record.record);
+ }
+ }
+ }
+ return records;
+ }
+
/**
* (Re)set a service to the probing state.
* @return The {@link MdnsProber.ProbingInfo} to send for probing.
@@ -834,7 +859,8 @@
if (registration == null) return null;
registration.setProbing(true);
- return makeProbingInfo(serviceId, registration.srvRecord.record);
+ return makeProbingInfo(
+ serviceId, registration.srvRecord.record, makeProbingInetAddressRecords());
}
/**
@@ -870,7 +896,8 @@
final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
existing.subtype, existing.repliedServiceCount, existing.sentPacketCount);
mServices.put(serviceId, newService);
- return makeProbingInfo(serviceId, newService.srvRecord.record);
+ return makeProbingInfo(
+ serviceId, newService.srvRecord.record, makeProbingInetAddressRecords());
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
index f9ee0df..d3493c7 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
@@ -80,9 +80,12 @@
private final ArrayMap<CacheKey, ServiceExpiredCallback> mCallbacks = new ArrayMap<>();
@NonNull
private final Handler mHandler;
+ @NonNull
+ private final MdnsFeatureFlags mMdnsFeatureFlags;
- public MdnsServiceCache(@NonNull Looper looper) {
+ public MdnsServiceCache(@NonNull Looper looper, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
mHandler = new Handler(looper);
+ mMdnsFeatureFlags = mdnsFeatureFlags;
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
index f851b35..4d407be 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
@@ -18,7 +18,8 @@
import android.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
+
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
@@ -27,7 +28,7 @@
import java.util.Objects;
/** An mDNS "SRV" record, which contains service information. */
-@VisibleForTesting
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class MdnsServiceRecord extends MdnsRecord {
public static final int PROTO_NONE = 0;
public static final int PROTO_TCP = 1;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
index 4149dbe..cf6c8ac 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
@@ -18,7 +18,8 @@
import android.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
+
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
import java.io.IOException;
@@ -28,7 +29,7 @@
import java.util.Objects;
/** An mDNS "TXT" record, which contains a list of {@link TextEntry}. */
-@VisibleForTesting
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class MdnsTextRecord extends MdnsRecord {
private List<TextEntry> entries;
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 25e59d5..cc67550 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -517,11 +517,12 @@
break;
}
case MSG_NOTIFY_NETWORK_STATUS: {
- // If no cached states, ignore.
- if (mLastNetworkStateSnapshots == null) break;
- // TODO (b/181642673): Protect mDefaultNetworks from concurrent accessing.
- handleNotifyNetworkStatus(
- mDefaultNetworks, mLastNetworkStateSnapshots, mActiveIface);
+ synchronized (mStatsLock) {
+ // If no cached states, ignore.
+ if (mLastNetworkStateSnapshots == null) break;
+ handleNotifyNetworkStatus(
+ mDefaultNetworks, mLastNetworkStateSnapshots, mActiveIface);
+ }
break;
}
case MSG_PERFORM_POLL_REGISTER_ALERT: {
diff --git a/service/Android.bp b/service/Android.bp
index 8e59e86..250693f 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -188,7 +188,6 @@
"dnsresolver_aidl_interface-V11-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
- "net-utils-device-common-bpf",
"net-utils-device-common-ip",
"net-utils-device-common-netlink",
"net-utils-services-common",
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 6a34a24..671c4ac 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -26,6 +26,7 @@
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_PERMISSION_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
+import static android.net.BpfNetMapsUtils.PRE_T;
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
import static android.net.BpfNetMapsUtils.matchToString;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
@@ -51,7 +52,9 @@
import android.app.StatsManager;
import android.content.Context;
+import android.net.BpfNetMapsReader;
import android.net.INetd;
+import android.net.UidOwnerValue;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -92,7 +95,6 @@
* {@hide}
*/
public class BpfNetMaps {
- private static final boolean PRE_T = !SdkLevel.isAtLeastT();
static {
if (!PRE_T) {
System.loadLibrary("service-connectivity");
@@ -298,6 +300,7 @@
}
/** Constructor used after T that doesn't need to use netd anymore. */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public BpfNetMaps(final Context context) {
this(context, null);
@@ -420,6 +423,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void addNaughtyApp(final int uid) {
throwIfPreT("addNaughtyApp is not available on pre-T devices");
@@ -438,6 +442,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void removeNaughtyApp(final int uid) {
throwIfPreT("removeNaughtyApp is not available on pre-T devices");
@@ -456,6 +461,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void addNiceApp(final int uid) {
throwIfPreT("addNiceApp is not available on pre-T devices");
@@ -474,6 +480,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void removeNiceApp(final int uid) {
throwIfPreT("removeNiceApp is not available on pre-T devices");
@@ -494,6 +501,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setChildChain(final int childChain, final boolean enable) {
throwIfPreT("setChildChain is not available on pre-T devices");
@@ -523,18 +531,14 @@
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
+ *
+ * @deprecated Use {@link BpfNetMapsReader#isChainEnabled} instead.
*/
+ // TODO: Migrate the callers to use {@link BpfNetMapsReader#isChainEnabled} instead.
+ @Deprecated
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public boolean isChainEnabled(final int childChain) {
- throwIfPreT("isChainEnabled is not available on pre-T devices");
-
- final long match = getMatchByFirewallChain(childChain);
- try {
- final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
- return (config.val & match) != 0;
- } catch (ErrnoException e) {
- throw new ServiceSpecificException(e.errno,
- "Unable to get firewall chain status: " + Os.strerror(e.errno));
- }
+ return BpfNetMapsReader.isChainEnabled(sConfigurationMap, childChain);
}
private Set<Integer> asSet(final int[] uids) {
@@ -554,6 +558,7 @@
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws IllegalArgumentException if {@code chain} is not a valid chain.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void replaceUidChain(final int chain, final int[] uids) {
throwIfPreT("replaceUidChain is not available on pre-T devices");
@@ -638,6 +643,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setUidRule(final int childChain, final int uid, final int firewallRule) {
throwIfPreT("setUidRule is not available on pre-T devices");
@@ -667,20 +673,12 @@
* @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
+ *
+ * @deprecated use {@link BpfNetMapsReader#getUidRule} instead.
*/
+ // TODO: Migrate the callers to use {@link BpfNetMapsReader#getUidRule} instead.
public int getUidRule(final int childChain, final int uid) {
- throwIfPreT("isUidChainEnabled is not available on pre-T devices");
-
- final long match = getMatchByFirewallChain(childChain);
- final boolean isAllowList = isFirewallAllowList(childChain);
- try {
- final UidOwnerValue uidMatch = sUidOwnerMap.getValue(new S32(uid));
- final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
- return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
- } catch (ErrnoException e) {
- throw new ServiceSpecificException(e.errno,
- "Unable to get uid rule status: " + Os.strerror(e.errno));
- }
+ return BpfNetMapsReader.getUidRule(sUidOwnerMap, childChain, uid);
}
private Set<Integer> getUidsMatchEnabled(final int childChain) throws ErrnoException {
@@ -830,6 +828,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void updateUidLockdownRule(final int uid, final boolean add) {
throwIfPreT("updateUidLockdownRule is not available on pre-T devices");
@@ -852,6 +851,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void swapActiveStatsMap() {
throwIfPreT("swapActiveStatsMap is not available on pre-T devices");
@@ -927,6 +927,7 @@
}
/** Register callback for statsd to pull atom. */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void setPullAtomCallback(final Context context) {
throwIfPreT("setPullAtomCallback is not available on pre-T devices");
@@ -1016,6 +1017,7 @@
* @throws IOException when file descriptor is invalid.
* @throws ServiceSpecificException when the method is called on an unsupported device.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public void dump(final IndentingPrintWriter pw, final FileDescriptor fd, boolean verbose)
throws IOException, ServiceSpecificException {
if (PRE_T) {
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 8475110..ada5860 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
@@ -67,6 +68,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
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_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -98,6 +100,11 @@
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND;
+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.NetworkMonitorUtils.isPrivateDnsValidationRequired;
import static com.android.net.module.util.PermissionUtils.checkAnyPermissionOf;
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
@@ -159,6 +166,7 @@
import android.net.IpMemoryStore;
import android.net.IpPrefix;
import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
import android.net.MatchAllNetworkSpecifier;
import android.net.NativeNetworkConfig;
import android.net.NativeNetworkType;
@@ -277,6 +285,7 @@
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.BinderUtils;
import com.android.net.module.util.BitUtils;
+import com.android.net.module.util.BpfUtils;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.InterfaceParams;
@@ -303,6 +312,7 @@
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.DscpPolicyTracker;
import com.android.server.connectivity.FullScore;
+import com.android.server.connectivity.HandlerUtils;
import com.android.server.connectivity.InvalidTagException;
import com.android.server.connectivity.KeepaliveResourceUtil;
import com.android.server.connectivity.KeepaliveTracker;
@@ -1255,16 +1265,24 @@
private static final String PRIORITY_ARG = "--dump-priority";
private static final String PRIORITY_ARG_HIGH = "HIGH";
private static final String PRIORITY_ARG_NORMAL = "NORMAL";
+ private static final int DUMPSYS_DEFAULT_TIMEOUT_MS = 10_000;
LocalPriorityDump() {}
private void dumpHigh(FileDescriptor fd, PrintWriter pw) {
- doDump(fd, pw, new String[] {DIAG_ARG});
- doDump(fd, pw, new String[] {SHORT_ARG});
+ if (!HandlerUtils.runWithScissors(mHandler, () -> {
+ doDump(fd, pw, new String[]{DIAG_ARG});
+ doDump(fd, pw, new String[]{SHORT_ARG});
+ }, DUMPSYS_DEFAULT_TIMEOUT_MS)) {
+ pw.println("dumpHigh timeout");
+ }
}
private void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
- doDump(fd, pw, args);
+ if (!HandlerUtils.runWithScissors(mHandler, () -> doDump(fd, pw, args),
+ DUMPSYS_DEFAULT_TIMEOUT_MS)) {
+ pw.println("dumpNormal timeout");
+ }
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -1517,6 +1535,14 @@
}
/**
+ * Get BPF program Id from CGROUP. See {@link BpfUtils#getProgramId}.
+ */
+ public int getBpfProgramId(final int attachType, @NonNull final String cgroupPath)
+ throws IOException {
+ return BpfUtils.getProgramId(attachType, cgroupPath);
+ }
+
+ /**
* Wraps {@link BroadcastOptionsShimImpl#newInstance(BroadcastOptions)}
*/
// TODO: when available in all active branches:
@@ -1773,7 +1799,7 @@
mNoServiceNetwork = new NetworkAgentInfo(null,
new Network(INetd.UNREACHABLE_NET_ID),
new NetworkInfo(TYPE_NONE, 0, "", ""),
- new LinkProperties(), new NetworkCapabilities(),
+ new LinkProperties(), new NetworkCapabilities(), null /* localNetworkConfig */,
new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null,
new NetworkAgentConfig(), this, null, null, 0, INVALID_UID,
mLingerDelayMs, mQosCallbackTracker, mDeps);
@@ -3240,6 +3266,26 @@
pw.decreaseIndent();
}
+ private void dumpBpfProgramStatus(IndentingPrintWriter pw) {
+ pw.println("Bpf Program Status:");
+ pw.increaseIndent();
+ try {
+ pw.print("CGROUP_INET_INGRESS: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_INGRESS, BpfUtils.CGROUP_PATH));
+ pw.print("CGROUP_INET_EGRESS: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_EGRESS, BpfUtils.CGROUP_PATH));
+ pw.print("CGROUP_INET_SOCK_CREATE: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_SOCK_CREATE, BpfUtils.CGROUP_PATH));
+ pw.print("CGROUP_INET4_BIND: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET4_BIND, BpfUtils.CGROUP_PATH));
+ pw.print("CGROUP_INET6_BIND: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET6_BIND, BpfUtils.CGROUP_PATH));
+ } catch (IOException e) {
+ pw.println(" IOException");
+ }
+ pw.decreaseIndent();
+ }
+
@VisibleForTesting
static final String KEY_DESTROY_FROZEN_SOCKETS_VERSION = "destroy_frozen_sockets_version";
@VisibleForTesting
@@ -3854,6 +3900,9 @@
dumpCloseFrozenAppSockets(pw);
pw.println();
+ dumpBpfProgramStatus(pw);
+
+ pw.println();
if (!CollectionUtils.contains(args, SHORT_ARG)) {
pw.println();
@@ -4121,6 +4170,11 @@
updateNetworkInfo(nai, info);
break;
}
+ case NetworkAgent.EVENT_LOCAL_NETWORK_CONFIG_CHANGED: {
+ final LocalNetworkConfig config = (LocalNetworkConfig) arg.second;
+ updateLocalNetworkConfig(nai, config);
+ break;
+ }
case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
updateNetworkScore(nai, (NetworkScore) arg.second);
break;
@@ -5006,7 +5060,10 @@
!nai.networkAgentConfig.allowBypass /* secure */,
getVpnType(nai), nai.networkAgentConfig.excludeLocalRouteVpn);
} else {
- config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.PHYSICAL,
+ final boolean hasLocalCap =
+ nai.networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK);
+ config = new NativeNetworkConfig(nai.network.getNetId(),
+ hasLocalCap ? NativeNetworkType.PHYSICAL_LOCAL : NativeNetworkType.PHYSICAL,
getNetworkPermission(nai.networkCapabilities),
false /* secure */,
VpnManager.TYPE_VPN_NONE,
@@ -8057,6 +8114,18 @@
}
/**
+ * Returns whether local agents are supported on this device.
+ *
+ * Local agents are supported from U on TVs, and from V on all devices.
+ */
+ @VisibleForTesting
+ public boolean areLocalAgentsSupported() {
+ final PackageManager pm = mContext.getPackageManager();
+ // Local agents are supported starting on U on TVs and on V on everything else.
+ return mDeps.isAtLeastV() || (mDeps.isAtLeastU() && pm.hasSystemFeature(FEATURE_LEANBACK));
+ }
+
+ /**
* Register a new agent with ConnectivityService to handle a network.
*
* @param na a reference for ConnectivityService to contact the agent asynchronously.
@@ -8067,13 +8136,18 @@
* @param networkCapabilities the initial capabilites of this network. They can be updated
* later : see {@link #updateCapabilities}.
* @param initialScore the initial score of the network. See {@link NetworkAgentInfo#getScore}.
+ * @param localNetworkConfig config about this local network, or null if not a local network
* @param networkAgentConfig metadata about the network. This is never updated.
* @param providerId the ID of the provider owning this NetworkAgent.
* @return the network created for this agent.
*/
- public Network registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo,
- LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- @NonNull NetworkScore initialScore, NetworkAgentConfig networkAgentConfig,
+ public Network registerNetworkAgent(INetworkAgent na,
+ NetworkInfo networkInfo,
+ LinkProperties linkProperties,
+ NetworkCapabilities networkCapabilities,
+ @NonNull NetworkScore initialScore,
+ @Nullable LocalNetworkConfig localNetworkConfig,
+ NetworkAgentConfig networkAgentConfig,
int providerId) {
Objects.requireNonNull(networkInfo, "networkInfo must not be null");
Objects.requireNonNull(linkProperties, "linkProperties must not be null");
@@ -8085,12 +8159,26 @@
} else {
enforceNetworkFactoryPermission();
}
+ final boolean hasLocalCap =
+ networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK);
+ if (hasLocalCap && !areLocalAgentsSupported()) {
+ // Before U, netd doesn't support PHYSICAL_LOCAL networks so this can't work.
+ throw new IllegalArgumentException("Local agents are not supported in this version");
+ }
+ final boolean hasLocalNetworkConfig = null != localNetworkConfig;
+ if (hasLocalCap != hasLocalNetworkConfig) {
+ throw new IllegalArgumentException(null != localNetworkConfig
+ ? "Only local network agents can have a LocalNetworkConfig"
+ : "Local network agents must have a LocalNetworkConfig"
+ );
+ }
final int uid = mDeps.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
return registerNetworkAgentInternal(na, networkInfo, linkProperties,
- networkCapabilities, initialScore, networkAgentConfig, providerId, uid);
+ networkCapabilities, initialScore, networkAgentConfig, localNetworkConfig,
+ providerId, uid);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -8098,7 +8186,8 @@
private Network registerNetworkAgentInternal(INetworkAgent na, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId,
+ NetworkScore currentScore, NetworkAgentConfig networkAgentConfig,
+ @Nullable LocalNetworkConfig localNetworkConfig, int providerId,
int uid) {
// Make a copy of the passed NI, LP, NC as the caller may hold a reference to them
@@ -8106,6 +8195,7 @@
final NetworkInfo niCopy = new NetworkInfo(networkInfo);
final NetworkCapabilities ncCopy = new NetworkCapabilities(networkCapabilities);
final LinkProperties lpCopy = new LinkProperties(linkProperties);
+ // No need to copy |localNetworkConfiguration| as it is immutable.
// At this point the capabilities/properties are untrusted and unverified, e.g. checks that
// the capabilities' access UIDs comply with security limitations. They will be sanitized
@@ -8113,9 +8203,9 @@
// because some of the checks must happen on the handler thread.
final NetworkAgentInfo nai = new NetworkAgentInfo(na,
new Network(mNetIdManager.reserveNetId()), niCopy, lpCopy, ncCopy,
- currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
- this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs,
- mQosCallbackTracker, mDeps);
+ localNetworkConfig, currentScore, mContext, mTrackerHandler,
+ new NetworkAgentConfig(networkAgentConfig), this, mNetd, mDnsResolver, providerId,
+ uid, mLingerDelayMs, mQosCallbackTracker, mDeps);
final String extraInfo = niCopy.getExtraInfo();
final String name = TextUtils.isEmpty(extraInfo)
@@ -8850,6 +8940,16 @@
updateCapabilities(nai.getScore(), nai, nai.networkCapabilities);
}
+ private void updateLocalNetworkConfig(@NonNull final NetworkAgentInfo nai,
+ @NonNull final LocalNetworkConfig config) {
+ if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)) {
+ Log.wtf(TAG, "Ignoring update of a local network info on non-local network " + nai);
+ return;
+ }
+ // TODO : actually apply the diff.
+ nai.localNetworkConfig = config;
+ }
+
/**
* Returns the interface which requires VPN isolation (ingress interface filtering).
*
@@ -9190,7 +9290,7 @@
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
- Bundle bundle = new Bundle();
+ final Bundle bundle = new Bundle();
// TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
// TODO: check if defensive copies of data is needed.
final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
@@ -10738,6 +10838,17 @@
Log.d(TAG, "Reevaluating network " + nai.network);
reportNetworkConnectivity(nai.network, !nai.isValidated());
return 0;
+ case "bpf-get-cgroup-program-id": {
+ // Usage : adb shell cmd connectivity bpf-get-cgroup-program-id <type>
+ // Get cgroup bpf program Id for the given type. See BpfUtils#getProgramId
+ // for more detail.
+ // If type can't be parsed, this throws NumberFormatException, which
+ // is passed back to adb who prints it.
+ final int type = Integer.parseInt(getNextArg());
+ final int ret = BpfUtils.getProgramId(type, BpfUtils.CGROUP_PATH);
+ pw.println(ret);
+ return 0;
+ }
default:
return handleDefaultCommands(cmd);
}
@@ -12476,6 +12587,20 @@
}
@Override
+ public void setDataSaverEnabled(final boolean enable) {
+ enforceNetworkStackOrSettingsPermission();
+ try {
+ final boolean ret = mNetd.bandwidthEnableDataSaver(enable);
+ if (!ret) {
+ throw new IllegalStateException("Error when changing iptables: " + enable);
+ }
+ } catch (RemoteException e) {
+ // Lack of permission or binder errors.
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
public void updateMeteredNetworkAllowList(final int uid, final boolean add) {
enforceNetworkStackOrSettingsPermission();
diff --git a/service/src/com/android/server/connectivity/ConnectivityNativeService.java b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
index e16117b..cf6127f 100644
--- a/service/src/com/android/server/connectivity/ConnectivityNativeService.java
+++ b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
@@ -16,9 +16,6 @@
package com.android.server.connectivity;
-import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND;
-import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -31,11 +28,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.BpfBitmap;
-import com.android.net.module.util.BpfUtils;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.PermissionUtils;
-import java.io.IOException;
import java.util.ArrayList;
/**
@@ -45,7 +40,7 @@
public static final String SERVICE_NAME = "connectivity_native";
private static final String TAG = ConnectivityNativeService.class.getSimpleName();
- private static final String CGROUP_PATH = "/sys/fs/cgroup";
+
private static final String BLOCKED_PORTS_MAP_PATH =
"/sys/fs/bpf/net_shared/map_block_blocked_ports_map";
diff --git a/service/src/com/android/server/connectivity/HandlerUtils.java b/service/src/com/android/server/connectivity/HandlerUtils.java
new file mode 100644
index 0000000..997ecbf
--- /dev/null
+++ b/service/src/com/android/server/connectivity/HandlerUtils.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+
+/**
+ * Helper class for Handler related utilities.
+ *
+ * @hide
+ */
+public class HandlerUtils {
+ // Note: @hide methods copied from android.os.Handler
+ /**
+ * Runs the specified task synchronously.
+ * <p>
+ * If the current thread is the same as the handler thread, then the runnable
+ * runs immediately without being enqueued. Otherwise, posts the runnable
+ * to the handler and waits for it to complete before returning.
+ * </p><p>
+ * This method is dangerous! Improper use can result in deadlocks.
+ * Never call this method while any locks are held or use it in a
+ * possibly re-entrant manner.
+ * </p><p>
+ * This method is occasionally useful in situations where a background thread
+ * must synchronously await completion of a task that must run on the
+ * handler's thread. However, this problem is often a symptom of bad design.
+ * Consider improving the design (if possible) before resorting to this method.
+ * </p><p>
+ * One example of where you might want to use this method is when you just
+ * set up a Handler thread and need to perform some initialization steps on
+ * it before continuing execution.
+ * </p><p>
+ * If timeout occurs then this method returns <code>false</code> but the runnable
+ * will remain posted on the handler and may already be in progress or
+ * complete at a later time.
+ * </p><p>
+ * When using this method, be sure to use {@link Looper#quitSafely} when
+ * quitting the looper. Otherwise {@link #runWithScissors} may hang indefinitely.
+ * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
+ * </p>
+ *
+ * @param h The target handler.
+ * @param r The Runnable that will be executed synchronously.
+ * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
+ *
+ * @return Returns true if the Runnable was successfully executed.
+ * Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ *
+ * @hide This method is prone to abuse and should probably not be in the API.
+ * If we ever do make it part of the API, we might want to rename it to something
+ * less funny like runUnsafe().
+ */
+ public static boolean runWithScissors(@NonNull Handler h, @NonNull Runnable r, long timeout) {
+ if (r == null) {
+ throw new IllegalArgumentException("runnable must not be null");
+ }
+ if (timeout < 0) {
+ throw new IllegalArgumentException("timeout must be non-negative");
+ }
+
+ if (Looper.myLooper() == h.getLooper()) {
+ r.run();
+ return true;
+ }
+
+ BlockingRunnable br = new BlockingRunnable(r);
+ return br.postAndWait(h, timeout);
+ }
+
+ private static final class BlockingRunnable implements Runnable {
+ private final Runnable mTask;
+ private boolean mDone;
+
+ BlockingRunnable(Runnable task) {
+ mTask = task;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mTask.run();
+ } finally {
+ synchronized (this) {
+ mDone = true;
+ notifyAll();
+ }
+ }
+ }
+
+ public boolean postAndWait(Handler handler, long timeout) {
+ if (!handler.post(this)) {
+ return false;
+ }
+
+ synchronized (this) {
+ if (timeout > 0) {
+ final long expirationTime = SystemClock.uptimeMillis() + timeout;
+ while (!mDone) {
+ long delay = expirationTime - SystemClock.uptimeMillis();
+ if (delay <= 0) {
+ return false; // timeout
+ }
+ try {
+ wait(delay);
+ } catch (InterruptedException ex) {
+ }
+ }
+ } else {
+ while (!mDone) {
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 0f72cd4..b0ad978 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -35,6 +35,7 @@
import android.net.INetworkAgentRegistry;
import android.net.INetworkMonitor;
import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
import android.net.NattKeepalivePacketData;
import android.net.Network;
import android.net.NetworkAgent;
@@ -64,7 +65,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.WakeupMessage;
-import com.android.modules.utils.build.SdkLevel;
import com.android.server.ConnectivityService;
import java.io.PrintWriter;
@@ -174,6 +174,7 @@
// TODO: make this private with a getter.
@NonNull public NetworkCapabilities networkCapabilities;
@NonNull public final NetworkAgentConfig networkAgentConfig;
+ @Nullable public LocalNetworkConfig localNetworkConfig;
// Underlying networks declared by the agent.
// The networks in this list might be declared by a VPN using setUnderlyingNetworks and are
@@ -470,8 +471,8 @@
+ networkCapabilities.getOwnerUid() + " to " + nc.getOwnerUid());
nc.setOwnerUid(networkCapabilities.getOwnerUid());
}
- restrictCapabilitiesFromNetworkAgent(
- nc, creatorUid, mHasAutomotiveFeature, carrierPrivilegeAuthenticator);
+ restrictCapabilitiesFromNetworkAgent(nc, creatorUid, mHasAutomotiveFeature,
+ mConnServiceDeps, carrierPrivilegeAuthenticator);
return nc;
}
@@ -601,6 +602,7 @@
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final boolean VDBG = false;
private final ConnectivityService mConnService;
+ private final ConnectivityService.Dependencies mConnServiceDeps;
private final Context mContext;
private final Handler mHandler;
private final QosCallbackTracker mQosCallbackTracker;
@@ -609,6 +611,7 @@
public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info,
@NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
+ @Nullable LocalNetworkConfig localNetworkConfig,
@NonNull NetworkScore score, Context context,
Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
IDnsResolver dnsResolver, int factorySerialNumber, int creatorUid,
@@ -626,8 +629,10 @@
networkInfo = info;
linkProperties = lp;
networkCapabilities = nc;
+ this.localNetworkConfig = localNetworkConfig;
networkAgentConfig = config;
mConnService = connService;
+ mConnServiceDeps = deps;
setScore(score); // uses members connService, networkCapabilities and networkAgentConfig
clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
mContext = context;
@@ -904,6 +909,12 @@
}
@Override
+ public void sendLocalNetworkConfig(@NonNull final LocalNetworkConfig config) {
+ mHandler.obtainMessage(NetworkAgent.EVENT_LOCAL_NETWORK_CONFIG_CHANGED,
+ new Pair<>(NetworkAgentInfo.this, config)).sendToTarget();
+ }
+
+ @Override
public void sendScore(@NonNull final NetworkScore score) {
mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_SCORE_CHANGED,
new Pair<>(NetworkAgentInfo.this, score)).sendToTarget();
@@ -1518,23 +1529,26 @@
*/
public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
final int creatorUid, final boolean hasAutomotiveFeature,
+ @NonNull final ConnectivityService.Dependencies deps,
@Nullable final CarrierPrivilegeAuthenticator authenticator) {
if (nc.hasTransport(TRANSPORT_TEST)) {
nc.restrictCapabilitiesForTestNetwork(creatorUid);
}
- if (!areAllowedUidsAcceptableFromNetworkAgent(nc, hasAutomotiveFeature, authenticator)) {
+ if (!areAllowedUidsAcceptableFromNetworkAgent(
+ nc, hasAutomotiveFeature, deps, authenticator)) {
nc.setAllowedUids(new ArraySet<>());
}
}
private static boolean areAllowedUidsAcceptableFromNetworkAgent(
@NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
+ @NonNull final ConnectivityService.Dependencies deps,
@Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
// NCs without access UIDs are fine.
if (!nc.hasAllowedUids()) return true;
// S and below must never accept access UIDs, even if an agent sends them, because netd
// didn't support the required feature in S.
- if (!SdkLevel.isAtLeastT()) return false;
+ if (!deps.isAtLeastT()) return false;
// On a non-restricted network, access UIDs make no sense
if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 621759e..0bcb757 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -181,6 +181,8 @@
},
}
+// The net-utils-device-common-netlink library requires the callers to contain
+// net-utils-device-common-struct.
java_library {
name: "net-utils-device-common-netlink",
srcs: [
@@ -192,12 +194,13 @@
"//packages/modules/Connectivity:__subpackages__",
"//packages/modules/NetworkStack:__subpackages__",
],
- static_libs: [
- "net-utils-device-common-struct",
- ],
libs: [
"androidx.annotation_annotation",
"framework-connectivity.stubs.module_lib",
+ // For libraries which are statically linked in framework-connectivity, do not
+ // statically link here because callers of this library might already have a static
+ // version linked.
+ "net-utils-device-common-struct",
],
apex_available: [
"com.android.tethering",
@@ -209,6 +212,8 @@
},
}
+// The net-utils-device-common-ip library requires the callers to contain
+// net-utils-device-common-struct.
java_library {
// TODO : this target should probably be folded into net-utils-device-common
name: "net-utils-device-common-ip",
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
index 98fda56..ea18d37 100644
--- a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
@@ -159,9 +159,11 @@
throws RemoteException, ServiceSpecificException {
netd.tetherInterfaceAdd(iface);
networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs);
- List<RouteInfo> routes = new ArrayList<>();
- routes.add(new RouteInfo(dest, null, iface, RTN_UNICAST));
- addRoutesToLocalNetwork(netd, iface, routes);
+ // Activate a route to dest and IPv6 link local.
+ modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
+ new RouteInfo(dest, null, iface, RTN_UNICAST));
+ modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
+ new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST));
}
/**
diff --git a/staticlibs/device/com/android/net/module/util/BpfUtils.java b/staticlibs/device/com/android/net/module/util/BpfUtils.java
index f1546c0..6116a5f 100644
--- a/staticlibs/device/com/android/net/module/util/BpfUtils.java
+++ b/staticlibs/device/com/android/net/module/util/BpfUtils.java
@@ -32,9 +32,13 @@
// Defined in include/uapi/linux/bpf.h. Only adding the CGROUPS currently being used for now.
public static final int BPF_CGROUP_INET_INGRESS = 0;
public static final int BPF_CGROUP_INET_EGRESS = 1;
+ 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;
+ // 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.
+ public static final String CGROUP_PATH = "/sys/fs/cgroup/";
/**
* Attach BPF program to CGROUP
@@ -53,6 +57,20 @@
}
/**
+ * Get BPF program Id from CGROUP.
+ *
+ * Note: This requires a 4.19 kernel which is only guaranteed on V+.
+ *
+ * @param attachType Bpf attach type. See bpf_attach_type in include/uapi/linux/bpf.h.
+ * @param cgroupPath Path of cgroup.
+ * @return Positive integer for a Program Id. 0 if no program is attached.
+ * @throws IOException if failed to open the cgroup directory or query bpf program.
+ */
+ public static int getProgramId(int attachType, @NonNull String cgroupPath) throws IOException {
+ return native_getProgramIdFromCgroup(attachType, cgroupPath);
+ }
+
+ /**
* Detach single BPF program from CGROUP
*/
public static void detachSingleProgram(int type, @NonNull String programPath,
diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java
index b638a46..dc0d19b 100644
--- a/staticlibs/device/com/android/net/module/util/Struct.java
+++ b/staticlibs/device/com/android/net/module/util/Struct.java
@@ -146,6 +146,14 @@
int arraysize() default 0;
}
+ /**
+ * Indicates that this field contains a computed value and is ignored for the purposes of Struct
+ * parsing.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface Computed {}
+
private static class FieldInfo {
@NonNull
public final Field annotation;
@@ -533,6 +541,7 @@
final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)];
for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) continue;
+ if (field.getAnnotation(Computed.class) != null) continue;
final Field annotation = field.getAnnotation(Field.class);
if (annotation == null) {
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
index 176b7bc..ecdd440 100644
--- a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
@@ -18,12 +18,17 @@
import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IAPREFIX;
+import android.net.IpPrefix;
import android.util.Log;
import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Computed;
import com.android.net.module.util.Struct.Field;
import com.android.net.module.util.Struct.Type;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -70,7 +75,11 @@
@Field(order = 5, type = Type.ByteArray, arraysize = 16)
public final byte[] prefix;
- public IaPrefixOption(final short code, final short length, final long preferred,
+ @Computed
+ private final IpPrefix mIpPrefix;
+
+ // Constructor used by Struct.parse()
+ protected IaPrefixOption(final short code, final short length, final long preferred,
final long valid, final byte prefixLen, final byte[] prefix) {
this.code = code;
this.length = length;
@@ -78,42 +87,68 @@
this.valid = valid;
this.prefixLen = prefixLen;
this.prefix = prefix.clone();
+
+ try {
+ final Inet6Address addr = (Inet6Address) InetAddress.getByAddress(prefix);
+ mIpPrefix = new IpPrefix(addr, prefixLen);
+ } catch (UnknownHostException | ClassCastException e) {
+ // UnknownHostException should never happen unless prefix is null.
+ // ClassCastException can occur when prefix is an IPv6 mapped IPv4 address.
+ // Both scenarios should throw an exception in the context of Struct#parse().
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public IaPrefixOption(final short length, final long preferred, final long valid,
+ final byte prefixLen, final byte[] prefix) {
+ this((byte) DHCP6_OPTION_IAPREFIX, length, preferred, valid, prefixLen, prefix);
}
/**
* Check whether or not IA Prefix option in IA_PD option is valid per RFC8415#section-21.22.
+ *
+ * Note: an expired prefix can still be valid.
*/
- public boolean isValid(int t2) {
- if (preferred < 0 || valid < 0) {
- Log.w(TAG, "IA_PD option with invalid lifetime, preferred lifetime " + preferred
- + ", valid lifetime " + valid);
+ public boolean isValid() {
+ if (preferred < 0) {
+ Log.w(TAG, "Invalid preferred lifetime: " + this);
+ return false;
+ }
+ if (valid < 0) {
+ Log.w(TAG, "Invalid valid lifetime: " + this);
return false;
}
if (preferred > valid) {
- Log.w(TAG, "IA_PD option with preferred lifetime " + preferred
- + " greater than valid lifetime " + valid);
+ Log.w(TAG, "Invalid lifetime. Preferred lifetime > valid lifetime: " + this);
return false;
}
if (prefixLen > 64) {
- Log.w(TAG, "IA_PD option with prefix length " + prefixLen
- + " longer than 64");
- return false;
- }
- // Either preferred lifetime or t2 might be 0 which is valid, then ignore it.
- if (preferred != 0 && t2 != 0 && preferred < t2) {
- Log.w(TAG, "preferred lifetime " + preferred + " is smaller than T2 " + t2);
+ Log.w(TAG, "Invalid prefix length: " + this);
return false;
}
return true;
}
/**
+ * Check whether or not IA Prefix option has 0 preferred and valid lifetimes.
+ */
+ public boolean withZeroLifetimes() {
+ return preferred == 0 && valid == 0;
+ }
+
+ /**
* Build an IA_PD prefix option with given specific parameters.
*/
public static ByteBuffer build(final short length, final long preferred, final long valid,
final byte prefixLen, final byte[] prefix) {
- final IaPrefixOption option = new IaPrefixOption((byte) DHCP6_OPTION_IAPREFIX,
+ final IaPrefixOption option = new IaPrefixOption(
length /* 25 + IAPrefix options length */, preferred, valid, prefixLen, prefix);
return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
}
+
+ @Override
+ public String toString() {
+ return "IA Prefix, length " + length + ": " + mIpPrefix + ", pref " + preferred + ", valid "
+ + valid;
+ }
}
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 5fe7ac3..a5e5afb 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -38,6 +38,7 @@
"net-utils-device-common",
"net-utils-device-common-async",
"net-utils-device-common-netlink",
+ "net-utils-device-common-struct",
"net-utils-device-common-wear",
"modules-utils-build_system",
],
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
index 733bd98..70f20d6 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
@@ -43,6 +43,8 @@
public class TestBpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> {
private final ConcurrentHashMap<K, V> mMap = new ConcurrentHashMap<>();
+ public TestBpfMap() {}
+
// TODO: Remove this constructor
public TestBpfMap(final Class<K> key, final Class<V> value) {
}
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
index eb94781..600a623 100644
--- a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
+++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -128,7 +128,7 @@
if (testInfo.device.getApiLevel() < 31) return
testInfo.exec("cmd connectivity set-chain3-enabled $enableChain")
enablePkgs.forEach { (pkg, allow) ->
- testInfo.exec("cmd connectivity set-package-networking-enabled $pkg $allow")
+ testInfo.exec("cmd connectivity set-package-networking-enabled $allow $pkg")
}
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index 0610774..93cc911 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -27,7 +27,7 @@
import android.os.RemoteException;
public class MyServiceClient {
- private static final int TIMEOUT_MS = 5000;
+ private static final int TIMEOUT_MS = 20_000;
private static final String PACKAGE = MyServiceClient.class.getPackage().getName();
private static final String APP2_PACKAGE = PACKAGE + ".app2";
private static final String SERVICE_NAME = APP2_PACKAGE + ".MyService";
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 5937655..392cba9 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -704,6 +704,7 @@
argThat<NetworkInfo> { it.detailedState == NetworkInfo.DetailedState.CONNECTING },
any(LinkProperties::class.java),
any(NetworkCapabilities::class.java),
+ any(), // LocalNetworkConfig TODO : specify when it's public
any(NetworkScore::class.java),
any(NetworkAgentConfig::class.java),
eq(NetworkProvider.ID_NONE))
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 12919ae..f705e34 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -45,6 +45,7 @@
// order-dependent setup.
"NetworkStackApiStableLib",
"androidx.test.ext.junit",
+ "compatibility-device-util-axt",
"frameworks-net-integration-testutils",
"kotlin-reflect",
"mockito-target-extended-minus-junit4",
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index e264b55..9b082a4 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -40,11 +40,14 @@
import android.os.IBinder
import android.os.SystemConfigManager
import android.os.UserHandle
+import android.os.VintfRuntimeInfo
import android.testing.TestableContext
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
import com.android.connectivity.resources.R
+import com.android.net.module.util.BpfUtils
import com.android.server.BpfNetMaps
import com.android.server.ConnectivityService
import com.android.server.NetworkAgentWrapper
@@ -53,6 +56,7 @@
import com.android.server.connectivity.MockableSystemProperties
import com.android.server.connectivity.MultinetworkPolicyTracker
import com.android.server.connectivity.ProxyTracker
+import com.android.testutils.DeviceInfoUtils
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.TestableNetworkCallback
import kotlin.test.assertEquals
@@ -60,6 +64,7 @@
import kotlin.test.assertTrue
import kotlin.test.fail
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
@@ -302,4 +307,25 @@
!it.hasCapability(NET_CAPABILITY_VALIDATED)
}
}
+
+ private fun isBpfGetCgroupProgramIdSupportedByKernel(): Boolean {
+ val kVersionString = VintfRuntimeInfo.getKernelVersion()
+ return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.19") >= 0
+ }
+
+ @Test
+ fun testBpfProgramAttachStatus() {
+ Assume.assumeTrue(isBpfGetCgroupProgramIdSupportedByKernel())
+
+ listOf(
+ BpfUtils.BPF_CGROUP_INET_INGRESS,
+ BpfUtils.BPF_CGROUP_INET_EGRESS,
+ BpfUtils.BPF_CGROUP_INET_SOCK_CREATE
+ ).forEach {
+ val ret = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd connectivity bpf-get-cgroup-program-id $it").trim()
+
+ assertTrue(Integer.parseInt(ret) > 0, "Unexpected output $ret for type $it")
+ }
+ }
}
diff --git a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
new file mode 100644
index 0000000..facb932
--- /dev/null
+++ b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net
+
+import android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY
+import android.net.BpfNetMapsUtils.getMatchByFirewallChain
+import android.os.Build
+import com.android.net.module.util.IBpfMap
+import com.android.net.module.util.Struct.S32
+import com.android.net.module.util.Struct.U32
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.TestBpfMap
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+// pre-T devices does not support Bpf.
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class BpfNetMapsReaderTest {
+ private val testConfigurationMap: IBpfMap<S32, U32> = TestBpfMap()
+ private val testUidOwnerMap: IBpfMap<S32, UidOwnerValue> = TestBpfMap()
+ private val bpfNetMapsReader = BpfNetMapsReader(
+ TestDependencies(testConfigurationMap, testUidOwnerMap))
+
+ class TestDependencies(
+ private val configMap: IBpfMap<S32, U32>,
+ private val uidOwnerMap: IBpfMap<S32, UidOwnerValue>
+ ) : BpfNetMapsReader.Dependencies() {
+ override fun getConfigurationMap() = configMap
+ override fun getUidOwnerMap() = uidOwnerMap
+ }
+
+ private fun doTestIsChainEnabled(chain: Int) {
+ testConfigurationMap.updateEntry(
+ UID_RULES_CONFIGURATION_KEY,
+ U32(getMatchByFirewallChain(chain))
+ )
+ assertTrue(bpfNetMapsReader.isChainEnabled(chain))
+ testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
+ assertFalse(bpfNetMapsReader.isChainEnabled(chain))
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testIsChainEnabled() {
+ doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE)
+ doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY)
+ doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_POWERSAVE)
+ doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_RESTRICTED)
+ doTestIsChainEnabled(ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY)
+ }
+}
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index 2170882..1e1fd35 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -54,6 +54,7 @@
import com.android.frameworks.tests.net.R;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.SkipPresubmit;
import org.junit.After;
import org.junit.Test;
@@ -343,6 +344,7 @@
}
+ @SkipPresubmit(reason = "Flaky: b/302325928; add to presubmit after fixing")
@Test
public void testFuzzing() throws Exception {
try {
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 5f280c6..da5f7e1 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -66,6 +66,7 @@
import android.content.Context;
import android.net.BpfNetMapsUtils;
import android.net.INetd;
+import android.net.UidOwnerValue;
import android.os.Build;
import android.os.ServiceSpecificException;
import android.system.ErrnoException;
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index ee11f7c..194cec3 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -935,24 +935,39 @@
return appUid + (firstSdkSandboxUid - Process.FIRST_APPLICATION_UID);
}
- // This function assumes the UID range for user 0 ([1, 99999])
- private static UidRangeParcel[] uidRangeParcelsExcludingUids(Integer... excludedUids) {
- int start = 1;
- Arrays.sort(excludedUids);
- List<UidRangeParcel> parcels = new ArrayList<UidRangeParcel>();
+ // Create the list of ranges for the primary user (User 0), excluding excludedUids.
+ private static List<Range<Integer>> intRangesPrimaryExcludingUids(List<Integer> excludedUids) {
+ final List<Integer> excludedUidsList = new ArrayList<>(excludedUids);
+ // Uid 0 is always excluded
+ if (!excludedUidsList.contains(0)) {
+ excludedUidsList.add(0);
+ }
+ return intRangesExcludingUids(PRIMARY_USER, excludedUidsList);
+ }
+
+ private static List<Range<Integer>> intRangesExcludingUids(int userId,
+ List<Integer> excludedAppIds) {
+ final List<Integer> excludedUids = CollectionUtils.map(excludedAppIds,
+ appId -> UserHandle.getUid(userId, appId));
+ final int userBase = userId * UserHandle.PER_USER_RANGE;
+ final int maxUid = userBase + UserHandle.PER_USER_RANGE - 1;
+
+ int start = userBase;
+ Collections.sort(excludedUids);
+ final List<Range<Integer>> ranges = new ArrayList<>();
for (int excludedUid : excludedUids) {
if (excludedUid == start) {
start++;
} else {
- parcels.add(new UidRangeParcel(start, excludedUid - 1));
+ ranges.add(new Range<>(start, excludedUid - 1));
start = excludedUid + 1;
}
}
- if (start <= 99999) {
- parcels.add(new UidRangeParcel(start, 99999));
+ if (start <= maxUid) {
+ ranges.add(new Range<>(start, maxUid));
}
- return parcels.toArray(new UidRangeParcel[0]);
+ return ranges;
}
private void waitForIdle() {
@@ -1720,6 +1735,14 @@
UnderlyingNetworkInfo underlyingNetworkInfo) {
mUnderlyingNetworkInfo = underlyingNetworkInfo;
}
+
+ @Override
+ public synchronized boolean setUnderlyingNetworks(@Nullable Network[] networks) {
+ if (!mAgentRegistered) return false;
+ mMockNetworkAgent.setUnderlyingNetworks(
+ (networks == null) ? null : Arrays.asList(networks));
+ return true;
+ }
}
private UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) {
@@ -1731,6 +1754,12 @@
return ranges.stream().map(r -> new UidRangeParcel(r, r)).toArray(UidRangeParcel[]::new);
}
+ private static UidRangeParcel[] intToUidRangeStableParcels(
+ final @NonNull List<Range<Integer>> ranges) {
+ return ranges.stream().map(
+ r -> new UidRangeParcel(r.getLower(), r.getUpper())).toArray(UidRangeParcel[]::new);
+ }
+
private void assertVpnTransportInfo(NetworkCapabilities nc, int type) {
assertNotNull(nc);
final TransportInfo ti = nc.getTransportInfo();
@@ -1863,6 +1892,8 @@
private static final UserHandle TERTIARY_USER_HANDLE = new UserHandle(TERTIARY_USER);
private static final int RESTRICTED_USER = 1;
+ private static final UidRange RESTRICTED_USER_UIDRANGE =
+ UidRange.createForUser(UserHandle.of(RESTRICTED_USER));
private static final UserInfo RESTRICTED_USER_INFO = new UserInfo(RESTRICTED_USER, "",
UserInfo.FLAG_RESTRICTED);
static {
@@ -2268,6 +2299,11 @@
}
@Override
+ public int getBpfProgramId(final int attachType, @NonNull final String cgroupPath) {
+ return 0;
+ }
+
+ @Override
public BroadcastOptionsShim makeBroadcastOptionsShim(BroadcastOptions options) {
reset(mBroadcastOptionsShim);
return mBroadcastOptionsShim;
@@ -9413,11 +9449,11 @@
&& c.hasTransport(TRANSPORT_WIFI));
callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
- doReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)).when(mPackageManager)
- .getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER);
-
- // New user added
- mMockVpn.onUserAdded(RESTRICTED_USER);
+ // New user added, this updates the Vpn uids, coverage in VpnTest.
+ // This is equivalent to `mMockVpn.onUserAdded(RESTRICTED_USER);`
+ final Set<UidRange> ranges = uidRangesForUids(uid);
+ ranges.add(RESTRICTED_USER_UIDRANGE);
+ mMockVpn.setUids(ranges);
// Expect that the VPN UID ranges contain both |uid| and the UID range for the newly-added
// restricted user.
@@ -9442,7 +9478,9 @@
&& !c.hasTransport(TRANSPORT_WIFI));
// User removed and expect to lose the UID range for the restricted user.
- mMockVpn.onUserRemoved(RESTRICTED_USER);
+ // This updates the Vpn uids, coverage in VpnTest.
+ // This is equivalent to `mMockVpn.onUserRemoved(RESTRICTED_USER);`
+ mMockVpn.setUids(uidRangesForUids(uid));
// Expect that the VPN gains the UID range for the restricted user, and that the capability
// change made just before that (i.e., loss of TRANSPORT_WIFI) is preserved.
@@ -9483,8 +9521,16 @@
assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
// Enable always-on VPN lockdown. The main user loses network access because no VPN is up.
- final ArrayList<String> allowList = new ArrayList<>();
- mMockVpn.setAlwaysOnPackage(ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ // Coverage in VpnTest.
+ final List<Integer> excludedUids = new ArrayList<>();
+ excludedUids.add(VPN_UID);
+ if (mDeps.isAtLeastT()) {
+ // On T onwards, the corresponding SDK sandbox UID should also be excluded
+ excludedUids.add(toSdkSandboxUid(VPN_UID));
+ }
+ final List<Range<Integer>> primaryRanges = intRangesPrimaryExcludingUids(excludedUids);
+ mCm.setRequireVpnForUids(true, primaryRanges);
+
waitForIdle();
assertNull(mCm.getActiveNetworkForUid(uid));
// This is arguably overspecified: a UID that is not running doesn't have an active network.
@@ -9493,32 +9539,28 @@
assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
// Start the restricted profile, and check that the UID within it loses network access.
- doReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)).when(mPackageManager)
- .getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER);
- doReturn(asList(PRIMARY_USER_INFO, RESTRICTED_USER_INFO)).when(mUserManager)
- .getAliveUsers();
// TODO: check that VPN app within restricted profile still has access, etc.
- mMockVpn.onUserAdded(RESTRICTED_USER);
- final Intent addedIntent = new Intent(ACTION_USER_ADDED);
- addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
- processBroadcast(addedIntent);
+ // Add a restricted user.
+ // This is equivalent to `mMockVpn.onUserAdded(RESTRICTED_USER);`, coverage in VpnTest.
+ final List<Range<Integer>> restrictedRanges =
+ intRangesExcludingUids(RESTRICTED_USER, excludedUids);
+ mCm.setRequireVpnForUids(true, restrictedRanges);
+ waitForIdle();
+
assertNull(mCm.getActiveNetworkForUid(uid));
assertNull(mCm.getActiveNetworkForUid(restrictedUid));
// Stop the restricted profile, and check that the UID within it has network access again.
- doReturn(asList(PRIMARY_USER_INFO)).when(mUserManager).getAliveUsers();
+ // Remove the restricted user.
+ // This is equivalent to `mMockVpn.onUserRemoved(RESTRICTED_USER);`, coverage in VpnTest.
+ mCm.setRequireVpnForUids(false, restrictedRanges);
+ waitForIdle();
- // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user.
- mMockVpn.onUserRemoved(RESTRICTED_USER);
- final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
- removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
- removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
- processBroadcast(removedIntent);
assertNull(mCm.getActiveNetworkForUid(uid));
assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
- mMockVpn.setAlwaysOnPackage(null, false /* lockdown */, allowList);
+ mCm.setRequireVpnForUids(false, primaryRanges);
+
waitForIdle();
}
@@ -9971,18 +10013,20 @@
new Handler(ConnectivityThread.getInstanceLooper()));
final int uid = Process.myUid();
- final ArrayList<String> allowList = new ArrayList<>();
- mMockVpn.setAlwaysOnPackage(ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
- waitForIdle();
- final Set<Integer> excludedUids = new ArraySet<Integer>();
+ // Enable always-on VPN lockdown, coverage in VpnTest.
+ final List<Integer> excludedUids = new ArrayList<Integer>();
excludedUids.add(VPN_UID);
if (mDeps.isAtLeastT()) {
// On T onwards, the corresponding SDK sandbox UID should also be excluded
excludedUids.add(toSdkSandboxUid(VPN_UID));
}
- final UidRangeParcel[] uidRangeParcels = uidRangeParcelsExcludingUids(
- excludedUids.toArray(new Integer[0]));
+
+ final List<Range<Integer>> primaryRanges = intRangesPrimaryExcludingUids(excludedUids);
+ mCm.setRequireVpnForUids(true, primaryRanges);
+ waitForIdle();
+
+ final UidRangeParcel[] uidRangeParcels = intToUidRangeStableParcels(primaryRanges);
InOrder inOrder = inOrder(mMockNetd);
expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
@@ -10002,7 +10046,8 @@
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
// Disable lockdown, expect to see the network unblocked.
- mMockVpn.setAlwaysOnPackage(null, false /* lockdown */, allowList);
+ mCm.setRequireVpnForUids(false, primaryRanges);
+ waitForIdle();
callback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> !cb.getBlocked());
defaultCallback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> !cb.getBlocked());
vpnUidCallback.assertNoCallback();
@@ -10015,22 +10060,25 @@
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
- // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked.
- allowList.add(TEST_PACKAGE_NAME);
- mMockVpn.setAlwaysOnPackage(ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ // Add our UID to the allowlist, expect network is not blocked. Coverage in VpnTest.
+ excludedUids.add(uid);
+ if (mDeps.isAtLeastT()) {
+ // On T onwards, the corresponding SDK sandbox UID should also be excluded
+ excludedUids.add(toSdkSandboxUid(uid));
+ }
+ final List<Range<Integer>> primaryRangesExcludingUid =
+ intRangesPrimaryExcludingUids(excludedUids);
+ mCm.setRequireVpnForUids(true, primaryRangesExcludingUid);
+ waitForIdle();
+
callback.assertNoCallback();
defaultCallback.assertNoCallback();
vpnUidCallback.assertNoCallback();
vpnUidDefaultCallback.assertNoCallback();
vpnDefaultCallbackAsUid.assertNoCallback();
- excludedUids.add(uid);
- if (mDeps.isAtLeastT()) {
- // On T onwards, the corresponding SDK sandbox UID should also be excluded
- excludedUids.add(toSdkSandboxUid(uid));
- }
- final UidRangeParcel[] uidRangeParcelsAlsoExcludingUs = uidRangeParcelsExcludingUids(
- excludedUids.toArray(new Integer[0]));
+ final UidRangeParcel[] uidRangeParcelsAlsoExcludingUs =
+ intToUidRangeStableParcels(primaryRangesExcludingUid);
expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcelsAlsoExcludingUs);
assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
@@ -10053,15 +10101,15 @@
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
- // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown.
- // Everything should now be blocked.
- mMockVpn.setAlwaysOnPackage(null, false /* lockdown */, allowList);
+ // Disable lockdown
+ mCm.setRequireVpnForUids(false, primaryRangesExcludingUid);
waitForIdle();
expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcelsAlsoExcludingUs);
- allowList.clear();
- mMockVpn.setAlwaysOnPackage(ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ // Remove our UID from the allowlist, and re-enable lockdown.
+ mCm.setRequireVpnForUids(true, primaryRanges);
waitForIdle();
expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
+ // Everything should now be blocked.
defaultCallback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> cb.getBlocked());
assertBlockedCallbackInAnyOrder(callback, true, mWiFiAgent, mCellAgent);
vpnUidCallback.assertNoCallback();
@@ -10074,7 +10122,7 @@
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
// Disable lockdown. Everything is unblocked.
- mMockVpn.setAlwaysOnPackage(null, false /* lockdown */, allowList);
+ mCm.setRequireVpnForUids(false, primaryRanges);
defaultCallback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> !cb.getBlocked());
assertBlockedCallbackInAnyOrder(callback, false, mWiFiAgent, mCellAgent);
vpnUidCallback.assertNoCallback();
@@ -10086,36 +10134,8 @@
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
- // Enable and disable an always-on VPN package without lockdown. Expect no changes.
- reset(mMockNetd);
- mMockVpn.setAlwaysOnPackage(ALWAYS_ON_PACKAGE, false /* lockdown */, allowList);
- inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
- callback.assertNoCallback();
- defaultCallback.assertNoCallback();
- vpnUidCallback.assertNoCallback();
- vpnUidDefaultCallback.assertNoCallback();
- vpnDefaultCallbackAsUid.assertNoCallback();
- assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
- assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
- assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
- assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
- assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
-
- mMockVpn.setAlwaysOnPackage(null, false /* lockdown */, allowList);
- inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
- callback.assertNoCallback();
- defaultCallback.assertNoCallback();
- vpnUidCallback.assertNoCallback();
- vpnUidDefaultCallback.assertNoCallback();
- vpnDefaultCallbackAsUid.assertNoCallback();
- assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
- assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
- assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
- assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
- assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
-
// Enable lockdown and connect a VPN. The VPN is not blocked.
- mMockVpn.setAlwaysOnPackage(ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ mCm.setRequireVpnForUids(true, primaryRanges);
defaultCallback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> cb.getBlocked());
assertBlockedCallbackInAnyOrder(callback, true, mWiFiAgent, mCellAgent);
vpnUidCallback.assertNoCallback();
@@ -10249,7 +10269,9 @@
// Init lockdown state to simulate LockdownVpnTracker behavior.
mCm.setLegacyLockdownVpnEnabled(true);
mMockVpn.setEnableTeardown(false);
- mMockVpn.setLockdown(true);
+ final List<Range<Integer>> ranges =
+ intRangesPrimaryExcludingUids(Collections.EMPTY_LIST /* excludedeUids */);
+ mCm.setRequireVpnForUids(true /* requireVpn */, ranges);
// Bring up a network.
final LinkProperties cellLp = new LinkProperties();
@@ -10454,7 +10476,8 @@
@Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testLockdownSetFirewallUidRule() throws Exception {
- final Set<Range<Integer>> lockdownRange = UidRange.toIntRanges(Set.of(PRIMARY_UIDRANGE));
+ final List<Range<Integer>> lockdownRange =
+ intRangesPrimaryExcludingUids(Collections.EMPTY_LIST /* excludedeUids */);
// Enable Lockdown
mCm.setRequireVpnForUids(true /* requireVpn */, lockdownRange);
waitForIdle();
@@ -12819,7 +12842,8 @@
private NetworkAgentInfo fakeNai(NetworkCapabilities nc, NetworkInfo networkInfo) {
return new NetworkAgentInfo(null, new Network(NET_ID), networkInfo, new LinkProperties(),
- nc, new NetworkScore.Builder().setLegacyInt(0).build(),
+ nc, null /* localNetworkConfig */,
+ new NetworkScore.Builder().setLegacyInt(0).build(),
mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0,
INVALID_UID, TEST_LINGER_DELAY_MS, mQosCallbackTracker,
new ConnectivityService.Dependencies());
diff --git a/tests/unit/java/com/android/server/HandlerUtilsTest.kt b/tests/unit/java/com/android/server/HandlerUtilsTest.kt
new file mode 100644
index 0000000..62bb651
--- /dev/null
+++ b/tests/unit/java/com/android/server/HandlerUtilsTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.os.HandlerThread
+import com.android.server.connectivity.HandlerUtils
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val THREAD_BLOCK_TIMEOUT_MS = 1000L
+const val TEST_REPEAT_COUNT = 100
+@RunWith(DevSdkIgnoreRunner::class)
+class HandlerUtilsTest {
+ val handlerThread = HandlerThread("HandlerUtilsTestHandlerThread").also {
+ it.start()
+ }
+ val handler = handlerThread.threadHandler
+
+ @Test
+ fun testRunWithScissors() {
+ // Repeat the test a fair amount of times to ensure that it does not pass by chance.
+ repeat(TEST_REPEAT_COUNT) {
+ var result = false
+ HandlerUtils.runWithScissors(handler, {
+ assertEquals(Thread.currentThread(), handlerThread)
+ result = true
+ }, THREAD_BLOCK_TIMEOUT_MS)
+ // Assert that the result is modified on the handler thread, but can also be seen from
+ // the current thread. The assertion should pass if the runWithScissors provides
+ // the guarantee where the assignment happens-before the assertion.
+ assertTrue(result)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ handlerThread.quitSafely()
+ handlerThread.join()
+ }
+}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 71bd330..771edb2 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -35,14 +35,17 @@
import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
+
import static com.android.networkstack.apishim.api33.ConstantsShim.REGISTER_NSD_OFFLOAD_ENGINE;
import static com.android.server.NsdService.DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF;
import static com.android.server.NsdService.MdnsListener;
import static com.android.server.NsdService.NO_TRANSACTION;
import static com.android.server.NsdService.parseTypeAndSubtype;
import static com.android.testutils.ContextUtils.mockService;
+
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -220,7 +223,7 @@
anyInt(), anyString(), anyString(), anyString(), anyInt());
doReturn(false).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
doReturn(mDiscoveryManager).when(mDeps)
- .makeMdnsDiscoveryManager(any(), any(), any());
+ .makeMdnsDiscoveryManager(any(), any(), any(), any());
doReturn(mMulticastLock).when(mWifiManager).createMulticastLock(any());
doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any(), any());
doReturn(DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF).when(mDeps).getDeviceConfigInt(
diff --git a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
index e6c0c83..07883ff 100644
--- a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -372,9 +372,10 @@
caps.addCapability(0);
caps.addTransportType(transport);
NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info,
- new LinkProperties(), caps, new NetworkScore.Builder().setLegacyInt(50).build(),
- mCtx, null, new NetworkAgentConfig.Builder().build(), mConnService, mNetd,
- mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), TEST_LINGER_DELAY_MS,
+ new LinkProperties(), caps, null /* localNetworkConfiguration */,
+ new NetworkScore.Builder().setLegacyInt(50).build(), mCtx, null,
+ new NetworkAgentConfig.Builder().build(), mConnService, mNetd, mDnsResolver,
+ NetworkProvider.ID_NONE, Binder.getCallingUid(), TEST_LINGER_DELAY_MS,
mQosCallbackTracker, new ConnectivityService.Dependencies());
if (setEverValidated) {
// As tests in this class deal with testing lingering, most tests are interested
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index d674767..48cfe77 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -579,6 +579,18 @@
}
@Test
+ public void testAlwaysOnWithoutLockdown() throws Exception {
+ final Vpn vpn = createVpn(PRIMARY_USER.id);
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[1], false /* lockdown */, null /* lockdownAllowlist */));
+ verify(mConnectivityManager, never()).setRequireVpnForUids(anyBoolean(), any());
+
+ assertTrue(vpn.setAlwaysOnPackage(
+ null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */));
+ verify(mConnectivityManager, never()).setRequireVpnForUids(anyBoolean(), any());
+ }
+
+ @Test
public void testLockdownChangingPackage() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
final Range<Integer> user = PRIMARY_USER_RANGE;
@@ -724,6 +736,37 @@
}
@Test
+ public void testLockdownSystemUser() throws Exception {
+ final Vpn vpn = createVpn(SYSTEM_USER_ID);
+
+ // Uid 0 is always excluded and PKG_UIDS[1] is the uid of the VPN.
+ final List<Integer> excludedUids = new ArrayList<>(List.of(0, PKG_UIDS[1]));
+ final List<Range<Integer>> ranges = makeVpnUidRange(SYSTEM_USER_ID, excludedUids);
+
+ // Set always-on with lockdown.
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[1], true /* lockdown */, null /* lockdownAllowlist */));
+ verify(mConnectivityManager).setRequireVpnForUids(true, ranges);
+
+ // Disable always-on with lockdown.
+ assertTrue(vpn.setAlwaysOnPackage(
+ null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */));
+ verify(mConnectivityManager).setRequireVpnForUids(false, ranges);
+
+ // Set always-on with lockdown and allow the app PKGS[2].
+ excludedUids.add(PKG_UIDS[2]);
+ final List<Range<Integer>> ranges2 = makeVpnUidRange(SYSTEM_USER_ID, excludedUids);
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[1], true /* lockdown */, Collections.singletonList(PKGS[2])));
+ verify(mConnectivityManager).setRequireVpnForUids(true, ranges2);
+
+ // Disable always-on with lockdown.
+ assertTrue(vpn.setAlwaysOnPackage(
+ null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */));
+ verify(mConnectivityManager).setRequireVpnForUids(false, ranges2);
+ }
+
+ @Test
public void testLockdownRuleRepeatability() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] {
@@ -788,6 +831,101 @@
}
@Test
+ public void testOnUserAddedAndRemoved_restrictedUser() throws Exception {
+ final InOrder order = inOrder(mMockNetworkAgent);
+ final Vpn vpn = createVpn(PRIMARY_USER.id);
+ final Set<Range<Integer>> initialRange = rangeSet(PRIMARY_USER_RANGE);
+ // Note since mVpnProfile is a Ikev2VpnProfile, this starts an IkeV2VpnRunner.
+ startLegacyVpn(vpn, mVpnProfile);
+ // Set an initial Uid range and mock the network agent
+ vpn.mNetworkCapabilities.setUids(initialRange);
+ vpn.mNetworkAgent = mMockNetworkAgent;
+
+ // Add the restricted user
+ setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
+ vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
+ // Expect restricted user range to be added to the NetworkCapabilities.
+ final Set<Range<Integer>> expectRestrictedRange =
+ rangeSet(PRIMARY_USER_RANGE, uidRangeForUser(RESTRICTED_PROFILE_A.id));
+ assertEquals(expectRestrictedRange, vpn.mNetworkCapabilities.getUids());
+ order.verify(mMockNetworkAgent).doSendNetworkCapabilities(
+ argThat(nc -> expectRestrictedRange.equals(nc.getUids())));
+
+ // Remove the restricted user
+ vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
+ // Expect restricted user range to be removed from the NetworkCapabilities.
+ assertEquals(initialRange, vpn.mNetworkCapabilities.getUids());
+ order.verify(mMockNetworkAgent).doSendNetworkCapabilities(
+ argThat(nc -> initialRange.equals(nc.getUids())));
+ }
+
+ @Test
+ public void testOnUserAddedAndRemoved_restrictedUserLockdown() throws Exception {
+ final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] {
+ new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())};
+ final Range<Integer> restrictedUserRange = uidRangeForUser(RESTRICTED_PROFILE_A.id);
+ final UidRangeParcel[] restrictedUserRangeParcel = new UidRangeParcel[] {
+ new UidRangeParcel(restrictedUserRange.getLower(), restrictedUserRange.getUpper())};
+ final Vpn vpn = createVpn(PRIMARY_USER.id);
+
+ // Set lockdown calls setRequireVpnForUids
+ vpn.setLockdown(true);
+ verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(primaryUserRangeParcel));
+
+ // Add the restricted user
+ doReturn(true).when(mUserManager).canHaveRestrictedProfile();
+ setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
+ vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
+
+ // Expect restricted user range to be added.
+ verify(mConnectivityManager).setRequireVpnForUids(true,
+ toRanges(restrictedUserRangeParcel));
+
+ // Mark as partial indicates that the user is removed, mUserManager.getAliveUsers() does not
+ // return the restricted user but it is still returned in mUserManager.getUserInfo().
+ RESTRICTED_PROFILE_A.partial = true;
+ // Remove the restricted user
+ vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
+ verify(mConnectivityManager).setRequireVpnForUids(false,
+ toRanges(restrictedUserRangeParcel));
+ // reset to avoid affecting other tests since RESTRICTED_PROFILE_A is static.
+ RESTRICTED_PROFILE_A.partial = false;
+ }
+
+ @Test
+ public void testOnUserAddedAndRemoved_restrictedUserAlwaysOn() throws Exception {
+ final Vpn vpn = createVpn(PRIMARY_USER.id);
+
+ // setAlwaysOnPackage() calls setRequireVpnForUids()
+ assertTrue(vpn.setAlwaysOnPackage(
+ PKGS[0], true /* lockdown */, null /* lockdownAllowlist */));
+ final List<Integer> excludedUids = List.of(PKG_UIDS[0]);
+ final List<Range<Integer>> primaryRanges =
+ makeVpnUidRange(PRIMARY_USER.id, excludedUids);
+ verify(mConnectivityManager).setRequireVpnForUids(true, primaryRanges);
+
+ // Add the restricted user
+ doReturn(true).when(mUserManager).canHaveRestrictedProfile();
+ setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
+ vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
+
+ final List<Range<Integer>> restrictedRanges =
+ makeVpnUidRange(RESTRICTED_PROFILE_A.id, excludedUids);
+ // Expect restricted user range to be added.
+ verify(mConnectivityManager).setRequireVpnForUids(true, restrictedRanges);
+
+ // Mark as partial indicates that the user is removed, mUserManager.getAliveUsers() does not
+ // return the restricted user but it is still returned in mUserManager.getUserInfo().
+ RESTRICTED_PROFILE_A.partial = true;
+ // Remove the restricted user
+ vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
+ verify(mConnectivityManager).setRequireVpnForUids(false, restrictedRanges);
+
+ // reset to avoid affecting other tests since RESTRICTED_PROFILE_A is static.
+ RESTRICTED_PROFILE_A.partial = false;
+ }
+
+ @Test
public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller()
throws Exception {
mTestDeps.mIgnoreCallingUidChecks = false;
@@ -1002,12 +1140,12 @@
// List in keystore is not changed, but UID for the removed packages is no longer exempted.
assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
- assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids),
+ assertEquals(makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids),
vpn.mNetworkCapabilities.getUids());
ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
- assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids),
+ assertEquals(makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids),
ncCaptor.getValue().getUids());
reset(mMockNetworkAgent);
@@ -1019,26 +1157,28 @@
// List in keystore is not changed and the uid list should be updated in the net cap.
assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
- assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids),
+ assertEquals(makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids),
vpn.mNetworkCapabilities.getUids());
verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
- assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids),
+ assertEquals(makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids),
ncCaptor.getValue().getUids());
}
- private Set<Range<Integer>> makeVpnUidRange(int userId, List<Integer> excludedList) {
+ private List<Range<Integer>> makeVpnUidRange(int userId, List<Integer> excludedAppIdList) {
final SortedSet<Integer> list = new TreeSet<>();
final int userBase = userId * UserHandle.PER_USER_RANGE;
- for (int uid : excludedList) {
- final int applicationUid = UserHandle.getUid(userId, uid);
- list.add(applicationUid);
- list.add(Process.toSdkSandboxUid(applicationUid)); // Add Sdk Sandbox UID
+ for (int appId : excludedAppIdList) {
+ final int uid = UserHandle.getUid(userId, appId);
+ list.add(uid);
+ if (Process.isApplicationUid(uid)) {
+ list.add(Process.toSdkSandboxUid(uid)); // Add Sdk Sandbox UID
+ }
}
final int minUid = userBase;
final int maxUid = userBase + UserHandle.PER_USER_RANGE - 1;
- final Set<Range<Integer>> ranges = new ArraySet<>();
+ final List<Range<Integer>> ranges = new ArrayList<>();
// Iterate the list to create the ranges between each uid.
int start = minUid;
@@ -1059,6 +1199,10 @@
return ranges;
}
+ private Set<Range<Integer>> makeVpnUidRangeSet(int userId, List<Integer> excludedAppIdList) {
+ return new ArraySet<>(makeVpnUidRange(userId, excludedAppIdList));
+ }
+
@Test
public void testSetAndGetAppExclusionListRestrictedUser() throws Exception {
final Vpn vpn = prepareVpnForVerifyAppExclusionList();
@@ -3004,8 +3148,15 @@
profile.mppe = useMppe;
doReturn(new Network[] { new Network(101) }).when(mConnectivityManager).getAllNetworks();
- doReturn(new Network(102)).when(mConnectivityManager).registerNetworkAgent(any(), any(),
- any(), any(), any(), any(), anyInt());
+ doReturn(new Network(102)).when(mConnectivityManager).registerNetworkAgent(
+ any(), // INetworkAgent
+ any(), // NetworkInfo
+ any(), // LinkProperties
+ any(), // NetworkCapabilities
+ any(), // LocalNetworkConfig
+ any(), // NetworkScore
+ any(), // NetworkAgentConfig
+ anyInt()); // provider ID
final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile);
final TestDeps deps = (TestDeps) vpn.mDeps;
@@ -3027,8 +3178,15 @@
assertEquals("nomppe", mtpdArgs[argsPrefix.length]);
}
- verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
- any(), any(), any(), any(), anyInt());
+ verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(
+ any(), // INetworkAgent
+ any(), // NetworkInfo
+ any(), // LinkProperties
+ any(), // NetworkCapabilities
+ any(), // LocalNetworkConfig
+ any(), // NetworkScore
+ any(), // NetworkAgentConfig
+ anyInt()); // provider ID
}, () -> { // Cleanup
vpn.mVpnRunner.exitVpnRunner();
deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier
@@ -3053,7 +3211,7 @@
.thenReturn(new Network[] { new Network(101) });
when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(),
- any(), any(), anyInt())).thenAnswer(invocation -> {
+ any(), any(), any(), anyInt())).thenAnswer(invocation -> {
// The runner has registered an agent and is now ready.
legacyRunnerReady.open();
return new Network(102);
@@ -3079,7 +3237,7 @@
ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
- lpCaptor.capture(), ncCaptor.capture(), any(), any(), anyInt());
+ lpCaptor.capture(), ncCaptor.capture(), any(), any(), any(), anyInt());
// In this test the expected address is always v4 so /32.
// Note that the interface needs to be specified because RouteInfo objects stored in
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index 8eace1c..a86f923 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -153,10 +153,10 @@
thread.start()
doReturn(TEST_HOSTNAME).`when`(mockDeps).generateHostname()
doReturn(mockInterfaceAdvertiser1).`when`(mockDeps).makeAdvertiser(eq(mockSocket1),
- any(), any(), any(), any(), eq(TEST_HOSTNAME), any()
+ any(), any(), any(), any(), eq(TEST_HOSTNAME), any(), any()
)
doReturn(mockInterfaceAdvertiser2).`when`(mockDeps).makeAdvertiser(eq(mockSocket2),
- any(), any(), any(), any(), eq(TEST_HOSTNAME), any()
+ any(), any(), any(), any(), eq(TEST_HOSTNAME), any(), any()
)
doReturn(true).`when`(mockInterfaceAdvertiser1).isProbing(anyInt())
doReturn(true).`when`(mockInterfaceAdvertiser2).isProbing(anyInt())
@@ -202,6 +202,7 @@
any(),
intAdvCbCaptor.capture(),
eq(TEST_HOSTNAME),
+ any(),
any()
)
@@ -259,10 +260,10 @@
val intAdvCbCaptor1 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
val intAdvCbCaptor2 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
- eq(thread.looper), any(), intAdvCbCaptor1.capture(), eq(TEST_HOSTNAME), any()
+ eq(thread.looper), any(), intAdvCbCaptor1.capture(), eq(TEST_HOSTNAME), any(), any()
)
verify(mockDeps).makeAdvertiser(eq(mockSocket2), eq(listOf(TEST_LINKADDR)),
- eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any()
+ eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any(), any()
)
verify(mockInterfaceAdvertiser1).addService(
anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE))
@@ -367,7 +368,7 @@
val intAdvCbCaptor = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
- eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any()
+ eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any(), any()
)
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
argThat { it.matches(SERVICE_1) }, eq(null))
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index e869b91..331a5b6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -106,7 +106,7 @@
doReturn(thread.getLooper()).when(socketClient).getLooper();
doReturn(true).when(socketClient).supportsRequestingSpecificNetworks();
discoveryManager = new MdnsDiscoveryManager(executorProvider, socketClient,
- sharedLog) {
+ sharedLog, MdnsFeatureFlags.newBuilder().build()) {
@Override
MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
@NonNull SocketKey socketKey) {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index a67dc5e..db41a6a 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -77,6 +77,7 @@
private val announcer = mock(MdnsAnnouncer::class.java)
private val prober = mock(MdnsProber::class.java)
private val sharedlog = SharedLog("MdnsInterfaceAdvertiserTest")
+ private val flags = MdnsFeatureFlags.newBuilder().build()
@Suppress("UNCHECKED_CAST")
private val probeCbCaptor = ArgumentCaptor.forClass(PacketRepeaterCallback::class.java)
as ArgumentCaptor<PacketRepeaterCallback<ProbingInfo>>
@@ -99,15 +100,14 @@
cb,
deps,
TEST_HOSTNAME,
- sharedlog
+ sharedlog,
+ flags
)
}
@Before
fun setUp() {
- doReturn(repository).`when`(deps).makeRecordRepository(any(),
- eq(TEST_HOSTNAME)
- )
+ doReturn(repository).`when`(deps).makeRecordRepository(any(), eq(TEST_HOSTNAME), any())
doReturn(replySender).`when`(deps).makeReplySender(anyString(), any(), any(), any(), any())
doReturn(announcer).`when`(deps).makeMdnsAnnouncer(anyString(), any(), any(), any(), any())
doReturn(prober).`when`(deps).makeMdnsProber(anyString(), any(), any(), any(), any())
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index c9b502e..f26f7e1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -78,6 +78,7 @@
override fun getInterfaceInetAddresses(iface: NetworkInterface) =
Collections.enumeration(TEST_ADDRESSES.map { it.address })
}
+ private val flags = MdnsFeatureFlags.newBuilder().build()
@Before
fun setUp() {
@@ -92,7 +93,7 @@
@Test
fun testAddServiceAndProbe() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
assertEquals(0, repository.servicesCount)
assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
null /* subtype */))
@@ -127,7 +128,7 @@
@Test
fun testAddAndConflicts() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
assertFailsWith(NameConflictException::class) {
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* subtype */)
@@ -139,7 +140,7 @@
@Test
fun testInvalidReuseOfServiceId() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
assertFailsWith(IllegalArgumentException::class) {
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2, null /* subtype */)
@@ -148,7 +149,7 @@
@Test
fun testHasActiveService() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
@@ -165,7 +166,7 @@
@Test
fun testExitAnnouncements() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
@@ -195,7 +196,7 @@
@Test
fun testExitAnnouncements_WithSubtype() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, TEST_SUBTYPE)
repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
@@ -231,7 +232,7 @@
@Test
fun testExitingServiceReAdded() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
repository.exitService(TEST_SERVICE_ID_1)
@@ -246,7 +247,7 @@
@Test
fun testOnProbingSucceeded() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
TEST_SUBTYPE)
repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
@@ -371,7 +372,7 @@
@Test
fun testGetOffloadPacket() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
val serviceType = arrayOf("_testservice", "_tcp", "local")
@@ -433,7 +434,7 @@
@Test
fun testGetReplyCaseInsensitive() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val questionsCaseInSensitive =
listOf(MdnsPointerRecord(arrayOf("_TESTSERVICE", "_TCP", "local"),
@@ -463,7 +464,7 @@
}
private fun doGetReplyTest(subtype: String?) {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, subtype)
val queriedName = if (subtype == null) arrayOf("_testservice", "_tcp", "local")
else arrayOf(subtype, "_sub", "_testservice", "_tcp", "local")
@@ -551,7 +552,7 @@
@Test
fun testGetConflictingServices() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
@@ -579,7 +580,7 @@
@Test
fun testGetConflictingServicesCaseInsensitive() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
@@ -607,7 +608,7 @@
@Test
fun testGetConflictingServices_IdenticalService() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
@@ -636,7 +637,7 @@
@Test
fun testGetConflictingServicesCaseInsensitive_IdenticalService() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
@@ -665,7 +666,7 @@
@Test
fun testGetServiceRepliedRequestsCount() {
- val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
// Verify that there is no packet replied.
assertEquals(MdnsConstants.NO_PACKET,
@@ -690,6 +691,68 @@
assertEquals(MdnsConstants.NO_PACKET,
repository.getServiceRepliedRequestsCount(TEST_SERVICE_ID_2))
}
+
+ @Test
+ fun testIncludeInetAddressRecordsInProbing() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME,
+ MdnsFeatureFlags.newBuilder().setIncludeInetAddressRecordsInProbing(true).build())
+ repository.updateAddresses(TEST_ADDRESSES)
+ assertEquals(0, repository.servicesCount)
+ assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
+ null /* subtype */))
+ assertEquals(1, repository.servicesCount)
+
+ val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
+ assertNotNull(probingInfo)
+ assertTrue(repository.isProbing(TEST_SERVICE_ID_1))
+
+ assertEquals(TEST_SERVICE_ID_1, probingInfo.serviceId)
+ val packet = probingInfo.getPacket(0)
+
+ assertEquals(MdnsConstants.FLAGS_QUERY, packet.flags)
+ assertEquals(0, packet.answers.size)
+ assertEquals(0, packet.additionalRecords.size)
+
+ assertEquals(2, packet.questions.size)
+ val expectedName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+ assertContentEquals(listOf(
+ MdnsAnyRecord(expectedName, false /* unicast */),
+ MdnsAnyRecord(TEST_HOSTNAME, false /* unicast */),
+ ), packet.questions)
+
+ assertEquals(4, packet.authorityRecords.size)
+ assertContentEquals(listOf(
+ MdnsServiceRecord(
+ expectedName,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120_000L /* ttlMillis */,
+ 0 /* servicePriority */,
+ 0 /* serviceWeight */,
+ TEST_PORT,
+ TEST_HOSTNAME),
+ MdnsInetAddressRecord(
+ TEST_HOSTNAME,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120_000L /* ttlMillis */,
+ TEST_ADDRESSES[0].address),
+ MdnsInetAddressRecord(
+ TEST_HOSTNAME,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120_000L /* ttlMillis */,
+ TEST_ADDRESSES[1].address),
+ MdnsInetAddressRecord(
+ TEST_HOSTNAME,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120_000L /* ttlMillis */,
+ TEST_ADDRESSES[2].address)
+ ), packet.authorityRecords)
+
+ assertContentEquals(intArrayOf(TEST_SERVICE_ID_1), repository.clearServices())
+ }
}
private fun MdnsRecordRepository.initWithService(
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
index 1b6f120..2b3b834 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
@@ -50,9 +50,6 @@
private val handler by lazy {
Handler(thread.looper)
}
- private val serviceCache by lazy {
- MdnsServiceCache(thread.looper)
- }
@Before
fun setUp() {
@@ -64,6 +61,11 @@
thread.quitSafely()
}
+ private fun makeFlags(isExpiredServicesRemovalEnabled: Boolean = false) =
+ MdnsFeatureFlags.Builder()
+ .setIsExpiredServicesRemovalEnabled(isExpiredServicesRemovalEnabled)
+ .build()
+
private fun <T> runningOnHandlerAndReturn(functor: (() -> T)): T {
val future = CompletableFuture<T>()
handler.post {
@@ -72,36 +74,51 @@
return future.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
- private fun addOrUpdateService(cacheKey: CacheKey, service: MdnsResponse): Unit =
- runningOnHandlerAndReturn { serviceCache.addOrUpdateService(cacheKey, service) }
+ private fun addOrUpdateService(
+ serviceCache: MdnsServiceCache,
+ cacheKey: CacheKey,
+ service: MdnsResponse
+ ): Unit = runningOnHandlerAndReturn { serviceCache.addOrUpdateService(cacheKey, service) }
- private fun removeService(serviceName: String, cacheKey: CacheKey): Unit =
- runningOnHandlerAndReturn { serviceCache.removeService(serviceName, cacheKey) }
+ private fun removeService(
+ serviceCache: MdnsServiceCache,
+ serviceName: String,
+ cacheKey: CacheKey
+ ): Unit = runningOnHandlerAndReturn { serviceCache.removeService(serviceName, cacheKey) }
- private fun getService(serviceName: String, cacheKey: CacheKey): MdnsResponse? =
- runningOnHandlerAndReturn { serviceCache.getCachedService(serviceName, cacheKey) }
+ private fun getService(
+ serviceCache: MdnsServiceCache,
+ serviceName: String,
+ cacheKey: CacheKey,
+ ): MdnsResponse? = runningOnHandlerAndReturn {
+ serviceCache.getCachedService(serviceName, cacheKey)
+ }
- private fun getServices(cacheKey: CacheKey): List<MdnsResponse> =
- runningOnHandlerAndReturn { serviceCache.getCachedServices(cacheKey) }
+ private fun getServices(
+ serviceCache: MdnsServiceCache,
+ cacheKey: CacheKey,
+ ): List<MdnsResponse> = runningOnHandlerAndReturn { serviceCache.getCachedServices(cacheKey) }
@Test
fun testAddAndRemoveService() {
- addOrUpdateService(cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
- var response = getService(SERVICE_NAME_1, cacheKey1)
+ val serviceCache = MdnsServiceCache(thread.looper, makeFlags())
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
+ var response = getService(serviceCache, SERVICE_NAME_1, cacheKey1)
assertNotNull(response)
assertEquals(SERVICE_NAME_1, response.serviceInstanceName)
- removeService(SERVICE_NAME_1, cacheKey1)
- response = getService(SERVICE_NAME_1, cacheKey1)
+ removeService(serviceCache, SERVICE_NAME_1, cacheKey1)
+ response = getService(serviceCache, SERVICE_NAME_1, cacheKey1)
assertNull(response)
}
@Test
fun testGetCachedServices_multipleServiceTypes() {
- addOrUpdateService(cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
- addOrUpdateService(cacheKey1, createResponse(SERVICE_NAME_2, SERVICE_TYPE_1))
- addOrUpdateService(cacheKey2, createResponse(SERVICE_NAME_2, SERVICE_TYPE_2))
+ val serviceCache = MdnsServiceCache(thread.looper, makeFlags())
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_2, SERVICE_TYPE_1))
+ addOrUpdateService(serviceCache, cacheKey2, createResponse(SERVICE_NAME_2, SERVICE_TYPE_2))
- val responses1 = getServices(cacheKey1)
+ val responses1 = getServices(serviceCache, cacheKey1)
assertEquals(2, responses1.size)
assertTrue(responses1.stream().anyMatch { response ->
response.serviceInstanceName == SERVICE_NAME_1
@@ -109,19 +126,19 @@
assertTrue(responses1.any { response ->
response.serviceInstanceName == SERVICE_NAME_2
})
- val responses2 = getServices(cacheKey2)
+ val responses2 = getServices(serviceCache, cacheKey2)
assertEquals(1, responses2.size)
assertTrue(responses2.any { response ->
response.serviceInstanceName == SERVICE_NAME_2
})
- removeService(SERVICE_NAME_2, cacheKey1)
- val responses3 = getServices(cacheKey1)
+ removeService(serviceCache, SERVICE_NAME_2, cacheKey1)
+ val responses3 = getServices(serviceCache, cacheKey1)
assertEquals(1, responses3.size)
assertTrue(responses3.any { response ->
response.serviceInstanceName == SERVICE_NAME_1
})
- val responses4 = getServices(cacheKey2)
+ val responses4 = getServices(serviceCache, cacheKey2)
assertEquals(1, responses4.size)
assertTrue(responses4.any { response ->
response.serviceInstanceName == SERVICE_NAME_2
@@ -129,6 +146,6 @@
}
private fun createResponse(serviceInstanceName: String, serviceType: String) = MdnsResponse(
- 0 /* now */, "$serviceInstanceName.$serviceType".split(".").toTypedArray(),
+ 0 /* now */, "$serviceInstanceName.$serviceType".split(".").toTypedArray(),
socketKey.interfaceIndex, socketKey.network)
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index fde5abd..ce154dd 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -193,7 +193,8 @@
thread = new HandlerThread("MdnsServiceTypeClientTests");
thread.start();
handler = new Handler(thread.getLooper());
- serviceCache = new MdnsServiceCache(thread.getLooper());
+ serviceCache = new MdnsServiceCache(
+ thread.getLooper(), MdnsFeatureFlags.newBuilder().build());
doAnswer(inv -> {
latestDelayMs = 0;
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
index 86426c2..6220e76 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
@@ -16,6 +16,7 @@
package com.android.server
+import android.net.LocalNetworkConfig
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
import android.net.NetworkCapabilities.TRANSPORT_WIFI
@@ -45,8 +46,9 @@
.build()
val keepConnectedAgent = Agent(nc = nc, score = FromS(NetworkScore.Builder()
.setKeepConnectedReason(KEEP_CONNECTED_DOWNSTREAM_NETWORK)
- .build()))
- val dontKeepConnectedAgent = Agent(nc = nc)
+ .build()),
+ lnc = LocalNetworkConfig.Builder().build())
+ val dontKeepConnectedAgent = Agent(nc = nc, lnc = LocalNetworkConfig.Builder().build())
doTestKeepConnected(keepConnectedAgent, dontKeepConnectedAgent)
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt
new file mode 100644
index 0000000..cfc3a3d
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.content.pm.PackageManager.FEATURE_LEANBACK
+import android.net.INetd
+import android.net.LocalNetworkConfig
+import android.net.NativeNetworkConfig
+import android.net.NativeNetworkType
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
+import android.net.NetworkRequest
+import android.net.NetworkScore
+import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.net.VpnManager
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.timeout
+import kotlin.test.assertFailsWith
+
+private const val TIMEOUT_MS = 2_000L
+private const val NO_CALLBACK_TIMEOUT_MS = 200L
+
+private fun keepConnectedScore() =
+ FromS(NetworkScore.Builder().setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST).build())
+
+private fun defaultLnc() = LocalNetworkConfig.Builder().build()
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.R)
+class CSLocalAgentCreationTests(
+ private val sdkLevel: Int,
+ private val isTv: Boolean,
+ private val addLocalNetCapToRequest: Boolean
+) : CSTest() {
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun arguments() = listOf(
+ arrayOf(VERSION_V, false /* isTv */, true /* addLocalNetCapToRequest */),
+ arrayOf(VERSION_V, false /* isTv */, false /* addLocalNetCapToRequest */),
+ arrayOf(VERSION_V, true /* isTv */, true /* addLocalNetCapToRequest */),
+ arrayOf(VERSION_V, true /* isTv */, false /* addLocalNetCapToRequest */),
+ arrayOf(VERSION_U, false /* isTv */, true /* addLocalNetCapToRequest */),
+ arrayOf(VERSION_U, false /* isTv */, false /* addLocalNetCapToRequest */),
+ arrayOf(VERSION_U, true /* isTv */, true /* addLocalNetCapToRequest */),
+ arrayOf(VERSION_U, true /* isTv */, false /* addLocalNetCapToRequest */),
+ arrayOf(VERSION_T, false /* isTv */, false /* addLocalNetCapToRequest */),
+ arrayOf(VERSION_T, true /* isTv */, false /* addLocalNetCapToRequest */),
+ )
+ }
+
+ private fun makeNativeNetworkConfigLocal(netId: Int, permission: Int) =
+ NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL_LOCAL, permission,
+ false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
+
+ @Test
+ fun testLocalAgents() {
+ val netdInOrder = inOrder(netd)
+ deps.setBuildSdk(sdkLevel)
+ doReturn(isTv).`when`(packageManager).hasSystemFeature(FEATURE_LEANBACK)
+ val allNetworksCb = TestableNetworkCallback()
+ val request = NetworkRequest.Builder()
+ if (addLocalNetCapToRequest) {
+ request.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ }
+ cm.registerNetworkCallback(request.build(), allNetworksCb)
+ val ncTemplate = NetworkCapabilities.Builder().run {
+ addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ }.build()
+ val localAgent = if (sdkLevel >= VERSION_V || sdkLevel == VERSION_U && isTv) {
+ Agent(nc = ncTemplate, score = keepConnectedScore(), lnc = defaultLnc())
+ } else {
+ assertFailsWith<IllegalArgumentException> { Agent(nc = ncTemplate, lnc = defaultLnc()) }
+ netdInOrder.verify(netd, never()).networkCreate(any())
+ return
+ }
+ localAgent.connect()
+ netdInOrder.verify(netd).networkCreate(
+ makeNativeNetworkConfigLocal(localAgent.network.netId, INetd.PERMISSION_NONE))
+ if (addLocalNetCapToRequest) {
+ assertEquals(localAgent.network, allNetworksCb.expect<Available>().network)
+ } else {
+ allNetworksCb.assertNoCallback(NO_CALLBACK_TIMEOUT_MS)
+ }
+ cm.unregisterNetworkCallback(allNetworksCb)
+ localAgent.disconnect()
+ netdInOrder.verify(netd, timeout(TIMEOUT_MS)).networkDestroy(localAgent.network.netId)
+ }
+
+ @Test
+ fun testBadAgents() {
+ assertFailsWith<IllegalArgumentException> {
+ Agent(nc = NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ lnc = null)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ Agent(nc = NetworkCapabilities.Builder().build(),
+ lnc = LocalNetworkConfig.Builder().build())
+ }
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
new file mode 100644
index 0000000..bd3efa9
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.LocalNetworkConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.RouteInfo
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFailsWith
+
+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 lp(iface: String) = LinkProperties().apply {
+ interfaceName = iface
+ addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+ addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class CSLocalAgentTests : CSTest() {
+ @Test
+ fun testBadAgents() {
+ assertFailsWith<IllegalArgumentException> {
+ Agent(nc = NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ lnc = null)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ Agent(nc = NetworkCapabilities.Builder().build(),
+ lnc = LocalNetworkConfig.Builder().build())
+ }
+ }
+
+ @Test
+ fun testUpdateLocalAgentConfig() {
+ deps.setBuildSdk(VERSION_V)
+
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ cb)
+
+ // Set up a local agent that should forward its traffic to the best DUN upstream.
+ val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK),
+ lp = lp("local0"),
+ lnc = LocalNetworkConfig.Builder().build(),
+ )
+ localAgent.connect()
+
+ cb.expect<Available>(localAgent.network)
+ cb.expect<CapabilitiesChanged>(localAgent.network)
+ cb.expect<LinkPropertiesChanged>(localAgent.network)
+ cb.expect<BlockedStatus>(localAgent.network)
+
+ val newLnc = LocalNetworkConfig.Builder()
+ .setUpstreamSelector(NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build())
+ .build()
+ localAgent.sendLocalNetworkConfig(newLnc)
+
+ localAgent.disconnect()
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index 1d0d5df..094ded3 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -21,6 +21,7 @@
import android.net.INetworkMonitor
import android.net.INetworkMonitorCallbacks
import android.net.LinkProperties
+import android.net.LocalNetworkConfig
import android.net.Network
import android.net.NetworkAgent
import android.net.NetworkAgentConfig
@@ -69,6 +70,7 @@
nac: NetworkAgentConfig,
val nc: NetworkCapabilities,
val lp: LinkProperties,
+ val lnc: LocalNetworkConfig?,
val score: FromS<NetworkScore>,
val provider: NetworkProvider?
) : TestableNetworkCallback.HasNetwork {
@@ -100,7 +102,7 @@
// Create the actual agent. NetworkAgent is abstract, so make an anonymous subclass.
if (deps.isAtLeastS()) {
agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
- nc, lp, score.value, nac, provider) {}
+ nc, lp, lnc, score.value, nac, provider) {}
} else {
agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
nc, lp, 50 /* score */, nac, provider) {}
@@ -165,4 +167,6 @@
agent.unregister()
cb.eventuallyExpect<Lost> { it.network == agent.network }
}
+
+ fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
}
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 2f78212..0ccbfc3 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -27,6 +27,7 @@
import android.net.INetd
import android.net.InetAddresses
import android.net.LinkProperties
+import android.net.LocalNetworkConfig
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
@@ -292,10 +293,11 @@
nc: NetworkCapabilities = defaultNc(),
nac: NetworkAgentConfig = emptyAgentConfig(nc.getLegacyType()),
lp: LinkProperties = defaultLp(),
+ lnc: LocalNetworkConfig? = null,
score: FromS<NetworkScore> = defaultScore(),
provider: NetworkProvider? = null
- ) = CSAgentWrapper(context, deps, csHandlerThread, networkStack, nac, nc, lp, score, provider)
-
+ ) = CSAgentWrapper(context, deps, csHandlerThread, networkStack,
+ nac, nc, lp, lnc, score, provider)
fun Agent(vararg transports: Int, lp: LinkProperties = defaultLp()): CSAgentWrapper {
val nc = NetworkCapabilities.Builder().apply {
transports.forEach {
diff --git a/thread/flags/thread_base.aconfig b/thread/flags/thread_base.aconfig
new file mode 100644
index 0000000..bf1f288
--- /dev/null
+++ b/thread/flags/thread_base.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.net.thread.flags"
+
+flag {
+ name: "thread_enabled"
+ namespace: "thread_network"
+ description: "Controls whether the Android Thread feature is enabled"
+ bug: "301473012"
+}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index fe189c2..9db8132 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -18,6 +18,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -30,7 +31,8 @@
* Provides the primary API for controlling all aspects of a Thread network.
*
* @hide
- */
+*/
+@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED)
@SystemApi
public class ThreadNetworkController {
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkFlags.java b/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
new file mode 100644
index 0000000..e6ab988
--- /dev/null
+++ b/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.thread;
+
+/**
+ * Container for flag constants defined in the "thread_network" namespace.
+ *
+ * @hide
+ */
+// TODO: replace this class with auto-generated "com.android.net.thread.flags.Flags" once the
+// flagging infra is fully supported for mainline modules.
+public final class ThreadNetworkFlags {
+ /** @hide */
+ public static final String FLAG_THREAD_ENABLED = "com.android.net.thread.flags.thread_enabled";
+
+ private ThreadNetworkFlags() {}
+}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkManager.java b/thread/framework/java/android/net/thread/ThreadNetworkManager.java
index 2a253a1..3e8288c 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkManager.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkManager.java
@@ -18,6 +18,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -34,6 +35,7 @@
*
* @hide
*/
+@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED)
@SystemApi
@SystemService(ThreadNetworkManager.SERVICE_NAME)
public class ThreadNetworkManager {