Merge "Revert "Use addEntries when creating NetworkStats"" into main
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
index 136dfb1..ac4d8b1 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -22,6 +22,12 @@
import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_BLUETOOTH;
+import static android.net.NetworkTemplate.MATCH_ETHERNET;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
@@ -48,6 +54,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.net.NetworkCapabilities;
+import android.net.NetworkTemplate;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
import android.stats.connectivity.UpstreamType;
@@ -419,4 +426,46 @@
}
return false;
}
+
+ /**
+ * Build NetworkTemplate for the given upstream type.
+ *
+ * <p> NetworkTemplate.Builder API was introduced in Android T.
+ *
+ * @param type the upstream type
+ * @return A NetworkTemplate object with a corresponding match rule or null if tethering
+ * metrics' data usage cannot be collected for a given upstream type.
+ */
+ @Nullable
+ public static NetworkTemplate buildNetworkTemplateForUpstreamType(@NonNull UpstreamType type) {
+ if (!isUsageSupportedForUpstreamType(type)) return null;
+
+ switch (type) {
+ case UT_CELLULAR:
+ // TODO: Handle the DUN connection, which is not a default network.
+ return new NetworkTemplate.Builder(MATCH_MOBILE)
+ .setMeteredness(METERED_YES)
+ .setDefaultNetworkStatus(DEFAULT_NETWORK_YES)
+ .build();
+ case UT_WIFI:
+ return new NetworkTemplate.Builder(MATCH_WIFI)
+ .setMeteredness(METERED_YES)
+ .setDefaultNetworkStatus(DEFAULT_NETWORK_YES)
+ .build();
+ case UT_BLUETOOTH:
+ return new NetworkTemplate.Builder(MATCH_BLUETOOTH)
+ .setMeteredness(METERED_YES)
+ .setDefaultNetworkStatus(DEFAULT_NETWORK_YES)
+ .build();
+ case UT_ETHERNET:
+ return new NetworkTemplate.Builder(MATCH_ETHERNET)
+ .setMeteredness(METERED_YES)
+ .setDefaultNetworkStatus(DEFAULT_NETWORK_YES)
+ .build();
+ default:
+ Log.e(TAG, "Unsupported UpstreamType: " + type.name());
+ break;
+ }
+ return null;
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
index 7cef9cb..fbc2893 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -22,6 +22,10 @@
import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import static android.net.NetworkTemplate.MATCH_BLUETOOTH;
+import static android.net.NetworkTemplate.MATCH_ETHERNET;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
@@ -47,11 +51,14 @@
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.NetworkCapabilities;
+import android.net.NetworkTemplate;
+import android.os.Build;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
import android.stats.connectivity.UpstreamType;
@@ -62,8 +69,11 @@
import com.android.networkstack.tethering.UpstreamNetworkState;
import com.android.networkstack.tethering.metrics.TetheringMetrics.Dependencies;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -72,12 +82,15 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public final class TetheringMetricsTest {
+ @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
private static final String TEST_CALLER_PKG = "com.test.caller.pkg";
private static final String SETTINGS_PKG = "com.android.settings";
private static final String SYSTEMUI_PKG = "com.android.systemui";
private static final String GMS_PKG = "com.google.android.gms";
private static final long TEST_START_TIME = 1670395936033L;
private static final long SECOND_IN_MILLIS = 1_000L;
+ private static final int MATCH_NONE = -1;
@Mock private Context mContext;
@Mock private Dependencies mDeps;
@@ -392,4 +405,27 @@
runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_LOWPAN, false /* isSupported */);
runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_UNKNOWN, false /* isSupported */);
}
+
+ private void runBuildNetworkTemplateForUpstreamType(final UpstreamType upstreamType,
+ final int matchRule) {
+ final NetworkTemplate template =
+ TetheringMetrics.buildNetworkTemplateForUpstreamType(upstreamType);
+ if (matchRule == MATCH_NONE) {
+ assertNull(template);
+ } else {
+ assertEquals(matchRule, template.getMatchRule());
+ }
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testBuildNetworkTemplateForUpstreamType() {
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_CELLULAR, MATCH_MOBILE);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_WIFI, MATCH_WIFI);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_BLUETOOTH, MATCH_BLUETOOTH);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_ETHERNET, MATCH_ETHERNET);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_WIFI_AWARE, MATCH_NONE);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_LOWPAN, MATCH_NONE);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_UNKNOWN, MATCH_NONE);
+ }
}
diff --git a/common/FlaggedApi.bp b/common/FlaggedApi.bp
index d5cfde3..fef9ac3 100644
--- a/common/FlaggedApi.bp
+++ b/common/FlaggedApi.bp
@@ -47,3 +47,20 @@
srcs: ["nearby_flags.aconfig"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
+
+aconfig_declarations {
+ name: "com.android.networksecurity.flags-aconfig",
+ package: "com.android.net.ct.flags",
+ container: "com.android.tethering",
+ srcs: ["networksecurity_flags.aconfig"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+java_aconfig_library {
+ name: "networksecurity_flags_java_lib",
+ aconfig_declarations: "com.android.networksecurity.flags-aconfig",
+ min_sdk_version: "30",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ apex_available: ["com.android.tethering"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/common/networksecurity_flags.aconfig b/common/networksecurity_flags.aconfig
new file mode 100644
index 0000000..ef8ffcd
--- /dev/null
+++ b/common/networksecurity_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.net.ct.flags"
+container: "com.android.tethering"
+flag {
+ name: "certificate_transparency_service"
+ is_exported: true
+ namespace: "network_security"
+ description: "Enable service for certificate transparency log list data"
+ bug: "319829948"
+}
diff --git a/framework/Android.bp b/framework/Android.bp
index deea6b6..4c4f792 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -63,6 +63,7 @@
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
":framework-connectivity-api-shared-srcs",
+ ":framework-networksecurity-sources",
],
aidl: {
generate_get_transaction_name: true,
@@ -93,6 +94,7 @@
"modules-utils-backgroundthread",
"modules-utils-build",
"modules-utils-preconditions",
+ "networksecurity_flags_java_lib",
"framework-connectivity-javastream-protos",
],
impl_only_static_libs: [
@@ -207,6 +209,7 @@
},
aconfig_declarations: [
"com.android.net.flags-aconfig",
+ "com.android.networksecurity.flags-aconfig",
],
}
@@ -307,6 +310,7 @@
srcs: [
":framework-connectivity-sources",
":framework-connectivity-tiramisu-updatable-sources",
+ ":framework-networksecurity-sources",
":framework-nearby-java-sources",
":framework-thread-sources",
],
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 8b539aa..afb44cc 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -137,9 +137,6 @@
// Size of the BPF log buffer for verifier logging
#define BPF_LOAD_LOG_SZ 0xfffff
-// Unspecified attach type is 0 which is BPF_CGROUP_INET_INGRESS.
-#define BPF_ATTACH_TYPE_UNSPEC BPF_CGROUP_INET_INGRESS
-
static unsigned int page_size = static_cast<unsigned int>(getpagesize());
constexpr const char* lookupSelinuxContext(const domain d) {
@@ -201,7 +198,7 @@
typedef struct {
const char* name;
enum bpf_prog_type type;
- enum bpf_attach_type expected_attach_type;
+ enum bpf_attach_type attach_type;
} sectionType;
/*
@@ -221,8 +218,8 @@
sectionType sectionNameTypes[] = {
{"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
{"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
- {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
- {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB},
+ {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK},
{"cgroupsockcreate/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_CREATE},
{"cgroupsockrelease/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_RELEASE},
{"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
@@ -230,28 +227,24 @@
{"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
{"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
{"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
- {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
{"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
{"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
{"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
{"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
- {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
- {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
+ {"schedact/", BPF_PROG_TYPE_SCHED_ACT},
+ {"schedcls/", BPF_PROG_TYPE_SCHED_CLS},
{"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
{"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
{"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
- {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
+ {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER},
{"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
{"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
- {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
+ {"xdp/", BPF_PROG_TYPE_XDP},
};
typedef struct {
enum bpf_prog_type type;
- enum bpf_attach_type expected_attach_type;
+ enum bpf_attach_type attach_type;
string name;
vector<char> data;
vector<char> rel_data;
@@ -435,12 +428,6 @@
return BPF_PROG_TYPE_UNSPEC;
}
-static enum bpf_attach_type getExpectedAttachType(string& name) {
- for (auto& snt : sectionNameTypes)
- if (StartsWith(name, snt.name)) return snt.expected_attach_type;
- return BPF_ATTACH_TYPE_UNSPEC;
-}
-
/*
static string getSectionName(enum bpf_prog_type type)
{
@@ -556,7 +543,8 @@
if (ptype == BPF_PROG_TYPE_UNSPEC) continue;
// This must be done before '/' is replaced with '_'.
- cs_temp.expected_attach_type = getExpectedAttachType(name);
+ for (auto& snt : sectionNameTypes)
+ if (StartsWith(name, snt.name)) cs_temp.attach_type = snt.attach_type;
string oldName = name;
@@ -1062,7 +1050,7 @@
.log_level = 1,
.log_buf = ptr_to_u64(log_buf.data()),
.log_size = static_cast<__u32>(log_buf.size()),
- .expected_attach_type = cs[i].expected_attach_type,
+ .expected_attach_type = cs[i].attach_type,
};
if (isAtLeastKernelVersion(4, 15, 0))
strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
diff --git a/netd/BpfBaseTest.cpp b/netd/BpfBaseTest.cpp
index c979a7b..34dfbb4 100644
--- a/netd/BpfBaseTest.cpp
+++ b/netd/BpfBaseTest.cpp
@@ -56,7 +56,7 @@
TEST_F(BpfBasicTest, TestCgroupMounted) {
std::string cg2_path;
- ASSERT_EQ(true, CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cg2_path));
+ ASSERT_EQ(true, CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cg2_path));
ASSERT_EQ(0, access(cg2_path.c_str(), R_OK));
ASSERT_EQ(0, access((cg2_path + "/cgroup.controllers").c_str(), R_OK));
}
diff --git a/networksecurity/OWNERS b/networksecurity/OWNERS
new file mode 100644
index 0000000..1a4130a
--- /dev/null
+++ b/networksecurity/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1479456
+
+sandrom@google.com
+tweek@google.com
diff --git a/networksecurity/framework/Android.bp b/networksecurity/framework/Android.bp
new file mode 100644
index 0000000..2b77926
--- /dev/null
+++ b/networksecurity/framework/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+ default_team: "trendy_team_platform_security",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "framework-networksecurity-sources",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
+ path: "src",
+ visibility: [
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+}
diff --git a/networksecurity/framework/src/android/net/ct/CertificateTransparencyManager.java b/networksecurity/framework/src/android/net/ct/CertificateTransparencyManager.java
new file mode 100644
index 0000000..94521ae
--- /dev/null
+++ b/networksecurity/framework/src/android/net/ct/CertificateTransparencyManager.java
@@ -0,0 +1,40 @@
+/*
+ * 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 android.net.ct;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemService;
+
+import com.android.net.ct.flags.Flags;
+
+/**
+ * Provides the primary API for the Certificate Transparency Manager.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CERTIFICATE_TRANSPARENCY_SERVICE)
+@SystemService(CertificateTransparencyManager.SERVICE_NAME)
+public final class CertificateTransparencyManager {
+
+ public static final String SERVICE_NAME = "certificate_transparency";
+
+ /**
+ * Creates a new CertificateTransparencyManager instance.
+ *
+ * @hide
+ */
+ public CertificateTransparencyManager() {}
+}
diff --git a/networksecurity/framework/src/android/net/ct/ICertificateTransparencyManager.aidl b/networksecurity/framework/src/android/net/ct/ICertificateTransparencyManager.aidl
new file mode 100644
index 0000000..b5bce7f
--- /dev/null
+++ b/networksecurity/framework/src/android/net/ct/ICertificateTransparencyManager.aidl
@@ -0,0 +1,22 @@
+/**
+ * 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 android.net.ct;
+
+/**
+* Interface for communicating with CertificateTransparencyService.
+* @hide
+*/
+interface ICertificateTransparencyManager {}
diff --git a/networksecurity/service/Android.bp b/networksecurity/service/Android.bp
new file mode 100644
index 0000000..e33abd5
--- /dev/null
+++ b/networksecurity/service/Android.bp
@@ -0,0 +1,41 @@
+// 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 {
+ default_team: "trendy_team_platform_security",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Main lib for Certificate Transparency services.
+java_library {
+ name: "service-networksecurity-pre-jarjar",
+ defaults: ["framework-system-server-module-defaults"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ libs: [
+ "framework-configinfrastructure",
+ "framework-connectivity-pre-jarjar",
+ "service-connectivity-pre-jarjar",
+ ],
+
+ // This is included in service-connectivity which is 30+
+ // TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX
+ // (service-connectivity is only used on 31+) and use 31 here
+ min_sdk_version: "30",
+ sdk_version: "system_server_current",
+ apex_available: ["com.android.tethering"],
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
new file mode 100644
index 0000000..8dd5951
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
@@ -0,0 +1,67 @@
+/*
+ * 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 android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import java.util.concurrent.Executors;
+
+/** Listener class for the Certificate Transparency Phenotype flags. */
+class CertificateTransparencyFlagsListener implements DeviceConfig.OnPropertiesChangedListener {
+
+ private static final String TAG = "CertificateTransparency";
+
+ private static final String VERSION = "version";
+ private static final String CONTENT_URL = "content_url";
+ private static final String METADATA_URL = "metadata_url";
+
+ CertificateTransparencyFlagsListener(Context context) {}
+
+ void initialize() {
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_TETHERING, Executors.newSingleThreadExecutor(), this);
+ // TODO: handle property changes triggering on boot before registering this listener.
+ }
+
+ @Override
+ public void onPropertiesChanged(Properties properties) {
+ if (!SdkLevel.isAtLeastV() || !NAMESPACE_TETHERING.equals(properties.getNamespace())) {
+ return;
+ }
+
+ String newVersion = DeviceConfig.getString(NAMESPACE_TETHERING, VERSION, "");
+ String newContentUrl = DeviceConfig.getString(NAMESPACE_TETHERING, CONTENT_URL, "");
+ String newMetadataUrl = DeviceConfig.getString(NAMESPACE_TETHERING, METADATA_URL, "");
+ if (TextUtils.isEmpty(newVersion)
+ || TextUtils.isEmpty(newContentUrl)
+ || TextUtils.isEmpty(newMetadataUrl)) {
+ return;
+ }
+
+ Log.d(TAG, "newVersion=" + newVersion);
+ Log.d(TAG, "newContentUrl=" + newContentUrl);
+ Log.d(TAG, "newMetadataUrl=" + newMetadataUrl);
+ // TODO: start download of URLs.
+ }
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
new file mode 100644
index 0000000..406a57f
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.annotation.RequiresApi;
+import android.content.Context;
+import android.net.ct.ICertificateTransparencyManager;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.net.ct.flags.Flags;
+import com.android.net.module.util.DeviceConfigUtils;
+import com.android.server.SystemService;
+
+/** Implementation of the Certificate Transparency service. */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public class CertificateTransparencyService extends ICertificateTransparencyManager.Stub {
+
+ private static final String TAG = "CertificateTransparency";
+ private static final String CERTIFICATE_TRANSPARENCY_ENABLED =
+ "certificate_transparency_service_enabled";
+
+ private final CertificateTransparencyFlagsListener mFlagsListener;
+
+ /**
+ * @return true if the CertificateTransparency service is enabled.
+ */
+ public static boolean enabled(Context context) {
+ // TODO: replace isTetheringFeatureEnabled with CT namespace flag.
+ return DeviceConfigUtils.isTetheringFeatureEnabled(
+ context, CERTIFICATE_TRANSPARENCY_ENABLED)
+ && Flags.certificateTransparencyService();
+ }
+
+ /** Creates a new {@link CertificateTransparencyService} object. */
+ public CertificateTransparencyService(Context context) {
+ mFlagsListener = new CertificateTransparencyFlagsListener(context);
+ }
+
+ /**
+ * Called by {@link com.android.server.ConnectivityServiceInitializer}.
+ *
+ * @see com.android.server.SystemService#onBootPhase
+ */
+ public void onBootPhase(int phase) {
+
+ switch (phase) {
+ case SystemService.PHASE_BOOT_COMPLETED:
+ Log.d(TAG, "setting up flags listeners");
+ mFlagsListener.initialize();
+ break;
+ default:
+ }
+ }
+}
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 779f354..e00b7cf 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -59,6 +59,7 @@
"framework-wifi",
"service-connectivity-pre-jarjar",
"service-nearby-pre-jarjar",
+ "service-networksecurity-pre-jarjar",
"service-thread-pre-jarjar",
service_remoteauth_pre_jarjar_lib,
"ServiceConnectivityResources",
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index 1ac2f6e..5d23fdc 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -28,6 +28,7 @@
import com.android.server.ethernet.EthernetService;
import com.android.server.ethernet.EthernetServiceImpl;
import com.android.server.nearby.NearbyService;
+import com.android.server.net.ct.CertificateTransparencyService;
import com.android.server.thread.ThreadNetworkService;
/**
@@ -43,6 +44,7 @@
private final NearbyService mNearbyService;
private final EthernetServiceImpl mEthernetServiceImpl;
private final ThreadNetworkService mThreadNetworkService;
+ private final CertificateTransparencyService mCertificateTransparencyService;
public ConnectivityServiceInitializer(Context context) {
super(context);
@@ -55,6 +57,7 @@
mNsdService = createNsdService(context);
mNearbyService = createNearbyService(context);
mThreadNetworkService = createThreadNetworkService(context);
+ mCertificateTransparencyService = createCertificateTransparencyService(context);
}
@Override
@@ -111,6 +114,10 @@
if (mThreadNetworkService != null) {
mThreadNetworkService.onBootPhase(phase);
}
+
+ if (SdkLevel.isAtLeastV() && mCertificateTransparencyService != null) {
+ mCertificateTransparencyService.onBootPhase(phase);
+ }
}
/**
@@ -186,4 +193,13 @@
}
return new ThreadNetworkService(context);
}
+
+ /** Return CertificateTransparencyService instance if enable, otherwise null. */
+ @Nullable
+ private CertificateTransparencyService createCertificateTransparencyService(
+ final Context context) {
+ return SdkLevel.isAtLeastV() && CertificateTransparencyService.enabled(context)
+ ? new CertificateTransparencyService(context)
+ : null;
+ }
}
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 8123d27..7fa605a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -390,12 +390,18 @@
*/
public void dump(PrintWriter pw) {
discoveryExecutor.checkAndRunOnHandlerThread(() -> {
- pw.println();
+ pw.println("Clients:");
// Dump ServiceTypeClients
for (MdnsServiceTypeClient serviceTypeClient
: perSocketServiceTypeClients.getAllMdnsServiceTypeClient()) {
serviceTypeClient.dump(pw);
}
+ pw.println();
+ // Dump ServiceCache
+ pw.println("Cached services:");
+ if (serviceCache != null) {
+ serviceCache.dump(pw, " ");
+ }
});
}
}
\ No newline at end of file
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 ebd95c9..c3cb776 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -228,12 +228,12 @@
* Create a ServiceRegistration with only update the subType.
*/
ServiceRegistration withSubtypes(@NonNull Set<String> newSubtypes,
- boolean avoidEmptyTxtRecords) {
+ @NonNull MdnsFeatureFlags featureFlags) {
NsdServiceInfo newServiceInfo = new NsdServiceInfo(serviceInfo);
newServiceInfo.setSubtypes(newSubtypes);
return new ServiceRegistration(srvRecord.record.getServiceHost(), newServiceInfo,
repliedServiceCount, sentPacketCount, exiting, isProbing, ttl,
- avoidEmptyTxtRecords);
+ featureFlags);
}
/**
@@ -241,7 +241,7 @@
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing,
- @Nullable Duration ttl, boolean avoidEmptyTxtRecords) {
+ @Nullable Duration ttl, @NonNull MdnsFeatureFlags featureFlags) {
this.serviceInfo = serviceInfo;
final long nonNameRecordsTtlMillis;
@@ -313,7 +313,7 @@
true /* cacheFlush */,
nonNameRecordsTtlMillis,
attrsToTextEntries(
- serviceInfo.getAttributes(), avoidEmptyTxtRecords)),
+ serviceInfo.getAttributes(), featureFlags)),
false /* sharedName */);
allRecords.addAll(ptrRecords);
@@ -397,9 +397,9 @@
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
int repliedServiceCount, int sentPacketCount, @Nullable Duration ttl,
- boolean avoidEmptyTxtRecords) {
+ @NonNull MdnsFeatureFlags featureFlags) {
this(deviceHostname, serviceInfo,repliedServiceCount, sentPacketCount,
- false /* exiting */, true /* isProbing */, ttl, avoidEmptyTxtRecords);
+ false /* exiting */, true /* isProbing */, ttl, featureFlags);
}
void setProbing(boolean probing) {
@@ -450,7 +450,7 @@
"Service ID must already exist for an update request: " + serviceId);
}
final ServiceRegistration updatedRegistration = existingRegistration.withSubtypes(
- subtypes, mMdnsFeatureFlags.avoidAdvertisingEmptyTxtRecords());
+ subtypes, mMdnsFeatureFlags);
mServices.put(serviceId, updatedRegistration);
}
@@ -482,7 +482,7 @@
final ServiceRegistration registration = new ServiceRegistration(
mDeviceHostname, serviceInfo, NO_PACKET /* repliedServiceCount */,
NO_PACKET /* sentPacketCount */, ttl,
- mMdnsFeatureFlags.avoidAdvertisingEmptyTxtRecords());
+ mMdnsFeatureFlags);
mServices.put(serviceId, registration);
// Remove existing exiting service
@@ -553,14 +553,14 @@
return new MdnsProber.ProbingInfo(serviceId, probingRecords);
}
- private static List<MdnsServiceInfo.TextEntry> attrsToTextEntries(Map<String, byte[]> attrs,
- boolean avoidEmptyTxtRecords) {
+ private static List<MdnsServiceInfo.TextEntry> attrsToTextEntries(
+ @NonNull Map<String, byte[]> attrs, @NonNull MdnsFeatureFlags featureFlags) {
final List<MdnsServiceInfo.TextEntry> out = new ArrayList<>(
attrs.size() == 0 ? 1 : attrs.size());
- if (avoidEmptyTxtRecords && attrs.size() == 0) {
+ if (featureFlags.avoidAdvertisingEmptyTxtRecords() && attrs.size() == 0) {
// As per RFC6763 6.1, empty TXT records are not allowed, but records containing a
// single empty String must be treated as equivalent.
- out.add(new MdnsServiceInfo.TextEntry("", (byte[]) null));
+ out.add(new MdnsServiceInfo.TextEntry("", MdnsServiceInfo.TextEntry.VALUE_NONE));
return out;
}
@@ -1418,7 +1418,7 @@
final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
existing.repliedServiceCount, existing.sentPacketCount, existing.ttl,
- mMdnsFeatureFlags.avoidAdvertisingEmptyTxtRecords());
+ mMdnsFeatureFlags);
mServices.put(serviceId, newService);
return makeProbingInfo(serviceId, newService);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
index 3636644..2957da5 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -19,11 +19,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
+import android.os.SystemClock;
+import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.DnsUtils;
import java.io.IOException;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -426,4 +429,18 @@
return count;
}
+
+ @Override
+ public String toString() {
+ return "Name: " + TextUtils.join(".", serviceName)
+ + ", pointerRecords: " + pointerRecords
+ + ", serviceRecord: " + serviceRecord
+ + ", textRecord: " + textRecord
+ + ", inet4AddressRecords: " + inet4AddressRecords
+ + ", inet6AddressRecords: " + inet6AddressRecords
+ + ", interfaceIndex: " + interfaceIndex
+ + ", network: " + network
+ + ", lastUpdateTime: " + Instant.now().minusMillis(
+ SystemClock.elapsedRealtime() - lastUpdateTime);
+ }
}
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 a8a4ef1..591ed8b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
@@ -32,6 +32,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.connectivity.mdns.util.MdnsUtils;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -71,6 +72,11 @@
return Objects.equals(mUpperCaseServiceType, ((CacheKey) other).mUpperCaseServiceType)
&& Objects.equals(mSocketKey, ((CacheKey) other).mSocketKey);
}
+
+ @Override
+ public String toString() {
+ return "CacheKey{ ServiceType=" + mUpperCaseServiceType + ", " + mSocketKey + " }";
+ }
}
/**
* A map of cached services. Key is composed of service type and socket. Value is the list of
@@ -353,6 +359,22 @@
mNextExpirationTime = getNextExpirationTime(now);
}
+ /**
+ * Dump ServiceCache state.
+ */
+ public void dump(PrintWriter pw, String indent) {
+ ensureRunningOnHandlerThread(mHandler);
+ // IndentingPrintWriter cannot be used on the mDNS stack build. So, manually add an indent.
+ for (int i = 0; i < mCachedServices.size(); i++) {
+ final CacheKey key = mCachedServices.keyAt(i);
+ pw.println(indent + key);
+ for (MdnsResponse response : mCachedServices.valueAt(i)) {
+ pw.println(indent + " Response{ " + response + " }");
+ }
+ pw.println();
+ }
+ }
+
/*** Callbacks for listening service expiration */
public interface ServiceExpiredCallback {
/*** Notify the service is expired */
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 114cf2e..11343d2 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -74,12 +74,12 @@
import static com.android.net.module.util.DeviceConfigUtils.getDeviceConfigPropertyInt;
import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
-import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_PERIODIC;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_DUMPSYS;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_FORCE_UPDATE;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_GLOBAL_ALERT;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_NETWORK_STATUS_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_OPEN_SESSION;
+import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_PERIODIC;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_REG_CALLBACK;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_REMOVE_UIDS;
@@ -242,13 +242,11 @@
// A message for broadcasting ACTION_NETWORK_STATS_UPDATED in handler thread to prevent
// deadlock.
private static final int MSG_BROADCAST_NETWORK_STATS_UPDATED = 4;
-
/** Flags to control detail level of poll event. */
private static final int FLAG_PERSIST_NETWORK = 0x1;
private static final int FLAG_PERSIST_UID = 0x2;
private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
private static final int FLAG_PERSIST_FORCE = 0x100;
-
/**
* When global alert quota is high, wait for this delay before processing each polling,
* and do not schedule further polls once there is already one queued.
@@ -313,6 +311,12 @@
static final String TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME = "trafficstats_cache_max_entries";
static final int DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS = 1000;
static final int DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES = 400;
+ /**
+ * The delay time between to network stats update intents.
+ * Added to fix intent spams (b/3115462)
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ static final int BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS = 1000;
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
@@ -385,6 +389,7 @@
long getXtPersistBytes(long def);
long getUidPersistBytes(long def);
long getUidTagPersistBytes(long def);
+ long getBroadcastNetworkStatsUpdateDelayMs();
}
private final Object mStatsLock = new Object();
@@ -469,15 +474,36 @@
private long mLastStatsSessionPoll;
+ /**
+ * The timestamp of the most recent network stats broadcast.
+ *
+ * Note that this time could be in the past for completed broadcasts,
+ * or in the future for scheduled broadcasts.
+ *
+ * It is initialized to {@code Long.MIN_VALUE} to ensure that the first broadcast request
+ * is fulfilled immediately, regardless of the delay time.
+ *
+ * This value is used to enforce rate limiting on intents, preventing intent spam.
+ */
+ @GuardedBy("mStatsLock")
+ private long mLatestNetworkStatsUpdatedBroadcastScheduledTime = Long.MIN_VALUE;
+
+
private final TrafficStatsRateLimitCache mTrafficStatsTotalCache;
private final TrafficStatsRateLimitCache mTrafficStatsIfaceCache;
private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
static final String TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_rate_limit_cache_enabled_flag";
+ static final String BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG =
+ "broadcast_network_stats_updated_rate_limit_enabled_flag";
private final boolean mAlwaysUseTrafficStatsRateLimitCache;
private final int mTrafficStatsRateLimitCacheExpiryDuration;
private final int mTrafficStatsRateLimitCacheMaxEntries;
+ private final boolean mBroadcastNetworkStatsUpdatedRateLimitEnabled;
+
+
+
private final Object mOpenSessionCallsLock = new Object();
/**
@@ -669,6 +695,8 @@
mAlwaysUseTrafficStatsRateLimitCache =
mDeps.alwaysUseTrafficStatsRateLimitCache(mContext);
+ mBroadcastNetworkStatsUpdatedRateLimitEnabled =
+ mDeps.enabledBroadcastNetworkStatsUpdatedRateLimiting(mContext);
mTrafficStatsRateLimitCacheExpiryDuration =
mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
mTrafficStatsRateLimitCacheMaxEntries =
@@ -696,6 +724,15 @@
@VisibleForTesting
public static class Dependencies {
/**
+ * Get broadcast network stats updated delay time in ms
+ * @return
+ */
+ @NonNull
+ public long getBroadcastNetworkStatsUpdateDelayMs() {
+ return BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS;
+ }
+
+ /**
* Get legacy platform stats directory.
*/
@NonNull
@@ -927,6 +964,17 @@
}
/**
+ * Get whether broadcast network stats update rate limiting is enabled.
+ *
+ * This method should only be called once in the constructor,
+ * to ensure that the code does not need to deal with flag values changing at runtime.
+ */
+ public boolean enabledBroadcastNetworkStatsUpdatedRateLimiting(Context ctx) {
+ return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
+ ctx, BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG);
+ }
+
+ /**
* Get whether TrafficStats rate-limit cache is always applied.
*
* This method should only be called once in the constructor,
@@ -2645,8 +2693,22 @@
performSampleLocked();
}
- // finally, dispatch updated event to any listeners
- mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED));
+ // Dispatch updated event to listeners, preventing intent spamming
+ // (b/343844995) possibly from abnormal modem RAT changes or misbehaving
+ // app calls (see NetworkStatsEventLogger#POLL_REASON_* for possible reasons).
+ // If no broadcasts are scheduled, use the time of the last broadcast
+ // to schedule the next one ASAP.
+ if (!mBroadcastNetworkStatsUpdatedRateLimitEnabled) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED));
+ } else if (mLatestNetworkStatsUpdatedBroadcastScheduledTime < SystemClock.uptimeMillis()) {
+ mLatestNetworkStatsUpdatedBroadcastScheduledTime = Math.max(
+ mLatestNetworkStatsUpdatedBroadcastScheduledTime
+ + mSettings.getBroadcastNetworkStatsUpdateDelayMs(),
+ SystemClock.uptimeMillis()
+ );
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED),
+ mLatestNetworkStatsUpdatedBroadcastScheduledTime);
+ }
Trace.traceEnd(TRACE_TAG_NETWORK);
}
@@ -3605,6 +3667,11 @@
public long getUidTagPersistBytes(long def) {
return def;
}
+
+ @Override
+ public long getBroadcastNetworkStatsUpdateDelayMs() {
+ return BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS;
+ }
}
// TODO: Read stats by using BpfNetMapsReader.
diff --git a/service/Android.bp b/service/Android.bp
index 1a0e045..c68f0b8 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -206,6 +206,7 @@
},
visibility: [
"//packages/modules/Connectivity/service-t",
+ "//packages/modules/Connectivity/networksecurity:__subpackages__",
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/thread/service:__subpackages__",
"//packages/modules/Connectivity/thread/tests:__subpackages__",
@@ -247,6 +248,7 @@
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
"service-nearby-pre-jarjar",
+ "service-networksecurity-pre-jarjar",
service_remoteauth_pre_jarjar_lib,
"service-thread-pre-jarjar",
],
@@ -315,6 +317,7 @@
":framework-connectivity-jarjar-rules",
":service-connectivity-jarjar-gen",
":service-nearby-jarjar-gen",
+ ":service-networksecurity-jarjar-gen",
":service-remoteauth-jarjar-gen",
":service-thread-jarjar-gen",
],
@@ -404,6 +407,24 @@
visibility: ["//visibility:private"],
}
+java_genrule {
+ name: "service-networksecurity-jarjar-gen",
+ tool_files: [
+ ":service-networksecurity-pre-jarjar{.jar}",
+ "jarjar-excludes.txt",
+ ],
+ tools: [
+ "jarjar-rules-generator",
+ ],
+ out: ["service_ct_jarjar_rules.txt"],
+ cmd: "$(location jarjar-rules-generator) " +
+ "$(location :service-networksecurity-pre-jarjar{.jar}) " +
+ "--prefix com.android.server.net.ct " +
+ "--excludes $(location jarjar-excludes.txt) " +
+ "--output $(out)",
+ visibility: ["//visibility:private"],
+}
+
genrule {
name: "statslog-connectivity-java-gen",
tools: ["stats-log-api-gen"],
diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java
index ac02229..b95e3b1 100644
--- a/service/src/com/android/server/connectivity/DnsManager.java
+++ b/service/src/com/android/server/connectivity/DnsManager.java
@@ -41,6 +41,7 @@
import android.net.NetworkCapabilities;
import android.net.ResolverParamsParcel;
import android.net.Uri;
+import android.net.resolv.aidl.DohParamsParcel;
import android.net.shared.PrivateDnsConfig;
import android.os.Binder;
import android.os.RemoteException;
@@ -52,16 +53,17 @@
import android.util.Pair;
import java.net.InetAddress;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
/**
* Encapsulate the management of DNS settings for networks.
@@ -382,15 +384,14 @@
paramsParcel.domains = getDomainStrings(lp.getDomains());
paramsParcel.tlsName = strictMode ? privateDnsCfg.hostname : "";
paramsParcel.tlsServers =
- strictMode ? makeStrings(
- Arrays.stream(privateDnsCfg.ips)
- .filter((ip) -> lp.isReachable(ip))
- .collect(Collectors.toList()))
+ strictMode ? makeStrings(getReachableAddressList(privateDnsCfg.ips, lp))
: useTls ? paramsParcel.servers // Opportunistic
: new String[0]; // Off
paramsParcel.transportTypes = nc.getTransportTypes();
paramsParcel.meteredNetwork = nc.isMetered();
paramsParcel.interfaceNames = lp.getAllInterfaceNames().toArray(new String[0]);
+ paramsParcel.dohParams = makeDohParamsParcel(privateDnsCfg, lp);
+
// Prepare to track the validation status of the DNS servers in the
// resolver config when private DNS is in opportunistic or strict mode.
if (useTls) {
@@ -404,15 +405,16 @@
}
Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
- + "%d, %d, %s, %s, %s, %b, %s)", paramsParcel.netId,
+ + "%d, %d, %s, %s, %s, %b, %s, %s, %s, %s, %d)", paramsParcel.netId,
Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains),
paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold,
paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
paramsParcel.retryCount, paramsParcel.tlsName,
Arrays.toString(paramsParcel.tlsServers),
Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork,
- Arrays.toString(paramsParcel.interfaceNames)));
-
+ Arrays.toString(paramsParcel.interfaceNames),
+ paramsParcel.dohParams.name, Arrays.toString(paramsParcel.dohParams.ips),
+ paramsParcel.dohParams.dohpath, paramsParcel.dohParams.port));
try {
mDnsResolver.setResolverConfiguration(paramsParcel);
} catch (RemoteException | ServiceSpecificException e) {
@@ -498,4 +500,26 @@
private static String[] getDomainStrings(String domains) {
return (TextUtils.isEmpty(domains)) ? new String[0] : domains.split(" ");
}
+
+ @NonNull
+ private List<InetAddress> getReachableAddressList(@NonNull InetAddress[] ips,
+ @NonNull LinkProperties lp) {
+ final ArrayList<InetAddress> out = new ArrayList<InetAddress>(Arrays.asList(ips));
+ out.removeIf(ip -> !lp.isReachable(ip));
+ return out;
+ }
+
+ @NonNull
+ private DohParamsParcel makeDohParamsParcel(@NonNull PrivateDnsConfig cfg,
+ @NonNull LinkProperties lp) {
+ if (cfg.mode == PRIVATE_DNS_MODE_OFF) {
+ return new DohParamsParcel.Builder().build();
+ }
+ return new DohParamsParcel.Builder()
+ .setName(cfg.dohName)
+ .setIps(makeStrings(getReachableAddressList(cfg.dohIps, lp)))
+ .setDohpath(cfg.dohPath)
+ .setPort(cfg.dohPort)
+ .build();
+ }
}
diff --git a/staticlibs/testutils/host/python/wifip2p_utils.py b/staticlibs/testutils/host/python/wifip2p_utils.py
index 8b4ffa5..ef6af75 100644
--- a/staticlibs/testutils/host/python/wifip2p_utils.py
+++ b/staticlibs/testutils/host/python/wifip2p_utils.py
@@ -14,11 +14,13 @@
from mobly import asserts
from mobly.controllers import android_device
+from net_tests_utils.host.python import tether_utils
def assume_wifi_p2p_test_preconditions(
server_device: android_device, client_device: android_device
) -> None:
+ """Preconditions check for running Wi-Fi P2P test."""
server = server_device.connectivity_multi_devices_snippet
client = client_device.connectivity_multi_devices_snippet
@@ -36,10 +38,51 @@
def setup_wifi_p2p_server_and_client(
server_device: android_device, client_device: android_device
) -> None:
- """Set up the Wi-Fi P2P server and client."""
+ """Set up the Wi-Fi P2P server and client, then connect them to establish a Wi-Fi P2P connection."""
+ server = server_device.connectivity_multi_devices_snippet
+ client = client_device.connectivity_multi_devices_snippet
+
# Start Wi-Fi P2P on both server and client.
- server_device.connectivity_multi_devices_snippet.startWifiP2p()
- client_device.connectivity_multi_devices_snippet.startWifiP2p()
+ server.startWifiP2p()
+ client.startWifiP2p()
+
+ # Get the current device name
+ server_name = server.getDeviceName()
+ client_name = client.getDeviceName()
+
+ # Generate Wi-Fi P2P group passphrase with random characters.
+ group_name = "DIRECT-" + tether_utils.generate_uuid32_base64()
+ group_passphrase = tether_utils.generate_uuid32_base64()
+
+ # Server creates a Wi-Fi P2P group
+ server.createGroup(group_name, group_passphrase)
+
+ # Start Wi-Fi P2p peers discovery on both devices
+ server.startPeersDiscovery()
+ client.startPeersDiscovery()
+
+ # Ensure the target device has been discovered
+ server_address = client.ensureDeviceDiscovered(server_name)
+ client_address = server.ensureDeviceDiscovered(client_name)
+
+ # Server invites the device to the group
+ server.inviteDeviceToGroup(group_name, group_passphrase, client_address)
+
+ # Wait for a p2p connection changed intent to ensure the invitation has been
+ # received.
+ client.waitForP2pConnectionChanged(True, group_name)
+ # Accept the group invitation
+ client.acceptGroupInvitation(server_address)
+
+ # Server waits for connection request from client and accept joining
+ server.waitForPeerConnectionRequestAndAcceptJoining(client_address)
+
+ # Wait for a p2p connection changed intent to ensure joining the group
+ client.waitForP2pConnectionChanged(False, group_name)
+
+ # Ensure Wi-Fi P2P connected on both devices
+ client.ensureDeviceConnected(server_name)
+ server.ensureDeviceConnected(client_name)
def cleanup_wifi_p2p(
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index eceb535..416a2e8 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -13,6 +13,7 @@
# limitations under the License.
from net_tests_utils.host.python import mdns_utils, multi_devices_test_base, tether_utils
+from net_tests_utils.host.python import wifip2p_utils
from net_tests_utils.host.python.tether_utils import UpstreamType
@@ -68,3 +69,21 @@
tether_utils.cleanup_tethering_for_upstream_type(
self.serverDevice, UpstreamType.NONE
)
+
+ def test_mdns_via_wifip2p(self):
+ wifip2p_utils.assume_wifi_p2p_test_preconditions(
+ self.serverDevice, self.clientDevice
+ )
+ mdns_utils.assume_mdns_test_preconditions(
+ self.clientDevice, self.serverDevice
+ )
+ try:
+ wifip2p_utils.setup_wifi_p2p_server_and_client(
+ self.serverDevice, self.clientDevice
+ )
+ mdns_utils.register_mdns_service_and_discover_resolve(
+ self.clientDevice, self.serverDevice
+ )
+ finally:
+ mdns_utils.cleanup_mdns_service(self.clientDevice, self.serverDevice)
+ wifip2p_utils.cleanup_wifi_p2p(self.serverDevice, self.clientDevice)
diff --git a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
index e0929bb..f8c9351 100644
--- a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
@@ -16,13 +16,28 @@
package com.google.snippet.connectivity
+import android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.MacAddress
import android.net.wifi.WifiManager
+import android.net.wifi.p2p.WifiP2pConfig
+import android.net.wifi.p2p.WifiP2pDevice
+import android.net.wifi.p2p.WifiP2pDeviceList
+import android.net.wifi.p2p.WifiP2pGroup
import android.net.wifi.p2p.WifiP2pManager
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.testutils.runAsShell
import com.google.android.mobly.snippet.Snippet
import com.google.android.mobly.snippet.rpc.Rpc
+import com.google.snippet.connectivity.Wifip2pMultiDevicesSnippet.Wifip2pIntentReceiver.IntentReceivedEvent.ConnectionChanged
+import com.google.snippet.connectivity.Wifip2pMultiDevicesSnippet.Wifip2pIntentReceiver.IntentReceivedEvent.PeersChanged
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
+import kotlin.test.assertNotNull
import kotlin.test.fail
private const val TIMEOUT_MS = 60000L
@@ -38,6 +53,35 @@
?: fail("Could not get WifiP2pManager service")
}
private lateinit var wifip2pChannel: WifiP2pManager.Channel
+ private val wifip2pIntentReceiver = Wifip2pIntentReceiver()
+
+ private class Wifip2pIntentReceiver : BroadcastReceiver() {
+ val history = ArrayTrackRecord<IntentReceivedEvent>().newReadHead()
+
+ sealed class IntentReceivedEvent {
+ abstract val intent: Intent
+ data class ConnectionChanged(override val intent: Intent) : IntentReceivedEvent()
+ data class PeersChanged(override val intent: Intent) : IntentReceivedEvent()
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ when (intent.action) {
+ WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
+ history.add(ConnectionChanged(intent))
+ }
+ WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
+ history.add(PeersChanged(intent))
+ }
+ }
+ }
+
+ inline fun <reified T : IntentReceivedEvent> eventuallyExpectedIntent(
+ timeoutMs: Long = TIMEOUT_MS,
+ crossinline predicate: (T) -> Boolean = { true }
+ ): T = history.poll(timeoutMs) { it is T && predicate(it) }.also {
+ assertNotNull(it, "Intent ${T::class} not received within ${timeoutMs}ms.")
+ } as T
+ }
@Rpc(description = "Check whether the device supports Wi-Fi P2P.")
fun isP2pSupported() = wifiManager.isP2pSupported()
@@ -55,6 +99,10 @@
}
}
p2pStateEnabledFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ // Register an intent filter to receive Wi-Fi P2P intents
+ val filter = IntentFilter(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
+ filter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
+ context.registerReceiver(wifip2pIntentReceiver, filter)
}
@Rpc(description = "Stop Wi-Fi P2P")
@@ -63,5 +111,202 @@
wifip2pManager.cancelConnect(wifip2pChannel, null)
wifip2pManager.removeGroup(wifip2pChannel, null)
}
+ // Unregister the intent filter
+ context.unregisterReceiver(wifip2pIntentReceiver)
+ }
+
+ @Rpc(description = "Get the current device name")
+ fun getDeviceName(): String {
+ // Retrieve current device info
+ val deviceFuture = CompletableFuture<String>()
+ wifip2pManager.requestDeviceInfo(wifip2pChannel) { wifiP2pDevice ->
+ if (wifiP2pDevice != null) {
+ deviceFuture.complete(wifiP2pDevice.deviceName)
+ }
+ }
+ // Return current device name
+ return deviceFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ @Rpc(description = "Wait for a p2p connection changed intent and check the group")
+ @Suppress("DEPRECATION")
+ fun waitForP2pConnectionChanged(ignoreGroupCheck: Boolean, groupName: String) {
+ wifip2pIntentReceiver.eventuallyExpectedIntent<ConnectionChanged>() {
+ val p2pGroup: WifiP2pGroup? =
+ it.intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)
+ val groupMatched = p2pGroup?.networkName == groupName
+ return@eventuallyExpectedIntent ignoreGroupCheck || groupMatched
+ }
+ }
+
+ @Rpc(description = "Create a Wi-Fi P2P group")
+ fun createGroup(groupName: String, groupPassphrase: String) {
+ // Create a Wi-Fi P2P group
+ val wifip2pConfig = WifiP2pConfig.Builder()
+ .setNetworkName(groupName)
+ .setPassphrase(groupPassphrase)
+ .build()
+ val createGroupFuture = CompletableFuture<Boolean>()
+ wifip2pManager.createGroup(
+ wifip2pChannel,
+ wifip2pConfig,
+ object : WifiP2pManager.ActionListener {
+ override fun onFailure(reason: Int) = Unit
+ override fun onSuccess() { createGroupFuture.complete(true) }
+ }
+ )
+ createGroupFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+
+ // Ensure the Wi-Fi P2P group is created.
+ waitForP2pConnectionChanged(false, groupName)
+ }
+
+ @Rpc(description = "Start Wi-Fi P2P peers discovery")
+ fun startPeersDiscovery() {
+ // Start discovery Wi-Fi P2P peers
+ wifip2pManager.discoverPeers(wifip2pChannel, null)
+
+ // Ensure the discovery is started
+ val p2pDiscoveryStartedFuture = CompletableFuture<Boolean>()
+ wifip2pManager.requestDiscoveryState(wifip2pChannel) { state ->
+ if (state == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) {
+ p2pDiscoveryStartedFuture.complete(true)
+ }
+ }
+ p2pDiscoveryStartedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ /**
+ * Get the device address from the given intent that matches the given device name.
+ *
+ * @param peersChangedIntent the intent to get the device address from
+ * @param deviceName the target device name
+ * @return the address of the target device or null if no devices match.
+ */
+ @Suppress("DEPRECATION")
+ private fun getDeviceAddress(peersChangedIntent: Intent, deviceName: String): String? {
+ val peers: WifiP2pDeviceList? =
+ peersChangedIntent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST)
+ return peers?.deviceList?.firstOrNull { it.deviceName == deviceName }?.deviceAddress
+ }
+
+ /**
+ * Ensure the given device has been discovered and returns the associated device address for
+ * connection.
+ *
+ * @param deviceName the target device name
+ * @return the address of the target device.
+ */
+ @Rpc(description = "Ensure the target Wi-Fi P2P device is discovered")
+ fun ensureDeviceDiscovered(deviceName: String): String {
+ val changedEvent = wifip2pIntentReceiver.eventuallyExpectedIntent<PeersChanged>() {
+ return@eventuallyExpectedIntent getDeviceAddress(it.intent, deviceName) != null
+ }
+ return getDeviceAddress(changedEvent.intent, deviceName)
+ ?: fail("Missing device in filtered intent")
+ }
+
+ @Rpc(description = "Invite a Wi-Fi P2P device to the group")
+ fun inviteDeviceToGroup(groupName: String, groupPassphrase: String, deviceAddress: String) {
+ // Connect to the device to send invitation
+ val wifip2pConfig = WifiP2pConfig.Builder()
+ .setNetworkName(groupName)
+ .setPassphrase(groupPassphrase)
+ .setDeviceAddress(MacAddress.fromString(deviceAddress))
+ .build()
+ val connectedFuture = CompletableFuture<Boolean>()
+ wifip2pManager.connect(
+ wifip2pChannel,
+ wifip2pConfig,
+ object : WifiP2pManager.ActionListener {
+ override fun onFailure(reason: Int) = Unit
+ override fun onSuccess() {
+ connectedFuture.complete(true)
+ }
+ }
+ )
+ connectedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ private fun runExternalApproverForGroupProcess(
+ deviceAddress: String,
+ isGroupInvitation: Boolean
+ ) {
+ val peer = MacAddress.fromString(deviceAddress)
+ runAsShell(MANAGE_WIFI_NETWORK_SELECTION) {
+ val connectionRequestFuture = CompletableFuture<Boolean>()
+ val attachedFuture = CompletableFuture<Boolean>()
+ wifip2pManager.addExternalApprover(
+ wifip2pChannel,
+ peer,
+ object : WifiP2pManager.ExternalApproverRequestListener {
+ override fun onAttached(deviceAddress: MacAddress) {
+ attachedFuture.complete(true)
+ }
+ override fun onDetached(deviceAddress: MacAddress, reason: Int) = Unit
+ override fun onConnectionRequested(
+ requestType: Int,
+ config: WifiP2pConfig,
+ device: WifiP2pDevice
+ ) {
+ connectionRequestFuture.complete(true)
+ }
+ override fun onPinGenerated(deviceAddress: MacAddress, pin: String) = Unit
+ }
+ )
+ if (isGroupInvitation) attachedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) else
+ connectionRequestFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+
+ val resultFuture = CompletableFuture<Boolean>()
+ wifip2pManager.setConnectionRequestResult(
+ wifip2pChannel,
+ peer,
+ WifiP2pManager.CONNECTION_REQUEST_ACCEPT,
+ object : WifiP2pManager.ActionListener {
+ override fun onFailure(reason: Int) = Unit
+ override fun onSuccess() {
+ resultFuture.complete(true)
+ }
+ }
+ )
+ resultFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+
+ val removeFuture = CompletableFuture<Boolean>()
+ wifip2pManager.removeExternalApprover(
+ wifip2pChannel,
+ peer,
+ object : WifiP2pManager.ActionListener {
+ override fun onFailure(reason: Int) = Unit
+ override fun onSuccess() {
+ removeFuture.complete(true)
+ }
+ }
+ )
+ removeFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ @Rpc(description = "Accept P2P group invitation from device")
+ fun acceptGroupInvitation(deviceAddress: String) {
+ // Accept the Wi-Fi P2P group invitation
+ runExternalApproverForGroupProcess(deviceAddress, true /* isGroupInvitation */)
+ }
+
+ @Rpc(description = "Wait for connection request from the peer and accept joining")
+ fun waitForPeerConnectionRequestAndAcceptJoining(deviceAddress: String) {
+ // Wait for connection request from the peer and accept joining
+ runExternalApproverForGroupProcess(deviceAddress, false /* isGroupInvitation */)
+ }
+
+ @Rpc(description = "Ensure the target device is connected")
+ fun ensureDeviceConnected(deviceName: String) {
+ // Retrieve peers and ensure the target device is connected
+ val connectedFuture = CompletableFuture<Boolean>()
+ wifip2pManager.requestPeers(wifip2pChannel) { peers -> peers?.deviceList?.any {
+ it.deviceName == deviceName && it.status == WifiP2pDevice.CONNECTED }.let {
+ connectedFuture.complete(true)
+ }
+ }
+ connectedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
}
diff --git a/tests/cts/net/src/android/net/cts/DnsTest.java b/tests/cts/net/src/android/net/cts/DnsTest.java
index fb63a19..b1e5680 100644
--- a/tests/cts/net/src/android/net/cts/DnsTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsTest.java
@@ -16,6 +16,10 @@
package android.net.cts;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -23,10 +27,16 @@
import android.net.Network;
import android.net.NetworkInfo;
import android.os.SystemClock;
-import android.test.AndroidTestCase;
import android.util.Log;
import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -36,7 +46,9 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-public class DnsTest extends AndroidTestCase {
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRunner.RestoreDefaultNetwork
+public class DnsTest {
static {
System.loadLibrary("nativedns_jni");
@@ -46,10 +58,13 @@
private static final String TAG = "DnsTest";
private static final String PROXY_NETWORK_TYPE = "PROXY";
+ private Context mContext;
private ConnectivityManager mCm;
+ @Before
public void setUp() {
- mCm = getContext().getSystemService(ConnectivityManager.class);
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mCm = mContext.getSystemService(ConnectivityManager.class);
}
/**
@@ -69,6 +84,7 @@
* Perf - measure size of first and second tier caches and their effect
* Assert requires network permission
*/
+ @Test
@RequiresDevice // IPv6 support may be missing on presubmit virtual hardware
public void testDnsWorks() throws Exception {
ensureIpv6Connectivity();
@@ -91,7 +107,7 @@
// Skip the rest of the test if the active network for watch is PROXY.
// TODO: Check NetworkInfo type in addition to type name once ag/601257 is merged.
- if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
&& activeNetworkInfoIsProxy()) {
Log.i(TAG, "Skipping test because the active network type name is PROXY.");
return;
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index ea3d2dd..b47b97d 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -54,6 +54,7 @@
import android.net.ResolverOptionsParcel;
import android.net.ResolverParamsParcel;
import android.net.RouteInfo;
+import android.net.resolv.aidl.DohParamsParcel;
import android.net.shared.PrivateDnsConfig;
import android.os.Build;
import android.provider.Settings;
@@ -327,8 +328,16 @@
@Test
public void testSendDnsConfiguration() throws Exception {
reset(mMockDnsResolver);
- mDnsManager.updatePrivateDns(new Network(TEST_NETID),
- mDnsManager.getPrivateDnsConfig());
+ final PrivateDnsConfig cfg = new PrivateDnsConfig(
+ PRIVATE_DNS_MODE_OPPORTUNISTIC /* mode */,
+ null /* hostname */,
+ null /* ips */,
+ "doh.com" /* dohName */,
+ null /* dohIps */,
+ "/some-path{?dns}" /* dohPath */,
+ 5353 /* dohPort */);
+
+ mDnsManager.updatePrivateDns(new Network(TEST_NETID), cfg);
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(TEST_IFACENAME);
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
@@ -352,7 +361,11 @@
expectedParams.transportTypes = TEST_TRANSPORT_TYPES;
expectedParams.resolverOptions = null;
expectedParams.meteredNetwork = true;
- expectedParams.dohParams = null;
+ expectedParams.dohParams = new DohParamsParcel.Builder()
+ .setName("doh.com")
+ .setDohpath("/some-path{?dns}")
+ .setPort(5353)
+ .build();
expectedParams.interfaceNames = new String[]{TEST_IFACENAME};
verify(mMockDnsResolver, times(1)).setResolverConfiguration(eq(expectedParams));
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 7e0a225..3d2f389 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -69,6 +69,8 @@
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.PollEvent.pollReasonNameOf;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
+import static com.android.server.net.NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG;
import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES;
import static com.android.server.net.NetworkStatsService.NETSTATS_FASTDATAINPUT_FALLBACKS_COUNTER_NAME;
@@ -82,6 +84,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;
import static org.mockito.AdditionalMatchers.aryEq;
@@ -101,8 +104,10 @@
import android.annotation.NonNull;
import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -138,6 +143,7 @@
import android.provider.Settings;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
+import android.testing.TestableLooper;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
@@ -150,6 +156,7 @@
import com.android.connectivity.resources.R;
import com.android.internal.util.FileRotator;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
@@ -618,6 +625,12 @@
}
@Override
+ public boolean enabledBroadcastNetworkStatsUpdatedRateLimiting(Context ctx) {
+ return mFeatureFlags.getOrDefault(
+ BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG, true);
+ }
+
+ @Override
public int getTrafficStatsRateLimitCacheExpiryDuration() {
return DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
}
@@ -2617,6 +2630,8 @@
private void mockDefaultSettings() throws Exception {
mockSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ mSettings.setBroadcastNetworkStatsUpdateDelayMs(
+ NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS);
}
private void mockSettings(long bucketDuration, long deleteAge) {
@@ -2631,6 +2646,8 @@
@NonNull
private volatile Config mConfig;
private final AtomicBoolean mCombineSubtypeEnabled = new AtomicBoolean();
+ private long mBroadcastNetworkStatsUpdateDelayMs =
+ NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS;
TestNetworkStatsSettings(long bucketDuration, long deleteAge) {
mConfig = new Config(bucketDuration, deleteAge, deleteAge);
@@ -2693,6 +2710,15 @@
public boolean getAugmentEnabled() {
return false;
}
+
+ @Override
+ public long getBroadcastNetworkStatsUpdateDelayMs() {
+ return mBroadcastNetworkStatsUpdateDelayMs;
+ }
+
+ public void setBroadcastNetworkStatsUpdateDelayMs(long broadcastDelay) {
+ mBroadcastNetworkStatsUpdateDelayMs = broadcastDelay;
+ }
}
private void assertStatsFilesExist(boolean exist) {
@@ -3064,4 +3090,91 @@
final String dump = getDump();
assertDumpContains(dump, "Log for testing");
}
+
+ private static class TestNetworkStatsUpdatedReceiver extends BroadcastReceiver {
+ private final ArrayTrackRecord<Intent>.ReadHead mHistory;
+
+ TestNetworkStatsUpdatedReceiver() {
+ mHistory = (new ArrayTrackRecord<Intent>()).newReadHead();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mHistory.add(intent);
+ }
+
+ /**
+ * Assert no broadcast intent is received in blocking manner
+ */
+ public void assertNoBroadcastIntentReceived() {
+ assertNull(mHistory.peek());
+ }
+
+ /**
+ * Assert an intent is received and remove it from queue
+ */
+ public void assertBroadcastIntentReceived() {
+ assertNotNull(mHistory.poll(WAIT_TIMEOUT, number -> true));
+ }
+ }
+
+ @FeatureFlag(name = BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG)
+ @Test
+ public void testNetworkStatsUpdatedIntentSpam_rateLimitOn() throws Exception {
+ // Set the update delay long enough that messages won't be processed before unblocked
+ // Set a short time to test the behavior before reaching delay.
+ // Constraint: test running time < toleranceMs < update delay time
+ mSettings.setBroadcastNetworkStatsUpdateDelayMs(100_000L);
+ final long toleranceMs = 5000;
+
+ final TestableLooper mTestableLooper = new TestableLooper(mHandlerThread.getLooper());
+ final TestNetworkStatsUpdatedReceiver receiver = new TestNetworkStatsUpdatedReceiver();
+ mServiceContext.registerReceiver(receiver, new IntentFilter(ACTION_NETWORK_STATS_UPDATED));
+
+ try {
+ // Test that before anything, the intent is delivered immediately
+ mService.forceUpdate();
+ mTestableLooper.processAllMessages();
+ receiver.assertBroadcastIntentReceived();
+ receiver.assertNoBroadcastIntentReceived();
+
+ // Test that the next two intents results in exactly one intent delivered
+ for (int i = 0; i < 2; i++) {
+ mService.forceUpdate();
+ }
+ // Test that the delay depends on our set value
+ mTestableLooper.moveTimeForward(mSettings.getBroadcastNetworkStatsUpdateDelayMs()
+ - toleranceMs);
+ mTestableLooper.processAllMessages();
+ receiver.assertNoBroadcastIntentReceived();
+
+ // Unblock messages and test that the second and third update
+ // is broadcasted right after the delay
+ mTestableLooper.moveTimeForward(toleranceMs);
+ mTestableLooper.processAllMessages();
+ receiver.assertBroadcastIntentReceived();
+ receiver.assertNoBroadcastIntentReceived();
+
+ } finally {
+ mTestableLooper.destroy();
+ }
+ }
+
+ @FeatureFlag(name = BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG, enabled = false)
+ @Test
+ public void testNetworkStatsUpdatedIntentSpam_rateLimitOff() throws Exception {
+ // Set the update delay long enough to ensure that messages are processed
+ // despite the rate limit.
+ mSettings.setBroadcastNetworkStatsUpdateDelayMs(100_000L);
+
+ final TestNetworkStatsUpdatedReceiver receiver = new TestNetworkStatsUpdatedReceiver();
+ mServiceContext.registerReceiver(receiver, new IntentFilter(ACTION_NETWORK_STATS_UPDATED));
+
+ for (int i = 0; i < 2; i++) {
+ mService.forceUpdate();
+ waitForIdle();
+ receiver.assertBroadcastIntentReceived();
+ }
+ receiver.assertNoBroadcastIntentReceived();
+ }
}
diff --git a/thread/framework/java/android/net/thread/ChannelMaxPower.aidl b/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
index bcda8a8..abc00b9 100644
--- a/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
+++ b/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
@@ -16,11 +16,11 @@
package android.net.thread;
- /**
- * Mapping from a channel to its max power.
- *
- * {@hide}
- */
+/**
+ * Mapping from a channel to its max power.
+ *
+ * {@hide}
+ */
parcelable ChannelMaxPower {
int channel; // The Thread radio channel.
int maxPower; // The max power in the unit of 0.01dBm. Passing INT16_MAX(32767) will
diff --git a/thread/framework/java/android/net/thread/IOperationalDatasetCallback.aidl b/thread/framework/java/android/net/thread/IOperationalDatasetCallback.aidl
index b576b33..3fece65 100644
--- a/thread/framework/java/android/net/thread/IOperationalDatasetCallback.aidl
+++ b/thread/framework/java/android/net/thread/IOperationalDatasetCallback.aidl
@@ -23,6 +23,8 @@
* @hide
*/
oneway interface IOperationalDatasetCallback {
- void onActiveOperationalDatasetChanged(in @nullable ActiveOperationalDataset activeOpDataset);
- void onPendingOperationalDatasetChanged(in @nullable PendingOperationalDataset pendingOpDataset);
+ void onActiveOperationalDatasetChanged(
+ in @nullable ActiveOperationalDataset activeOpDataset);
+ void onPendingOperationalDatasetChanged(
+ in @nullable PendingOperationalDataset pendingOpDataset);
}
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index f50de74..b7f68c9 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -28,9 +28,9 @@
import android.net.thread.ThreadConfiguration;
/**
-* Interface for communicating with ThreadNetworkControllerService.
-* @hide
-*/
+ * Interface for communicating with ThreadNetworkControllerService.
+ * @hide
+ */
interface IThreadNetworkController {
void registerStateCallback(in IStateCallback callback);
void unregisterStateCallback(in IStateCallback callback);
@@ -38,10 +38,12 @@
void unregisterOperationalDatasetCallback(in IOperationalDatasetCallback callback);
void join(in ActiveOperationalDataset activeOpDataset, in IOperationReceiver receiver);
- void scheduleMigration(in PendingOperationalDataset pendingOpDataset, in IOperationReceiver receiver);
+ void scheduleMigration(
+ in PendingOperationalDataset pendingOpDataset, in IOperationReceiver receiver);
void leave(in IOperationReceiver receiver);
- void setTestNetworkAsUpstream(in String testNetworkInterfaceName, in IOperationReceiver receiver);
+ void setTestNetworkAsUpstream(
+ in String testNetworkInterfaceName, in IOperationReceiver receiver);
void setChannelMaxPowers(in ChannelMaxPower[] channelMaxPowers, in IOperationReceiver receiver);
int getThreadVersion();
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkManager.aidl b/thread/framework/java/android/net/thread/IThreadNetworkManager.aidl
index 0e394b1..b63cd72 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkManager.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkManager.aidl
@@ -19,9 +19,9 @@
import android.net.thread.IThreadNetworkController;
/**
-* Interface for communicating with ThreadNetworkService.
-* @hide
-*/
+ * Interface for communicating with ThreadNetworkService.
+ * @hide
+ */
interface IThreadNetworkManager {
List<IThreadNetworkController> getAllThreadNetworkControllers();
}
diff --git a/thread/scripts/make-pretty.sh b/thread/scripts/make-pretty.sh
index c176bfa..e012d41 100755
--- a/thread/scripts/make-pretty.sh
+++ b/thread/scripts/make-pretty.sh
@@ -1,9 +1,35 @@
#!/usr/bin/env bash
-SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+ANDROID_ROOT_DIR=$(
+ while [ ! -d ".repo" ] && [ "$PWD" != "/" ]; do cd ..; done
+ pwd
+)
-GOOGLE_JAVA_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/tools/common/google-java-format/google-java-format
-ANDROID_BP_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/build-tools/linux-x86/bin/bpfmt
+if [ ! -d "$ANDROID_ROOT_DIR/.repo" ]; then
+ echo "Error: The script has to run in an Android repo checkout"
+ exit 1
+fi
-$GOOGLE_JAVA_FORMAT --aosp -i $(find $SCRIPT_DIR/../ -name "*.java")
-$ANDROID_BP_FORMAT -w $(find $SCRIPT_DIR/../ -name "*.bp")
+GOOGLE_JAVA_FORMAT=$ANDROID_ROOT_DIR/prebuilts/tools/common/google-java-format/google-java-format
+ANDROID_BP_FORMAT=$ANDROID_ROOT_DIR/prebuilts/build-tools/linux-x86/bin/bpfmt
+AIDL_FORMAT=$ANDROID_ROOT_DIR/system/tools/aidl/aidl-format.sh
+
+CONNECTIVITY_DIR=$ANDROID_ROOT_DIR/packages/modules/Connectivity
+OPENTHREAD_DIR=$ANDROID_ROOT_DIR/external/openthread
+OTBR_POSIX_DIR=$ANDROID_ROOT_DIR/external/ot-br-posix
+
+ALLOWED_CODE_DIRS=($CONNECTIVITY_DIR $OPENTHREAD_DIR $OTBR_POSIX_DIR)
+CODE_DIR=$(git rev-parse --show-toplevel)
+
+if [[ ! " ${ALLOWED_CODE_DIRS[@]} " =~ " ${CODE_DIR} " ]]; then
+ echo "Error: The script has to run in the Git project Connectivity, openthread or ot-br-posix"
+ exit 1
+fi
+
+if [[ $CODE_DIR == $CONNECTIVITY_DIR ]]; then
+ CODE_DIR=$CODE_DIR"/thread"
+fi
+
+$GOOGLE_JAVA_FORMAT --aosp -i $(find $CODE_DIR -name "*.java")
+$ANDROID_BP_FORMAT -w $(find $CODE_DIR -name "*.bp")
+$AIDL_FORMAT -w $(find $CODE_DIR -name "*.aidl")