Merge "Add L2capPacketForwarder" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d91136a..8592af2 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -77,7 +77,7 @@
"name": "libnetworkstats_test"
},
{
- "name": "CtsTetheringTestLatestSdk",
+ "name": "CtsTetheringTest",
"options": [
{
"exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
@@ -226,6 +226,9 @@
},
{
"name": "FrameworksNetIntegrationTests"
+ },
+ {
+ "name": "CtsTetheringTest"
}
],
"postsubmit": [
@@ -257,9 +260,6 @@
// TODO: Move to presumit after meet SLO requirement.
{
"name": "NetworkStaticLibHostPythonTests"
- },
- {
- "name": "CtsTetheringTestLatestSdk"
}
],
"mainline-presubmit": [
@@ -394,7 +394,7 @@
"name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
- "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "name": "CtsTetheringTest[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"options": [
{
"exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
@@ -409,7 +409,7 @@
"keywords": ["sim"]
},
{
- "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "name": "CtsTetheringTest[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"keywords": ["sim"],
"options": [
{
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index f27b379..f7a44f1 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -933,11 +933,18 @@
// it creates a IpServer and sends out a broadcast indicating that the
// interface is "available".
if (emulateInterfaceStatusChanged) {
- // There is 1 IpServer state change event: STATE_AVAILABLE
- verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
- verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
- verify(mWifiManager).updateInterfaceIpState(
- TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+ if (!SdkLevel.isAtLeastB()) {
+ // There is 1 IpServer state change event: STATE_AVAILABLE
+ verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
+ verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
+ verify(mWifiManager).updateInterfaceIpState(
+ TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+ } else {
+ // Starting in B, ignore the interfaceStatusChanged
+ verify(mNotificationUpdater, never()).onDownstreamChanged(DOWNSTREAM_NONE);
+ verify(mWifiManager, never()).updateInterfaceIpState(
+ TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+ }
}
verifyNoMoreInteractions(mNetd);
verifyNoMoreInteractions(mWifiManager);
@@ -1934,7 +1941,6 @@
workingLocalOnlyHotspotEnrichedApBroadcast(false);
}
- // TODO: Test with and without interfaceStatusChanged().
@Test
public void failingWifiTetheringLegacyApBroadcast() throws Exception {
initTetheringOnTestThread();
@@ -1953,12 +1959,20 @@
// tethering mode is to be started.
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
+ mLooper.dispatchAll();
- // There is 1 IpServer state change event: STATE_AVAILABLE
- verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
- verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
- verify(mWifiManager).updateInterfaceIpState(
- TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+ if (!SdkLevel.isAtLeastB()) {
+ // There is 1 IpServer state change event: STATE_AVAILABLE from interfaceStatusChanged
+ verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
+ verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
+ verify(mWifiManager).updateInterfaceIpState(
+ TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+ } else {
+ // Starting in B, ignore the interfaceStatusChanged
+ verify(mNotificationUpdater, never()).onDownstreamChanged(DOWNSTREAM_NONE);
+ verify(mWifiManager, never()).updateInterfaceIpState(
+ TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+ }
verifyNoMoreInteractions(mNetd);
verifyNoMoreInteractions(mWifiManager);
}
@@ -2361,14 +2375,19 @@
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
initTetheringUpstream(upstreamState);
when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
- mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
- mLooper.dispatchAll();
- tetherState = callback.pollTetherStatesChanged();
- assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
+ mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+ mLooper.dispatchAll();
+ if (SdkLevel.isAtLeastB()) {
+ // Starting in B, ignore the interfaceStatusChanged
+ callback.assertNoStateChangeCallback();
+ }
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ mLooper.dispatchAll();
+ tetherState = callback.pollTetherStatesChanged();
+ assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIface});
callback.expectUpstreamChanged(upstreamState.network);
@@ -2459,19 +2478,25 @@
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
initTetheringUpstream(upstreamState);
when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
+
+ // Enable wifi tethering
+ mBinderCallingUid = TEST_CALLER_UID;
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, null);
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
mLooper.dispatchAll();
+ if (SdkLevel.isAtLeastB()) {
+ // Starting in B, ignore the interfaceStatusChanged
+ callback.assertNoStateChangeCallback();
+ }
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ mLooper.dispatchAll();
+ // Verify we see Available -> Tethered states
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
callback.pollTetherStatesChanged().availableList);
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
differentCallback.pollTetherStatesChanged().availableList);
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
settingsCallback.pollTetherStatesChanged().availableList);
-
- // Enable wifi tethering
- mBinderCallingUid = TEST_CALLER_UID;
- mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, null);
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
callback.pollTetherStatesChanged().tetheredList);
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
diff --git a/bpf/headers/include/bpf/BpfClassic.h b/bpf/headers/include/bpf/BpfClassic.h
index e6cef89..26d8ad5 100644
--- a/bpf/headers/include/bpf/BpfClassic.h
+++ b/bpf/headers/include/bpf/BpfClassic.h
@@ -170,6 +170,9 @@
// IPv6 extension headers (HOPOPTS, DSTOPS, FRAG) begin with a u8 nexthdr
#define BPF_LOAD_NETX_RELATIVE_V6EXTHDR_NEXTHDR BPF_LOAD_NETX_RELATIVE_L4_U8(0)
+// IPv6 MLD start with u8 type
+#define BPF_LOAD_NETX_RELATIVE_MLD_TYPE BPF_LOAD_NETX_RELATIVE_L4_U8(0)
+
// IPv6 fragment header is always exactly 8 bytes long
#define BPF_LOAD_CONSTANT_V6FRAGHDR_LEN \
BPF_STMT(BPF_LD | BPF_IMM, 8)
diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
index 8810995..6af7228 100644
--- a/bpf/netd/BpfHandler.cpp
+++ b/bpf/netd/BpfHandler.cpp
@@ -36,6 +36,8 @@
using base::unique_fd;
using base::WaitForProperty;
using bpf::getSocketCookie;
+using bpf::isAtLeastKernelVersion;
+using bpf::queryProgram;
using bpf::retrieveProgram;
using netdutils::Status;
using netdutils::statusFromErrno;
@@ -56,7 +58,7 @@
if (!cgroupProg.ok()) {
return statusFromErrno(errno, fmt::format("Failed to get program from {}", programPath));
}
- if (android::bpf::attachProgram(type, cgroupProg, cgroupFd)) {
+ if (bpf::attachProgram(type, cgroupProg, cgroupFd)) {
return statusFromErrno(errno, fmt::format("Program {} attach failed", programPath));
}
return netdutils::status::ok;
@@ -84,12 +86,12 @@
if (!modules::sdklevel::IsAtLeastT()) return Status("S- platform is unsupported");
// S requires eBPF support which was only added in 4.9, so this should be satisfied.
- if (!bpf::isAtLeastKernelVersion(4, 9, 0)) {
+ if (!isAtLeastKernelVersion(4, 9, 0)) {
return Status("kernel version < 4.9.0 is unsupported");
}
// U bumps the kernel requirement up to 4.14
- if (modules::sdklevel::IsAtLeastU() && !bpf::isAtLeastKernelVersion(4, 14, 0)) {
+ if (modules::sdklevel::IsAtLeastU() && !isAtLeastKernelVersion(4, 14, 0)) {
return Status("U+ platform with kernel version < 4.14.0 is unsupported");
}
@@ -99,12 +101,12 @@
}
// V bumps the kernel requirement up to 4.19
- if (modules::sdklevel::IsAtLeastV() && !bpf::isAtLeastKernelVersion(4, 19, 0)) {
+ if (modules::sdklevel::IsAtLeastV() && !isAtLeastKernelVersion(4, 19, 0)) {
return Status("V+ platform with kernel version < 4.19.0 is unsupported");
}
// 25Q2 bumps the kernel requirement up to 5.4
- if (isAtLeast25Q2() && !bpf::isAtLeastKernelVersion(5, 4, 0)) {
+ if (isAtLeast25Q2() && !isAtLeastKernelVersion(5, 4, 0)) {
return Status("25Q2+ platform with kernel version < 5.4.0 is unsupported");
}
@@ -123,12 +125,12 @@
// cgroup if the program is pinned properly.
// TODO: delete the if statement once all devices should support cgroup
// socket filter (ie. the minimum kernel version required is 4.14).
- if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
+ if (isAtLeastKernelVersion(4, 14, 0)) {
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_CREATE_PROG_PATH,
cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
}
- if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+ if (isAtLeastKernelVersion(5, 10, 0)) {
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_RELEASE_PROG_PATH,
cg_fd, BPF_CGROUP_INET_SOCK_RELEASE));
}
@@ -136,7 +138,7 @@
if (modules::sdklevel::IsAtLeastV()) {
// V requires 4.19+, so technically this 2nd 'if' is not required, but it
// doesn't hurt us to try to support AOSP forks that try to support older kernels.
- if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
+ if (isAtLeastKernelVersion(4, 19, 0)) {
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT4_PROG_PATH,
cg_fd, BPF_CGROUP_INET4_CONNECT));
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT6_PROG_PATH,
@@ -151,7 +153,7 @@
cg_fd, BPF_CGROUP_UDP6_SENDMSG));
}
- if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
+ if (isAtLeastKernelVersion(5, 4, 0)) {
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_GETSOCKOPT_PROG_PATH,
cg_fd, BPF_CGROUP_GETSOCKOPT));
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SETSOCKOPT_PROG_PATH,
@@ -159,7 +161,7 @@
}
}
- if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
+ if (isAtLeastKernelVersion(4, 19, 0)) {
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_BIND4_PROG_PATH,
cg_fd, BPF_CGROUP_INET4_BIND));
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_BIND6_PROG_PATH,
@@ -167,32 +169,32 @@
// This should trivially pass, since we just attached up above,
// but BPF_PROG_QUERY is only implemented on 4.19+ kernels.
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_EGRESS) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_INGRESS) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_CREATE) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET4_BIND) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_BIND) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_INET_EGRESS) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_INET_INGRESS) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_CREATE) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_INET4_BIND) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_INET6_BIND) <= 0) abort();
}
- if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
+ if (isAtLeastKernelVersion(5, 10, 0)) {
+ if (queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
}
if (modules::sdklevel::IsAtLeastV()) {
// V requires 4.19+, so technically this 2nd 'if' is not required, but it
// doesn't hurt us to try to support AOSP forks that try to support older kernels.
- if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET4_CONNECT) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_CONNECT) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_RECVMSG) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_RECVMSG) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_SENDMSG) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_SENDMSG) <= 0) abort();
+ if (isAtLeastKernelVersion(4, 19, 0)) {
+ if (queryProgram(cg_fd, BPF_CGROUP_INET4_CONNECT) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_INET6_CONNECT) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_UDP4_RECVMSG) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_UDP6_RECVMSG) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_UDP4_SENDMSG) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_UDP6_SENDMSG) <= 0) abort();
}
- if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_GETSOCKOPT) <= 0) abort();
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_SETSOCKOPT) <= 0) abort();
+ if (isAtLeastKernelVersion(5, 4, 0)) {
+ if (queryProgram(cg_fd, BPF_CGROUP_GETSOCKOPT) <= 0) abort();
+ if (queryProgram(cg_fd, BPF_CGROUP_SETSOCKOPT) <= 0) abort();
}
}
@@ -232,7 +234,7 @@
// but there could be platform provided (xt_)bpf programs that oem/vendor
// modified netd (which calls us during init) depends on...
ALOGI("Waiting for platform BPF programs");
- android::bpf::waitForProgsLoaded();
+ bpf::waitForProgsLoaded();
}
if (!mainlineNetBpfLoadDone()) {
@@ -306,7 +308,7 @@
Status BpfHandler::initMaps() {
// bpfLock() requires bpfGetFdMapId which is only available on 4.14+ kernels.
- if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
+ if (isAtLeastKernelVersion(4, 14, 0)) {
mapLockTest();
}
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 60a827b..5f279fa 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -165,3 +165,12 @@
bug: "372936361"
is_fixed_read_only: true
}
+
+flag {
+ name: "restrict_local_network"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for controlling access to the local network behind a new runtime permission. Requires ConnectivityCompatChanges.RESTRICT_LOCAL_NETWORK to enable feature."
+ bug: "365139289"
+ is_fixed_read_only: true
+}
diff --git a/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
new file mode 100644
index 0000000..95265b9
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.bpf;
+
+import com.android.net.module.util.InetAddressUtils;
+import com.android.net.module.util.Struct;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+public class LocalNetAccessKey extends Struct {
+
+ @Field(order = 0, type = Type.U32)
+ public final long lpmBitlen;
+ @Field(order = 1, type = Type.U32)
+ public final long ifIndex;
+ @Field(order = 2, type = Type.Ipv6Address)
+ public final Inet6Address remoteAddress;
+ @Field(order = 3, type = Type.U16)
+ public final int protocol;
+ @Field(order = 4, type = Type.UBE16)
+ public final int remotePort;
+
+ public LocalNetAccessKey(long lpmBitlen, long ifIndex, InetAddress remoteAddress, int protocol,
+ int remotePort) {
+ this.lpmBitlen = lpmBitlen;
+ this.ifIndex = ifIndex;
+ this.protocol = protocol;
+ this.remotePort = remotePort;
+
+ if (remoteAddress instanceof Inet4Address) {
+ this.remoteAddress = InetAddressUtils.v4MappedV6Address((Inet4Address) remoteAddress);
+ } else {
+ this.remoteAddress = (Inet6Address) remoteAddress;
+ }
+ }
+
+ public LocalNetAccessKey(long lpmBitlen, long ifIndex, Inet6Address remoteAddress, int protocol,
+ int remotePort) {
+ this.lpmBitlen = lpmBitlen;
+ this.ifIndex = ifIndex;
+ this.remoteAddress = remoteAddress;
+ this.protocol = protocol;
+ this.remotePort = remotePort;
+ }
+
+ @Override
+ public String toString() {
+ return "LocalNetAccessKey{"
+ + "lpmBitlen=" + lpmBitlen
+ + ", ifIndex=" + ifIndex
+ + ", remoteAddress=" + remoteAddress
+ + ", protocol=" + protocol
+ + ", remotePort=" + remotePort
+ + "}";
+ }
+}
diff --git a/framework/src/android/net/BpfNetMapsConstants.java b/framework/src/android/net/BpfNetMapsConstants.java
index f3773de..f1a6f00 100644
--- a/framework/src/android/net/BpfNetMapsConstants.java
+++ b/framework/src/android/net/BpfNetMapsConstants.java
@@ -60,6 +60,11 @@
"/sys/fs/bpf/netd_shared/map_netd_data_saver_enabled_map";
public static final String INGRESS_DISCARD_MAP_PATH =
"/sys/fs/bpf/netd_shared/map_netd_ingress_discard_map";
+ public static final String LOCAL_NET_ACCESS_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_local_net_access_map";
+ public static final String LOCAL_NET_BLOCKED_UID_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_local_net_blocked_uid_map";
+
public static final Struct.S32 UID_RULES_CONFIGURATION_KEY = new Struct.S32(0);
public static final Struct.S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new Struct.S32(1);
public static final Struct.S32 DATA_SAVER_ENABLED_KEY = new Struct.S32(0);
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
index 45871de..1fbb3f3 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -21,7 +21,6 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
@@ -34,9 +33,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
/** Helper class to download certificate transparency log files. */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -52,8 +51,6 @@
private final List<CompatibilityVersion> mCompatVersions = new ArrayList<>();
- private boolean started = false;
-
CertificateTransparencyDownloader(
Context context,
DataStore dataStore,
@@ -71,33 +68,8 @@
mCompatVersions.add(compatVersion);
}
- void start() {
- if (started) {
- return;
- }
- mContext.registerReceiver(
- this,
- new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
- Context.RECEIVER_EXPORTED);
- mDataStore.load();
- started = true;
-
- if (Config.DEBUG) {
- Log.d(TAG, "CertificateTransparencyDownloader started.");
- }
- }
-
- void stop() {
- if (!started) {
- return;
- }
- mContext.unregisterReceiver(this);
- mDataStore.delete();
- started = false;
-
- if (Config.DEBUG) {
- Log.d(TAG, "CertificateTransparencyDownloader stopped.");
- }
+ void clearCompatibilityVersions() {
+ mCompatVersions.clear();
}
long startPublicKeyDownload() {
@@ -226,40 +198,19 @@
return;
}
- boolean success = false;
- CTLogListUpdateState failureReason = CTLogListUpdateState.UNKNOWN_STATE;
+ LogListUpdateStatus updateStatus = mSignatureVerifier.verify(contentUri, metadataUri);
+ // TODO(b/391327942): parse file and log the timestamp of the log list
- try {
- success = mSignatureVerifier.verify(contentUri, metadataUri);
- } catch (MissingPublicKeyException e) {
- updateFailureCount();
- failureReason = CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND;
- Log.e(TAG, "No public key found for log list verification", e);
- } catch (InvalidKeyException e) {
- updateFailureCount();
- failureReason = CTLogListUpdateState.SIGNATURE_INVALID;
- Log.e(TAG, "Signature invalid for log list verification", e);
- } catch (IOException | GeneralSecurityException e) {
- Log.e(TAG, "Could not verify new log list", e);
- }
-
- if (!success) {
+ if (!updateStatus.isSignatureVerified()) {
Log.w(TAG, "Log list did not pass verification");
- // Avoid logging failure twice
- if (failureReason == CTLogListUpdateState.UNKNOWN_STATE) {
- updateFailureCount();
- failureReason = CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED;
- }
-
- mLogger.logCTLogListUpdateStateChangedEvent(
- failureReason,
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
+ mLogger.logCTLogListUpdateStateChangedEvent(updateStatus);
return;
}
+ boolean success = false;
+
try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
success = compatVersion.install(inputStream);
} catch (IOException e) {
@@ -272,45 +223,28 @@
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* value= */ 0);
mDataStore.store();
} else {
- updateFailureCount();
mLogger.logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState.VERSION_ALREADY_EXISTS,
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
+ updateStatus
+ .toBuilder()
+ .setState(CTLogListUpdateState.VERSION_ALREADY_EXISTS)
+ .build());
}
}
private void handleDownloadFailed(DownloadStatus status) {
Log.e(TAG, "Download failed with " + status);
- updateFailureCount();
- int failureCount =
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0);
-
+ LogListUpdateStatus.Builder updateStatus = LogListUpdateStatus.builder();
if (status.isHttpError()) {
- mLogger.logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState.HTTP_ERROR,
- failureCount,
- status.reason());
+ updateStatus
+ .setState(CTLogListUpdateState.HTTP_ERROR)
+ .setHttpErrorStatusCode(status.reason());
} else {
// TODO(b/384935059): handle blocked domain logging
- mLogger.logCTLogListUpdateStateChangedEventWithDownloadStatus(
- status.reason(), failureCount);
+ updateStatus.setDownloadStatus(Optional.of(status.reason()));
}
- }
- /**
- * Updates the data store with the current number of consecutive log list update failures.
- */
- private void updateFailureCount() {
- int failure_count =
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0);
- int new_failure_count = failure_count + 1;
-
- mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, new_failure_count);
- mDataStore.store();
+ mLogger.logCTLogListUpdateStateChangedEvent(updateStatus.build());
}
private long download(String url) {
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
index a8acc60..e6f1379 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
@@ -17,12 +17,12 @@
import android.annotation.RequiresApi;
import android.app.AlarmManager;
+import android.app.DownloadManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
import android.os.Build;
import android.os.ConfigUpdate;
import android.os.SystemClock;
@@ -33,28 +33,28 @@
public class CertificateTransparencyJob extends BroadcastReceiver {
private static final String TAG = "CertificateTransparencyJob";
- private static final String UPDATE_CONFIG_PERMISSION = "android.permission.UPDATE_CONFIG";
private final Context mContext;
- private final CompatibilityVersion mCompatVersion;
+ private final DataStore mDataStore;
private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
+ private final CompatibilityVersion mCompatVersion;
private final AlarmManager mAlarmManager;
private final PendingIntent mPendingIntent;
+ private boolean mScheduled = false;
private boolean mDependenciesReady = false;
/** Creates a new {@link CertificateTransparencyJob} object. */
public CertificateTransparencyJob(
- Context context, CertificateTransparencyDownloader certificateTransparencyDownloader) {
+ Context context,
+ DataStore dataStore,
+ CertificateTransparencyDownloader certificateTransparencyDownloader,
+ CompatibilityVersion compatVersion) {
mContext = context;
- mCompatVersion =
- new CompatibilityVersion(
- Config.COMPATIBILITY_VERSION,
- Config.URL_SIGNATURE,
- Config.URL_LOG_LIST,
- Config.CT_ROOT_DIRECTORY_PATH);
+ mDataStore = dataStore;
mCertificateTransparencyDownloader = certificateTransparencyDownloader;
- mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
+ mCompatVersion = compatVersion;
+
mAlarmManager = context.getSystemService(AlarmManager.class);
mPendingIntent =
PendingIntent.getBroadcast(
@@ -65,15 +65,19 @@
}
void schedule() {
- mContext.registerReceiver(
- this,
- new IntentFilter(ConfigUpdate.ACTION_UPDATE_CT_LOGS),
- Context.RECEIVER_EXPORTED);
- mAlarmManager.setInexactRepeating(
- AlarmManager.ELAPSED_REALTIME,
- SystemClock.elapsedRealtime(), // schedule first job at earliest convenient time.
- AlarmManager.INTERVAL_DAY,
- mPendingIntent);
+ if (!mScheduled) {
+ mContext.registerReceiver(
+ this,
+ new IntentFilter(ConfigUpdate.ACTION_UPDATE_CT_LOGS),
+ Context.RECEIVER_EXPORTED);
+ mAlarmManager.setInexactRepeating(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock
+ .elapsedRealtime(), // schedule first job at earliest convenient time.
+ AlarmManager.INTERVAL_DAY,
+ mPendingIntent);
+ }
+ mScheduled = true;
if (Config.DEBUG) {
Log.d(TAG, "CertificateTransparencyJob scheduled.");
@@ -81,12 +85,19 @@
}
void cancel() {
- mContext.unregisterReceiver(this);
- mAlarmManager.cancel(mPendingIntent);
- mCertificateTransparencyDownloader.stop();
- mCompatVersion.delete();
+ if (mScheduled) {
+ mContext.unregisterReceiver(this);
+ mAlarmManager.cancel(mPendingIntent);
+ }
+ mScheduled = false;
+
+ if (mDependenciesReady) {
+ stopDependencies();
+ }
mDependenciesReady = false;
+ mCompatVersion.delete();
+
if (Config.DEBUG) {
Log.d(TAG, "CertificateTransparencyJob canceled.");
}
@@ -98,16 +109,11 @@
Log.w(TAG, "Received unexpected broadcast with action " + intent);
return;
}
- if (context.checkCallingOrSelfPermission(UPDATE_CONFIG_PERMISSION)
- != PackageManager.PERMISSION_GRANTED) {
- Log.e(TAG, "Caller does not have UPDATE_CONFIG permission.");
- return;
- }
if (Config.DEBUG) {
Log.d(TAG, "Starting CT daily job.");
}
if (!mDependenciesReady) {
- mCertificateTransparencyDownloader.start();
+ startDependencies();
mDependenciesReady = true;
}
@@ -117,4 +123,27 @@
Log.d(TAG, "Public key download started successfully.");
}
}
+
+ private void startDependencies() {
+ mDataStore.load();
+ mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
+ mContext.registerReceiver(
+ mCertificateTransparencyDownloader,
+ new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
+ Context.RECEIVER_EXPORTED);
+
+ if (Config.DEBUG) {
+ Log.d(TAG, "CertificateTransparencyJob dependencies ready.");
+ }
+ }
+
+ private void stopDependencies() {
+ mContext.unregisterReceiver(mCertificateTransparencyDownloader);
+ mCertificateTransparencyDownloader.clearCompatibilityVersions();
+ mDataStore.delete();
+
+ if (Config.DEBUG) {
+ Log.d(TAG, "CertificateTransparencyJob dependencies stopped.");
+ }
+ }
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java
index 8d53983..967a04b 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLogger.java
@@ -20,31 +20,12 @@
public interface CertificateTransparencyLogger {
/**
- * Logs a CTLogListUpdateStateChanged event to statsd, when failure is from DownloadManager.
+ * Logs a CTLogListUpdateStateChanged event to statsd.
*
- * @param downloadStatus DownloadManager failure status why the log list wasn't updated
- * @param failureCount number of consecutive log list update failures
+ * @param updateStatus status object containing details from this update event (e.g. log list
+ * signature, log list timestamp, failure reason if applicable)
*/
- void logCTLogListUpdateStateChangedEventWithDownloadStatus(
- int downloadStatus, int failureCount);
-
- /**
- * Logs a CTLogListUpdateStateChanged event to statsd without a HTTP error status code.
- *
- * @param failureReason reason why the log list wasn't updated
- * @param failureCount number of consecutive log list update failures
- */
- void logCTLogListUpdateStateChangedEvent(CTLogListUpdateState failureReason, int failureCount);
-
- /**
- * Logs a CTLogListUpdateStateChanged event to statsd with an HTTP error status code.
- *
- * @param failureReason reason why the log list wasn't updated (e.g. DownloadManager failures)
- * @param failureCount number of consecutive log list update failures
- * @param httpErrorStatusCode if relevant, the HTTP error status code from DownloadManager
- */
- void logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState failureReason, int failureCount, int httpErrorStatusCode);
+ void logCTLogListUpdateStateChangedEvent(LogListUpdateStatus updateStatus);
/**
* Intermediate enum for use with CertificateTransparencyStatsLog.
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java
index 6accdf8..9c3210d 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyLoggerImpl.java
@@ -35,42 +35,56 @@
/** Implementation for logging to statsd for Certificate Transparency. */
class CertificateTransparencyLoggerImpl implements CertificateTransparencyLogger {
- @Override
- public void logCTLogListUpdateStateChangedEventWithDownloadStatus(
- int downloadStatus, int failureCount) {
- logCTLogListUpdateStateChangedEvent(
- downloadStatusToFailureReason(downloadStatus),
- failureCount,
- /* httpErrorStatusCode= */ 0);
+ private final DataStore mDataStore;
+
+ CertificateTransparencyLoggerImpl(DataStore dataStore) {
+ mDataStore = dataStore;
}
@Override
- public void logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState failureReason, int failureCount) {
- logCTLogListUpdateStateChangedEvent(
- localEnumToStatsLogEnum(failureReason),
- failureCount,
- /* httpErrorStatusCode= */ 0);
- }
+ public void logCTLogListUpdateStateChangedEvent(LogListUpdateStatus updateStatus) {
+ int updateState =
+ updateStatus
+ .downloadStatus()
+ .map(s -> downloadStatusToFailureReason(s))
+ .orElseGet(() -> localEnumToStatsLogEnum(updateStatus.state()));
+ int failureCount =
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0);
- @Override
- public void logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState failureReason, int failureCount, int httpErrorStatusCode) {
logCTLogListUpdateStateChangedEvent(
- localEnumToStatsLogEnum(failureReason), failureCount, httpErrorStatusCode);
+ updateState,
+ failureCount,
+ updateStatus.httpErrorStatusCode(),
+ updateStatus.signature());
}
private void logCTLogListUpdateStateChangedEvent(
- int failureReason, int failureCount, int httpErrorStatusCode) {
+ int updateState, int failureCount, int httpErrorStatusCode, String signature) {
+ updateFailureCount();
+
CertificateTransparencyStatsLog.write(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_STATE_CHANGED,
- failureReason,
+ updateState,
failureCount,
httpErrorStatusCode,
- /* signature= */ "",
+ signature,
/* logListTimestampMs= */ 0);
}
+ /**
+ * Updates the data store with the current number of consecutive log list update failures.
+ */
+ private void updateFailureCount() {
+ int failure_count =
+ mDataStore.getPropertyInt(
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0);
+ int new_failure_count = failure_count + 1;
+
+ mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, new_failure_count);
+ mDataStore.store();
+ }
+
/** Converts DownloadStatus reason into failure reason to log. */
private int downloadStatusToFailureReason(int downloadStatusReason) {
switch (downloadStatusReason) {
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
index ed98056..a71ff7c 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -41,8 +41,6 @@
private final CertificateTransparencyJob mCertificateTransparencyJob;
- private boolean started = false;
-
/**
* @return true if the CertificateTransparency service is enabled.
*/
@@ -53,16 +51,22 @@
/** Creates a new {@link CertificateTransparencyService} object. */
public CertificateTransparencyService(Context context) {
DataStore dataStore = new DataStore(Config.PREFERENCES_FILE);
- DownloadHelper downloadHelper = new DownloadHelper(context);
- SignatureVerifier signatureVerifier = new SignatureVerifier(context);
- CertificateTransparencyDownloader downloader =
- new CertificateTransparencyDownloader(
+
+ mCertificateTransparencyJob =
+ new CertificateTransparencyJob(
context,
dataStore,
- downloadHelper,
- signatureVerifier,
- new CertificateTransparencyLoggerImpl());
- mCertificateTransparencyJob = new CertificateTransparencyJob(context, downloader);
+ new CertificateTransparencyDownloader(
+ context,
+ dataStore,
+ new DownloadHelper(context),
+ new SignatureVerifier(context),
+ new CertificateTransparencyLoggerImpl(dataStore)),
+ new CompatibilityVersion(
+ Config.COMPATIBILITY_VERSION,
+ Config.URL_SIGNATURE,
+ Config.URL_LOG_LIST,
+ Config.CT_ROOT_DIRECTORY_PATH));
}
/**
@@ -104,19 +108,13 @@
if (Config.DEBUG) {
Log.d(TAG, "CertificateTransparencyService start");
}
- if (!started) {
- mCertificateTransparencyJob.schedule();
- started = true;
- }
+ mCertificateTransparencyJob.schedule();
}
private void stopService() {
if (Config.DEBUG) {
Log.d(TAG, "CertificateTransparencyService stop");
}
- if (started) {
- mCertificateTransparencyJob.cancel();
- started = false;
- }
+ mCertificateTransparencyJob.cancel();
}
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java b/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java
new file mode 100644
index 0000000..3d05857
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/LogListUpdateStatus.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net.ct;
+
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_INVALID;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_NOT_FOUND;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED;
+
+import com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/** Class to represent the signature verification status for Certificate Transparency. */
+@AutoValue
+public abstract class LogListUpdateStatus {
+
+ abstract CTLogListUpdateState state();
+
+ abstract String signature();
+
+ abstract long logListTimestamp();
+
+ abstract int httpErrorStatusCode();
+
+ abstract Optional<Integer> downloadStatus();
+
+ boolean isSignatureVerified() {
+ // Check that none of the signature verification failures have been set as the state
+ return state() != PUBLIC_KEY_NOT_FOUND
+ && state() != SIGNATURE_INVALID
+ && state() != SIGNATURE_NOT_FOUND
+ && state() != SIGNATURE_VERIFICATION_FAILED;
+ }
+
+ boolean hasSignature() {
+ return signature() != null && signature().length() > 0;
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setState(CTLogListUpdateState updateState);
+
+ abstract Builder setSignature(String signature);
+
+ abstract Builder setLogListTimestamp(long timestamp);
+
+ abstract Builder setHttpErrorStatusCode(int httpStatusCode);
+
+ abstract Builder setDownloadStatus(Optional<Integer> downloadStatus);
+
+ abstract LogListUpdateStatus build();
+ }
+
+ abstract LogListUpdateStatus.Builder toBuilder();
+
+ static Builder builder() {
+ return new AutoValue_LogListUpdateStatus.Builder()
+ .setState(CTLogListUpdateState.UNKNOWN_STATE)
+ .setSignature("")
+ .setLogListTimestamp(0L)
+ .setHttpErrorStatusCode(0)
+ .setDownloadStatus(Optional.empty());
+ }
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/MissingPublicKeyException.java b/networksecurity/service/src/com/android/server/net/ct/MissingPublicKeyException.java
deleted file mode 100644
index 80607f6..0000000
--- a/networksecurity/service/src/com/android/server/net/ct/MissingPublicKeyException.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.net.ct;
-
-/**
- * An exception thrown when the public key is missing for CT signature verification.
- */
-public class MissingPublicKeyException extends Exception {
-
- public MissingPublicKeyException(String message) {
- super(message);
- }
-}
diff --git a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
index 67ef63f..3ba56db 100644
--- a/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
+++ b/networksecurity/service/src/com/android/server/net/ct/SignatureVerifier.java
@@ -15,6 +15,11 @@
*/
package com.android.server.net.ct;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_INVALID;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_NOT_FOUND;
+import static com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED;
+
import android.annotation.NonNull;
import android.annotation.RequiresApi;
import android.content.ContentResolver;
@@ -27,7 +32,9 @@
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
@@ -40,6 +47,7 @@
public class SignatureVerifier {
private final Context mContext;
+ private static final String TAG = "SignatureVerifier";
@NonNull private Optional<PublicKey> mPublicKey = Optional.empty();
@@ -79,30 +87,54 @@
mPublicKey = Optional.of(publicKey);
}
- boolean verify(Uri file, Uri signature)
- throws GeneralSecurityException, IOException, MissingPublicKeyException {
+ LogListUpdateStatus verify(Uri file, Uri signature) {
+ LogListUpdateStatus.Builder statusBuilder = LogListUpdateStatus.builder();
+
if (!mPublicKey.isPresent()) {
- throw new MissingPublicKeyException("Missing public key for signature verification");
+ statusBuilder.setState(PUBLIC_KEY_NOT_FOUND);
+ Log.e(TAG, "No public key found for log list verification");
+ return statusBuilder.build();
}
- Signature verifier = Signature.getInstance("SHA256withRSA");
- verifier.initVerify(mPublicKey.get());
+
ContentResolver contentResolver = mContext.getContentResolver();
- boolean success = false;
try (InputStream fileStream = contentResolver.openInputStream(file);
InputStream signatureStream = contentResolver.openInputStream(signature)) {
+ Signature verifier = Signature.getInstance("SHA256withRSA");
+ verifier.initVerify(mPublicKey.get());
verifier.update(fileStream.readAllBytes());
byte[] signatureBytes = signatureStream.readAllBytes();
try {
- success = verifier.verify(Base64.getDecoder().decode(signatureBytes));
+ byte[] decodedSigBytes = Base64.getDecoder().decode(signatureBytes);
+ statusBuilder.setSignature(new String(decodedSigBytes, StandardCharsets.UTF_8));
+
+ if (!verifier.verify(decodedSigBytes)) {
+ // Leave the UpdateState as UNKNOWN_STATE if successful as there are other
+ // potential failures past the signature verification step
+ statusBuilder.setState(SIGNATURE_VERIFICATION_FAILED);
+ }
} catch (IllegalArgumentException e) {
- Log.w("CertificateTransparencyDownloader", "Invalid signature base64 encoding", e);
- // TODO: remove the fallback once the signature base64 is published
- Log.i("CertificateTransparencyDownloader", "Signature verification as raw bytes");
- success = verifier.verify(signatureBytes);
+ Log.w(TAG, "Invalid signature base64 encoding", e);
+ statusBuilder.setSignature(new String(signatureBytes, StandardCharsets.UTF_8));
+ statusBuilder.setState(SIGNATURE_INVALID);
+ return statusBuilder.build();
}
+ } catch (InvalidKeyException e) {
+ Log.e(TAG, "Signature invalid for log list verification", e);
+ statusBuilder.setState(SIGNATURE_INVALID);
+ return statusBuilder.build();
+ } catch (IOException | GeneralSecurityException e) {
+ Log.e(TAG, "Could not verify new log list", e);
+ statusBuilder.setState(SIGNATURE_VERIFICATION_FAILED);
+ return statusBuilder.build();
}
- return success;
+
+ // Double check if the signature is empty that we set the state correctly
+ if (!statusBuilder.build().hasSignature()) {
+ statusBuilder.setState(SIGNATURE_NOT_FOUND);
+ }
+
+ return statusBuilder.build();
}
}
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
index ec4d6be..08704d1 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
@@ -16,12 +16,10 @@
package com.android.server.net.ct;
+import static com.google.common.io.Files.toByteArray;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -48,6 +46,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -57,6 +56,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -64,6 +64,7 @@
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;
+import java.util.Optional;
/** Tests for the {@link CertificateTransparencyDownloader}. */
@RunWith(JUnit4.class)
@@ -71,6 +72,8 @@
@Mock private DownloadManager mDownloadManager;
@Mock private CertificateTransparencyLogger mLogger;
+ private ArgumentCaptor<LogListUpdateStatus> mUpdateStatusCaptor =
+ ArgumentCaptor.forClass(LogListUpdateStatus.class);
private PrivateKey mPrivateKey;
private PublicKey mPublicKey;
@@ -108,15 +111,15 @@
mContext.getFilesDir());
prepareDownloadManager();
+ mDataStore.load();
mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
- mCertificateTransparencyDownloader.start();
}
@After
public void tearDown() {
mSignatureVerifier.resetPublicKey();
- mCertificateTransparencyDownloader.stop();
mCompatVersion.delete();
+ mDataStore.delete();
}
@Test
@@ -204,14 +207,12 @@
mContext,
makePublicKeyDownloadFailedIntent(DownloadManager.ERROR_INSUFFICIENT_SPACE));
- assertThat(
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
verify(mLogger, times(1))
- .logCTLogListUpdateStateChangedEventWithDownloadStatus(
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- /* failureCount= */ 1);
+ .logCTLogListUpdateStateChangedEvent(
+ LogListUpdateStatus.builder()
+ .setDownloadStatus(
+ Optional.of(DownloadManager.ERROR_INSUFFICIENT_SPACE))
+ .build());
}
@Test
@@ -253,14 +254,12 @@
makeMetadataDownloadFailedIntent(
mCompatVersion, DownloadManager.ERROR_INSUFFICIENT_SPACE));
- assertThat(
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
verify(mLogger, times(1))
- .logCTLogListUpdateStateChangedEventWithDownloadStatus(
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- /* failureCount= */ 1);
+ .logCTLogListUpdateStateChangedEvent(
+ LogListUpdateStatus.builder()
+ .setDownloadStatus(
+ Optional.of(DownloadManager.ERROR_INSUFFICIENT_SPACE))
+ .build());
}
@Test
@@ -306,14 +305,12 @@
makeContentDownloadFailedIntent(
mCompatVersion, DownloadManager.ERROR_INSUFFICIENT_SPACE));
- assertThat(
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
verify(mLogger, times(1))
- .logCTLogListUpdateStateChangedEventWithDownloadStatus(
- DownloadManager.ERROR_INSUFFICIENT_SPACE,
- /* failureCount= */ 1);
+ .logCTLogListUpdateStateChangedEvent(
+ LogListUpdateStatus.builder()
+ .setDownloadStatus(
+ Optional.of(DownloadManager.ERROR_INSUFFICIENT_SPACE))
+ .build());
}
@Test
@@ -335,7 +332,7 @@
@Test
public void
- testDownloader_contentDownloadSuccess_noSignatureFound_logsSingleFailure()
+ testDownloader_contentDownloadSuccess_noPublicKeyFound_logsSingleFailure()
throws Exception {
File logListFile = makeLogListFile("456");
File metadataFile = sign(logListFile);
@@ -349,26 +346,10 @@
mCertificateTransparencyDownloader.onReceive(
mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
- assertThat(
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
verify(mLogger, times(1))
- .logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND,
- /* failureCount= */ 1);
- verify(mLogger, never())
- .logCTLogListUpdateStateChangedEvent(
- eq(CTLogListUpdateState.SIGNATURE_NOT_FOUND),
- anyInt());
- verify(mLogger, never())
- .logCTLogListUpdateStateChangedEvent(
- eq(CTLogListUpdateState.SIGNATURE_INVALID),
- anyInt());
- verify(mLogger, never())
- .logCTLogListUpdateStateChangedEvent(
- eq(CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED),
- anyInt());
+ .logCTLogListUpdateStateChangedEvent(mUpdateStatusCaptor.capture());
+ assertThat(mUpdateStatusCaptor.getValue().state())
+ .isEqualTo(CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND);
}
@Test
@@ -391,26 +372,10 @@
mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
// Assert
- assertThat(
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
verify(mLogger, times(1))
- .logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState.SIGNATURE_INVALID,
- /* failureCount= */ 1);
- verify(mLogger, never())
- .logCTLogListUpdateStateChangedEvent(
- eq(CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED),
- anyInt());
- verify(mLogger, never())
- .logCTLogListUpdateStateChangedEvent(
- eq(CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND),
- anyInt());
- verify(mLogger, never())
- .logCTLogListUpdateStateChangedEvent(
- eq(CTLogListUpdateState.SIGNATURE_NOT_FOUND),
- anyInt());
+ .logCTLogListUpdateStateChangedEvent(mUpdateStatusCaptor.capture());
+ assertThat(mUpdateStatusCaptor.getValue().state())
+ .isEqualTo(CTLogListUpdateState.SIGNATURE_INVALID);
}
@Test
@@ -433,26 +398,14 @@
mContext, makeContentDownloadCompleteIntent(mCompatVersion, logListFile));
// Assert
- assertThat(
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
- verify(mLogger, never())
- .logCTLogListUpdateStateChangedEvent(
- eq(CTLogListUpdateState.SIGNATURE_NOT_FOUND),
- anyInt());
- verify(mLogger, never())
- .logCTLogListUpdateStateChangedEvent(
- eq(CTLogListUpdateState.SIGNATURE_INVALID),
- anyInt());
- verify(mLogger, never())
- .logCTLogListUpdateStateChangedEvent(
- eq(CTLogListUpdateState.PUBLIC_KEY_NOT_FOUND),
- anyInt());
+ byte[] signatureBytes = Base64.getDecoder().decode(toByteArray(metadataFile));
verify(mLogger, times(1))
- .logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED,
- /* failureCount= */ 1);
+ .logCTLogListUpdateStateChangedEvent(mUpdateStatusCaptor.capture());
+ LogListUpdateStatus statusValue = mUpdateStatusCaptor.getValue();
+ assertThat(statusValue.state())
+ .isEqualTo(CTLogListUpdateState.SIGNATURE_VERIFICATION_FAILED);
+ assertThat(statusValue.signature())
+ .isEqualTo(new String(signatureBytes, StandardCharsets.UTF_8));
}
@Test
@@ -469,14 +422,13 @@
mCertificateTransparencyDownloader.onReceive(
mContext, makeContentDownloadCompleteIntent(mCompatVersion, invalidLogListFile));
- assertThat(
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(1);
+ byte[] signatureBytes = Base64.getDecoder().decode(toByteArray(metadataFile));
verify(mLogger, times(1))
- .logCTLogListUpdateStateChangedEvent(
- CTLogListUpdateState.VERSION_ALREADY_EXISTS,
- /* failureCount= */ 1);
+ .logCTLogListUpdateStateChangedEvent(mUpdateStatusCaptor.capture());
+ LogListUpdateStatus statusValue = mUpdateStatusCaptor.getValue();
+ assertThat(statusValue.state()).isEqualTo(CTLogListUpdateState.VERSION_ALREADY_EXISTS);
+ assertThat(statusValue.signature())
+ .isEqualTo(new String(signatureBytes, StandardCharsets.UTF_8));
}
@Test
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 44868b2d..c743573 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -25,6 +25,8 @@
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH;
import static android.net.BpfNetMapsConstants.IIF_MATCH;
import static android.net.BpfNetMapsConstants.INGRESS_DISCARD_MAP_PATH;
+import static android.net.BpfNetMapsConstants.LOCAL_NET_ACCESS_MAP_PATH;
+import static android.net.BpfNetMapsConstants.LOCAL_NET_BLOCKED_UID_MAP_PATH;
import static android.net.BpfNetMapsConstants.LOCKDOWN_VPN_MATCH;
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_PERMISSION_MAP_PATH;
@@ -74,6 +76,7 @@
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Bool;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.Struct.U8;
@@ -81,6 +84,7 @@
import com.android.net.module.util.bpf.CookieTagMapValue;
import com.android.net.module.util.bpf.IngressDiscardKey;
import com.android.net.module.util.bpf.IngressDiscardValue;
+import com.android.net.module.util.bpf.LocalNetAccessKey;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -131,6 +135,9 @@
private static IBpfMap<S32, U8> sDataSaverEnabledMap = null;
private static IBpfMap<IngressDiscardKey, IngressDiscardValue> sIngressDiscardMap = null;
+ private static IBpfMap<LocalNetAccessKey, Bool> sLocalNetAccessMap = null;
+ private static IBpfMap<U32, Bool> sLocalNetBlockedUidMap = null;
+
private static final List<Pair<Integer, String>> PERMISSION_LIST = Arrays.asList(
Pair.create(PERMISSION_INTERNET, "PERMISSION_INTERNET"),
Pair.create(PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS")
@@ -186,6 +193,25 @@
sIngressDiscardMap = ingressDiscardMap;
}
+ /**
+ * Set localNetAccessMap for test.
+ */
+ @VisibleForTesting
+ public static void setLocalNetAccessMapForTest(
+ IBpfMap<LocalNetAccessKey, Bool> localNetAccessMap) {
+ sLocalNetAccessMap = localNetAccessMap;
+ }
+
+ /**
+ * Set localNetBlockedUidMap for test.
+ */
+ @VisibleForTesting
+ public static void setLocalNetBlockedUidMapForTest(
+ IBpfMap<U32, Bool> localNetBlockedUidMap) {
+ sLocalNetBlockedUidMap = localNetBlockedUidMap;
+ }
+
+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U32> getConfigurationMap() {
try {
@@ -247,6 +273,26 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ private static IBpfMap<U32, Bool> getLocalNetBlockedUidMap() {
+ try {
+ return SingleWriterBpfMap.getSingleton(LOCAL_NET_BLOCKED_UID_MAP_PATH,
+ U32.class, Bool.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open local_net_blocked_uid map", e);
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ private static IBpfMap<LocalNetAccessKey, Bool> getLocalNetAccessMap() {
+ try {
+ return SingleWriterBpfMap.getSingleton(LOCAL_NET_ACCESS_MAP_PATH,
+ LocalNetAccessKey.class, Bool.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open local_net_access map", e);
+ }
+ }
+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static void initBpfMaps() {
if (sConfigurationMap == null) {
@@ -299,6 +345,27 @@
} catch (ErrnoException e) {
throw new IllegalStateException("Failed to initialize ingress discard map", e);
}
+
+ if (isAtLeast25Q2()) {
+ if (sLocalNetAccessMap == null) {
+ sLocalNetAccessMap = getLocalNetAccessMap();
+ }
+ try {
+ sLocalNetAccessMap.clear();
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Failed to initialize local_net_access map", e);
+ }
+
+ if (sLocalNetBlockedUidMap == null) {
+ sLocalNetBlockedUidMap = getLocalNetBlockedUidMap();
+ }
+ try {
+ sLocalNetBlockedUidMap.clear();
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Failed to initialize local_net_blocked_uid map",
+ e);
+ }
+ }
}
/**
@@ -387,6 +454,21 @@
}
}
+ private void throwIfPre25Q2(final String msg) {
+ if (!isAtLeast25Q2()) {
+ throw new UnsupportedOperationException(msg);
+ }
+ }
+
+ /*
+ ToDo : Remove this method when SdkLevel.isAtLeastB() is fixed, aosp is at sdk level 36 or use
+ NetworkStackUtils.isAtLeast25Q2 when it is moved to a static lib.
+ */
+ private static boolean isAtLeast25Q2() {
+ return SdkLevel.isAtLeastB() || (SdkLevel.isAtLeastV()
+ && "Baklava".equals(Build.VERSION.CODENAME));
+ }
+
private void removeRule(final int uid, final long match, final String caller) {
try {
synchronized (sUidOwnerMap) {
@@ -810,6 +892,113 @@
}
/**
+ * Add configuration to local_net_access trie map.
+ * @param lpmBitlen prefix length that will be used for longest matching
+ * @param iface interface name
+ * @param address remote address. ipv4 addresses would be mapped to v6
+ * @param protocol required for longest match in special cases
+ * @param remotePort src/dst port for ingress/egress
+ * @param isAllowed is the local network call allowed or blocked.
+ */
+ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ public void addLocalNetAccess(final int lpmBitlen, final String iface,
+ final InetAddress address, final int protocol, final int remotePort,
+ final boolean isAllowed) {
+ throwIfPre25Q2("addLocalNetAccess is not available on pre-B devices");
+ final int ifIndex = mDeps.getIfIndex(iface);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Failed to get if index, skip addLocalNetAccess for " + address
+ + "(" + iface + ")");
+ return;
+ }
+ LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+ address, protocol, remotePort);
+
+ try {
+ sLocalNetAccessMap.updateEntry(localNetAccessKey, new Bool(isAllowed));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to add local network access for localNetAccessKey : "
+ + localNetAccessKey + ", isAllowed : " + isAllowed);
+ }
+ }
+
+ /**
+ * False if the configuration is disallowed.
+ *
+ * @param lpmBitlen prefix length that will be used for longest matching
+ * @param iface interface name
+ * @param address remote address. ipv4 addresses would be mapped to v6
+ * @param protocol required for longest match in special cases
+ * @param remotePort src/dst port for ingress/egress
+ */
+ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ public boolean getLocalNetAccess(final int lpmBitlen, final String iface,
+ final InetAddress address, final int protocol, final int remotePort) {
+ throwIfPre25Q2("getLocalNetAccess is not available on pre-B devices");
+ final int ifIndex = mDeps.getIfIndex(iface);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Failed to get if index, returning default from getLocalNetAccess for "
+ + address + "(" + iface + ")");
+ return true;
+ }
+ LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+ address, protocol, remotePort);
+ try {
+ Bool value = sLocalNetAccessMap.getValue(localNetAccessKey);
+ return value == null ? true : value.val;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to find local network access configuration for "
+ + "localNetAccessKey : " + localNetAccessKey);
+ }
+ return true;
+ }
+
+ /**
+ * Add uid to local_net_blocked_uid map.
+ * @param uid application uid that needs to block local network calls.
+ */
+ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ public void addUidToLocalNetBlockMap(final int uid) {
+ throwIfPre25Q2("addUidToLocalNetBlockMap is not available on pre-B devices");
+ try {
+ sLocalNetBlockedUidMap.updateEntry(new U32(uid), new Bool(true));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to add local network blocked for uid : " + uid);
+ }
+ }
+
+ /**
+ * True if local network calls are blocked for application.
+ * @param uid application uid that needs check if local network calls are blocked.
+ */
+ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ public boolean getUidValueFromLocalNetBlockMap(final int uid) {
+ throwIfPre25Q2("getUidValueFromLocalNetBlockMap is not available on pre-B devices");
+ try {
+ Bool value = sLocalNetBlockedUidMap.getValue(new U32(uid));
+ return value == null ? false : value.val;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to find uid(" + uid
+ + ") is present in local network blocked map");
+ }
+ return false;
+ }
+
+ /**
+ * Remove uid from local_net_blocked_uid map(if present).
+ * @param uid application uid that needs check if local network calls are blocked.
+ */
+ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ public void removeUidFromLocalNetBlockMap(final int uid) {
+ throwIfPre25Q2("removeUidFromLocalNetBlockMap is not available on pre-B devices");
+ try {
+ sLocalNetBlockedUidMap.deleteEntry(new U32(uid));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to remove uid(" + uid + ") from local network blocked map");
+ }
+ }
+
+ /**
* Get granted permissions for specified uid. If uid is not in the map, this method returns
* {@link android.net.INetd.PERMISSION_INTERNET} since this is a default permission.
* See {@link #setNetPermForUids}
@@ -1079,6 +1268,14 @@
(key, value) -> "[" + key.dstAddr + "]: "
+ value.iif1 + "(" + mDeps.getIfName(value.iif1) + "), "
+ value.iif2 + "(" + mDeps.getIfName(value.iif2) + ")");
+ if (sLocalNetBlockedUidMap != null) {
+ BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap",
+ (key, value) -> "[" + key + "]: " + value);
+ }
+ if (sLocalNetBlockedUidMap != null) {
+ BpfDump.dumpMap(sLocalNetBlockedUidMap, pw, "sLocalNetBlockedUidMap",
+ (key, value) -> "[" + key + "]: " + value);
+ }
dumpDataSaverConfig(pw);
pw.decreaseIndent();
}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index beaa174..0d388e8 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -31,6 +31,7 @@
import static android.net.INetd.PERMISSION_UNINSTALLED;
import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.net.connectivity.ConnectivityCompatChanges.RESTRICT_LOCAL_NETWORK;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
@@ -38,6 +39,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -70,6 +72,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.flags.Flags;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.ProcessShimImpl;
@@ -279,6 +282,12 @@
mContext = context;
mBpfNetMaps = bpfNetMaps;
mThread = thread;
+ if (Flags.restrictLocalNetwork()) {
+ // This listener should finish registration by the time the system has completed
+ // boot setup such that any changes to runtime permissions for local network
+ // restrictions can only occur after this registration has completed.
+ mPackageManager.addOnPermissionsChangeListener(new PermissionChangeListener());
+ }
}
private void ensureRunningOnHandlerThread() {
@@ -1311,4 +1320,19 @@
private static void loge(String s, Throwable e) {
Log.e(TAG, s, e);
}
+
+ private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
+ @Override
+ public void onPermissionsChanged(int uid) {
+ // RESTRICT_LOCAL_NETWORK is a compat change that is enabled when developers manually
+ // opt-in to this change, or when the app's targetSdkVersion is greater than 36.
+ // The RESTRICT_LOCAL_NETWORK compat change is used here instead of the
+ // Flags.restrictLocalNetwork() is used to offer the feature to devices, but it will
+ // only be enforced when develoeprs choose to enable it.
+ // TODO(b/394567896): Update compat change checks
+ if (CompatChanges.isChangeEnabled(RESTRICT_LOCAL_NETWORK, uid)) {
+ // TODO(b/388803658): Update network permissions and record change
+ }
+ }
+ }
}
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
index 21e781c..5425d0e 100644
--- a/staticlibs/native/tcutils/tcutils.cpp
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -361,7 +361,7 @@
const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
-int sendAndProcessNetlinkResponse(const void *req, int len) {
+int sendAndProcessNetlinkResponse(const void *req, int len, bool enoent_ok) {
// TODO: use unique_fd instead of ScopeGuard
unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
if (!fd.ok()) {
@@ -445,7 +445,9 @@
return -ENOMSG;
}
- if (resp.e.error) {
+ if (resp.e.error == -ENOENT) {
+ if (!enoent_ok) ALOGE("NLMSG_ERROR message returned ENOENT");
+ } else if (resp.e.error) {
ALOGE("NLMSG_ERROR message return error: %d", resp.e.error);
}
return resp.e.error; // returns 0 on success
@@ -560,7 +562,8 @@
};
#undef CLSACT
- return sendAndProcessNetlinkResponse(&req, sizeof(req));
+ const bool enoent_ok = (nlMsgType == RTM_DELQDISC);
+ return sendAndProcessNetlinkResponse(&req, sizeof(req), enoent_ok);
}
// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned
@@ -666,7 +669,7 @@
snprintf(req.options.name.str, sizeof(req.options.name.str), "%s:[*fsobj]",
basename(bpfProgPath));
- int error = sendAndProcessNetlinkResponse(&req, sizeof(req));
+ int error = sendAndProcessNetlinkResponse(&req, sizeof(req), false);
return error;
}
@@ -698,7 +701,8 @@
return error;
}
return sendAndProcessNetlinkResponse(filter.getRequest(),
- filter.getRequestSize());
+ filter.getRequestSize(),
+ false);
}
// tc filter del dev .. in/egress prio .. protocol ..
@@ -726,7 +730,7 @@
},
};
- return sendAndProcessNetlinkResponse(&req, sizeof(req));
+ return sendAndProcessNetlinkResponse(&req, sizeof(req), true);
}
} // namespace android
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
index a93ae3e..ae0de79 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
@@ -18,28 +18,36 @@
import android.Manifest.permission.MODIFY_PHONE_STATE
import android.Manifest.permission.READ_PHONE_STATE
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.ConditionVariable
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager
+import android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.android.modules.utils.build.SdkLevel.isAtLeastU
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
private val TAG = CarrierConfigRule::class.simpleName
+private const val CARRIER_CONFIG_CHANGE_TIMEOUT_MS = 10_000L
/**
* A [TestRule] that helps set [CarrierConfigManager] overrides for tests and clean up the test
* configuration automatically on teardown.
*/
class CarrierConfigRule : TestRule {
- private val ccm by lazy { InstrumentationRegistry.getInstrumentation().context.getSystemService(
- CarrierConfigManager::class.java
- ) }
+ private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+ private val ccm by lazy { context.getSystemService(CarrierConfigManager::class.java) }
// Map of (subId) -> (original values of overridden settings)
private val originalConfigs = mutableMapOf<Int, PersistableBundle>()
@@ -61,6 +69,33 @@
}
}
+ private class ConfigChangeReceiver(private val subId: Int) : BroadcastReceiver() {
+ val cv = ConditionVariable()
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != ACTION_CARRIER_CONFIG_CHANGED ||
+ intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, -1) != subId) {
+ return
+ }
+ // This may race with other config changes for the same subId, but there is no way to
+ // know which update is being reported, and querying the override would return the
+ // latest values even before the config is applied. Config changes should be rare, so it
+ // is unlikely they would happen exactly after the override applied here and cause
+ // flakes.
+ cv.open()
+ }
+ }
+
+ private fun overrideConfigAndWait(subId: Int, config: PersistableBundle) {
+ val changeReceiver = ConfigChangeReceiver(subId)
+ context.registerReceiver(changeReceiver, IntentFilter(ACTION_CARRIER_CONFIG_CHANGED))
+ ccm.overrideConfig(subId, config)
+ assertTrue(
+ changeReceiver.cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+ "Timed out waiting for config change for subId $subId"
+ )
+ context.unregisterReceiver(changeReceiver)
+ }
+
/**
* Add carrier config overrides with the specified configuration.
*
@@ -79,7 +114,7 @@
originalConfig.putAll(previousValues)
runAsShell(MODIFY_PHONE_STATE) {
- ccm.overrideConfig(subId, config)
+ overrideConfigAndWait(subId, config)
}
}
@@ -93,10 +128,10 @@
runAsShell(MODIFY_PHONE_STATE) {
originalConfigs.forEach { (subId, config) ->
try {
- // Do not use overrideConfig with null, as it would reset configs that may
+ // Do not use null as the config to reset, as it would reset configs that may
// have been set by target preparers such as
// ConnectivityTestTargetPreparer / CarrierConfigSetupTest.
- ccm.overrideConfig(subId, config)
+ overrideConfigAndWait(subId, config)
} catch (e: Throwable) {
Log.e(TAG, "Error resetting carrier config for subId $subId")
}
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 57bc2be..06f2075 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -605,6 +605,9 @@
}
private fun assumeNoInterfaceForTetheringAvailable() {
+ // Requesting a tethered interface will stop IpClient. Prevent it from doing so
+ // if adb is connected over ethernet.
+ assumeFalse(isAdbOverEthernet())
// Interfaces that have configured NetworkCapabilities will never be used for tethering,
// see aosp/2123900.
try {
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 4ba41cd..8fcc703 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -743,7 +743,6 @@
}
return@tryTest
}
- cv.close()
if (hold) {
carrierConfigRule.addConfigOverrides(subId, PersistableBundle().also {
it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
@@ -752,7 +751,6 @@
} else {
carrierConfigRule.cleanUpNow()
}
- assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't change carrier privilege")
} cleanup @JvmSerializableLambda {
runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
tm.unregisterCarrierPrivilegesCallback(cpb)
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index d167836..a1e0797 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -55,34 +55,6 @@
host_required: ["net-tests-utils-host-common"],
}
-// Tethering CTS tests that target the latest released SDK. These tests can be installed on release
-// devices which has equal or lowner sdk version than target sdk and are useful for qualifying
-// mainline modules on release devices.
-android_test {
- name: "CtsTetheringTestLatestSdk",
- defaults: [
- "ConnectivityTestsLatestSdkDefaults",
- "CtsTetheringTestDefaults",
- ],
-
- min_sdk_version: "30",
-
- static_libs: [
- "TetheringIntegrationTestsLatestSdkLib",
- ],
-
- test_suites: [
- "general-tests",
- "mts-tethering",
- ],
-
- test_config_template: "AndroidTestTemplate.xml",
-
- // Include both the 32 and 64 bit versions
- compile_multilib: "both",
- jarjar_rules: ":NetworkStackJarJarRules",
-}
-
// Tethering CTS tests for development and release. These tests always target the platform SDK
// version, and are subject to all the restrictions appropriate to that version. Before SDK
// finalization, these tests have a min_sdk_version of 10000, but they can still be installed on
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 06bdca6..375d604 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -16,6 +16,7 @@
package com.android.server.net.integrationtests
+import android.Manifest.permission
import android.app.usage.NetworkStatsManager
import android.content.ComponentName
import android.content.Context
@@ -66,6 +67,7 @@
import com.android.testutils.DeviceInfoUtils
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
import com.android.testutils.tryTest
import java.util.function.BiConsumer
import java.util.function.Consumer
@@ -208,7 +210,9 @@
networkStackClient = TestNetworkStackClient(realContext)
networkStackClient.start()
- service = TestConnectivityService(TestDependencies())
+ service = runAsShell(permission.OBSERVE_GRANT_REVOKE_PERMISSIONS) {
+ TestConnectivityService(TestDependencies())
+ }
cm = ConnectivityManager(context, service)
context.addMockSystemService(Context.CONNECTIVITY_SERVICE, cm)
context.addMockSystemService(Context.NETWORK_STATS_SERVICE, statsManager)
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index c1c15ca..73eb24d 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -71,6 +71,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -99,6 +100,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct.Bool;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.Struct.U8;
@@ -106,6 +108,7 @@
import com.android.net.module.util.bpf.CookieTagMapValue;
import com.android.net.module.util.bpf.IngressDiscardKey;
import com.android.net.module.util.bpf.IngressDiscardValue;
+import com.android.net.module.util.bpf.LocalNetAccessKey;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -171,6 +174,10 @@
private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap =
new TestBpfMap<>(S32.class, UidOwnerValue.class);
private final IBpfMap<S32, U8> mUidPermissionMap = new TestBpfMap<>(S32.class, U8.class);
+ private final IBpfMap<U32, Bool> mLocalNetBlockedUidMap =
+ new TestBpfMap<>(U32.class, Bool.class);
+ private final IBpfMap<LocalNetAccessKey, Bool> mLocalNetAccessMap =
+ new TestBpfMap<>(LocalNetAccessKey.class, Bool.class);
private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap =
spy(new TestBpfMap<>(CookieTagMapKey.class, CookieTagMapValue.class));
private final IBpfMap<S32, U8> mDataSaverEnabledMap = new TestBpfMap<>(S32.class, U8.class);
@@ -189,6 +196,8 @@
CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap);
+ BpfNetMaps.setLocalNetAccessMapForTest(mLocalNetAccessMap);
+ BpfNetMaps.setLocalNetBlockedUidMapForTest(mLocalNetBlockedUidMap);
BpfNetMaps.setCookieTagMapForTest(mCookieTagMap);
BpfNetMaps.setDataSaverEnabledMapForTest(mDataSaverEnabledMap);
mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED));
@@ -235,6 +244,138 @@
}
@Test
+ @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddLocalNetAccessBeforeV() {
+ assertThrows(UnsupportedOperationException.class, () ->
+ mBpfNetMaps.addLocalNetAccess(0, TEST_IF_NAME, Inet6Address.ANY, 0, 0, true));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddLocalNetAccessAfterV() throws Exception {
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mBpfNetMaps.addLocalNetAccess(160, TEST_IF_NAME,
+ Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+ assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+ assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("100.68.0.0"), 0, 0)));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddLocalNetAccessAfterVWithIncorrectInterface() throws Exception {
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mBpfNetMaps.addLocalNetAccess(160, "wlan2",
+ Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+ assertTrue(mLocalNetAccessMap.isEmpty());
+ }
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testGetLocalNetAccessBeforeV() {
+ assertThrows(UnsupportedOperationException.class, () ->
+ mBpfNetMaps.getLocalNetAccess(0, TEST_IF_NAME, Inet6Address.ANY, 0, 0));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testGetLocalNetAccessAfterV() throws Exception {
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mLocalNetAccessMap.updateEntry(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0),
+ new Bool(false));
+
+ assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+
+ assertFalse(mBpfNetMaps.getLocalNetAccess(160, TEST_IF_NAME,
+ Inet4Address.getByName("196.68.0.0"), 0, 0));
+ assertTrue(mBpfNetMaps.getLocalNetAccess(160, TEST_IF_NAME,
+ Inet4Address.getByName("100.68.0.0"), 0, 0));
+ }
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddUidToLocalNetBlockMapBeforeV() {
+ assertThrows(UnsupportedOperationException.class, () ->
+ mBpfNetMaps.addUidToLocalNetBlockMap(0));
+ }
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testIsUidPresentInLocalNetBlockMapBeforeV() {
+ assertThrows(UnsupportedOperationException.class, () ->
+ mBpfNetMaps.getUidValueFromLocalNetBlockMap(0));
+ }
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemoveUidFromLocalNetBlockMapBeforeV() {
+ assertThrows(UnsupportedOperationException.class, () ->
+ mBpfNetMaps.removeUidFromLocalNetBlockMap(0));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddUidFromLocalNetBlockMapAfterV() throws Exception {
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mBpfNetMaps.addUidToLocalNetBlockMap(uid0);
+ assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid0)).val);
+ assertNull(mLocalNetBlockedUidMap.getValue(new U32(uid1)));
+
+ mBpfNetMaps.addUidToLocalNetBlockMap(uid1);
+ assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid1)).val);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testIsUidPresentInLocalNetBlockMapAfterV() throws Exception {
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mLocalNetBlockedUidMap.updateEntry(new U32(uid0), new Bool(true));
+ assertTrue(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid0));
+ assertFalse(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid1));
+
+ mLocalNetBlockedUidMap.updateEntry(new U32(uid1), new Bool(true));
+ assertTrue(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid1));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemoveUidFromLocalNetBlockMapAfterV() throws Exception {
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mLocalNetBlockedUidMap.updateEntry(new U32(uid0), new Bool(true));
+ mLocalNetBlockedUidMap.updateEntry(new U32(uid1), new Bool(true));
+
+ assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid0)).val);
+ assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid1)).val);
+
+ mBpfNetMaps.removeUidFromLocalNetBlockMap(uid0);
+ assertNull(mLocalNetBlockedUidMap.getValue(new U32(uid0)));
+ assertTrue(mLocalNetBlockedUidMap.getValue(new U32(uid1)).val);
+
+ mBpfNetMaps.removeUidFromLocalNetBlockMap(uid1);
+ assertNull(mLocalNetBlockedUidMap.getValue(new U32(uid1)));
+ }
+
+ @Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testIsChainEnabled() throws Exception {
doTestIsChainEnabled(FIREWALL_CHAIN_DOZABLE);
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 5bde31a..55c68b7 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.NETWORK_STACK;
+import static android.Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_OEM;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRODUCT;
@@ -41,6 +42,7 @@
import static android.os.Process.SYSTEM_UID;
import static com.android.server.connectivity.PermissionMonitor.isHigherNetworkPermission;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
import static junit.framework.Assert.fail;
@@ -1235,8 +1237,10 @@
// Use the real context as this test must ensure the *real* system package holds the
// necessary permission.
final Context realContext = InstrumentationRegistry.getContext();
- final PermissionMonitor monitor = new PermissionMonitor(
- realContext, mNetdService, mBpfNetMaps, mHandlerThread);
+ final PermissionMonitor monitor = runAsShell(
+ OBSERVE_GRANT_REVOKE_PERMISSIONS,
+ () -> new PermissionMonitor(realContext, mNetdService, mBpfNetMaps, mHandlerThread)
+ );
final PackageManager manager = realContext.getPackageManager();
final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
GET_PERMISSIONS | MATCH_ANY_USER);