Merge "Disable R+ tests on Q"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 7fa4b7f..95f854b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -137,9 +137,6 @@
"exclude-annotation": "androidx.test.filters.RequiresDevice"
}
]
- },
- {
- "name": "TetheringCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
}
],
"auto-postsubmit": [
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 586923c..ac777d7 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -132,15 +132,41 @@
hidden_api: {
max_target_r_low_priority: [
"hiddenapi/hiddenapi-max-target-r-loprio.txt",
- ],
+ ],
max_target_o_low_priority: [
"hiddenapi/hiddenapi-max-target-o-low-priority.txt",
"hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt",
- ],
+ ],
unsupported: [
"hiddenapi/hiddenapi-unsupported.txt",
"hiddenapi/hiddenapi-unsupported-tiramisu.txt",
],
+
+ // The following packages contain classes from other modules on the
+ // bootclasspath. That means that the hidden API flags for this module
+ // has to explicitly list every single class this module provides in
+ // that package to differentiate them from the classes provided by other
+ // modules. That can include private classes that are not part of the
+ // API.
+ split_packages: [
+ "android.app.usage",
+ "android.net",
+ "android.net.netstats",
+ "android.net.util",
+ ],
+
+ // The following packages and all their subpackages currently only
+ // contain classes from this bootclasspath_fragment. Listing a package
+ // here won't prevent other bootclasspath modules from adding classes in
+ // any of those packages but it will prevent them from adding those
+ // classes into an API surface, e.g. public, system, etc.. Doing so will
+ // result in a build failure due to inconsistent flags.
+ package_prefixes: [
+ "android.net.apf",
+ "android.net.connectivity",
+ "android.net.netstats.provider",
+ "android.net.nsd",
+ ],
},
}
diff --git a/Tethering/res/values/config.xml b/Tethering/res/values/config.xml
index 0412a49..bfec5bc 100644
--- a/Tethering/res/values/config.xml
+++ b/Tethering/res/values/config.xml
@@ -78,6 +78,12 @@
<!-- Use legacy wifi p2p dedicated address instead of randomize address. -->
<bool translatable="false" name="config_tether_enable_legacy_wifi_p2p_dedicated_ip">false</bool>
+ <!-- Use lease subnet prefix length to reserve the range outside of subnet prefix length.
+ This configuration only valid if its value larger than dhcp server address prefix length
+ and config_tether_enable_legacy_wifi_p2p_dedicated_ip is true.
+ -->
+ <integer translatable="false" name="config_p2p_leases_subnet_prefix_length">0</integer>
+
<!-- Dhcp range (min, max) to use for tethering purposes -->
<string-array translatable="false" name="config_tether_dhcp_range">
</string-array>
diff --git a/Tethering/res/values/overlayable.xml b/Tethering/res/values/overlayable.xml
index 91fbd7d..7bd905c 100644
--- a/Tethering/res/values/overlayable.xml
+++ b/Tethering/res/values/overlayable.xml
@@ -32,6 +32,7 @@
<item type="bool" name="config_tether_enable_bpf_offload"/>
<item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
<item type="bool" name="config_tether_enable_legacy_wifi_p2p_dedicated_ip"/>
+ <item type="integer" name="config_p2p_leases_subnet_prefix_length"/>
<item type="integer" name="config_tether_offload_poll_interval"/>
<item type="array" name="config_tether_upstream_types"/>
<item type="bool" name="config_tether_upstream_automatic"/>
diff --git a/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
index aaaec17..8d58945 100644
--- a/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -185,6 +185,16 @@
return this;
}
+ /** Set leases subnet prefix length. If the value is smaller than server address prefix length,
+ * this configuration will be ignored.
+ *
+ * <p>If not set, the default value is zero.
+ */
+ public DhcpServingParamsParcelExt setLeasesSubnetPrefixLength(int prefixLength) {
+ this.leasesSubnetPrefixLength = prefixLength;
+ return this;
+ }
+
private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
int[] res = new int[addrs.size()];
int i = 0;
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index acd2625..c718f4c 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -241,6 +241,7 @@
private final LinkProperties mLinkProperties;
private final boolean mUsingLegacyDhcp;
private final boolean mUsingBpfOffload;
+ private final int mP2pLeasesSubnetPrefixLength;
private final Dependencies mDeps;
@@ -299,6 +300,7 @@
mLinkProperties = new LinkProperties();
mUsingLegacyDhcp = config.useLegacyDhcpServer();
mUsingBpfOffload = config.isBpfOffloadEnabled();
+ mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength();
mPrivateAddressCoordinator = addressCoordinator;
mDeps = deps;
resetLinkProperties();
@@ -527,6 +529,9 @@
@Nullable Inet4Address clientAddr) {
final boolean changePrefixOnDecline =
(mInterfaceType == TetheringManager.TETHERING_NCM && clientAddr == null);
+ final int subnetPrefixLength = mInterfaceType == TetheringManager.TETHERING_WIFI_P2P
+ ? mP2pLeasesSubnetPrefixLength : 0 /* default value */;
+
return new DhcpServingParamsParcelExt()
.setDefaultRouters(defaultRouter)
.setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
@@ -534,7 +539,8 @@
.setServerAddr(serverAddr)
.setMetered(true)
.setSingleClientAddr(clientAddr)
- .setChangePrefixOnDecline(changePrefixOnDecline);
+ .setChangePrefixOnDecline(changePrefixOnDecline)
+ .setLeasesSubnetPrefixLength(subnetPrefixLength);
// TODO: also advertise link MTU
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index eaf8589..f9f3ed9 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -149,6 +149,7 @@
// TODO: Add to TetheringConfigurationParcel if required.
private final boolean mEnableBpfOffload;
private final boolean mEnableWifiP2pDedicatedIp;
+ private final int mP2pLeasesSubnetPrefixLength;
private final int mUsbTetheringFunction;
protected final ContentResolver mContentResolver;
@@ -214,9 +215,27 @@
R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip,
false /* defaultValue */);
+ mP2pLeasesSubnetPrefixLength = getP2pLeasesSubnetPrefixLengthFromRes(res, configLog);
+
configLog.log(toString());
}
+ private int getP2pLeasesSubnetPrefixLengthFromRes(final Resources res, final SharedLog log) {
+ if (!mEnableWifiP2pDedicatedIp) return 0;
+
+ int prefixLength = getResourceInteger(res,
+ R.integer.config_p2p_leases_subnet_prefix_length, 0 /* default value */);
+
+ // DhcpLeaseRepository ignores the first and last addresses of the range so the max prefix
+ // length is 30.
+ if (prefixLength < 0 || prefixLength > 30) {
+ log.e("Invalid p2p leases subnet prefix length configuration: " + prefixLength);
+ return 0;
+ }
+
+ return prefixLength;
+ }
+
/** Check whether using legacy dhcp server. */
public boolean useLegacyDhcpServer() {
return mEnableLegacyDhcpServer;
@@ -272,6 +291,15 @@
return mEnableWifiP2pDedicatedIp;
}
+ /**
+ * Get subnet prefix length of dhcp leases for wifi p2p.
+ * This feature only support when wifi p2p use dedicated address. If
+ * #shouldEnableWifiP2pDedicatedIp is false, this method would always return 0.
+ */
+ public int getP2pLeasesSubnetPrefixLength() {
+ return mP2pLeasesSubnetPrefixLength;
+ }
+
/** Does the dumping.*/
public void dump(PrintWriter pw) {
pw.print("activeDataSubId: ");
@@ -310,6 +338,9 @@
pw.print("enableWifiP2pDedicatedIp: ");
pw.println(mEnableWifiP2pDedicatedIp);
+ pw.print("p2pLeasesSubnetPrefixLength: ");
+ pw.println(mP2pLeasesSubnetPrefixLength);
+
pw.print("mUsbTetheringFunction: ");
pw.println(isUsingNcm() ? "NCM" : "RNDIS");
}
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 6eaf68b..a4d0448 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -83,68 +83,3 @@
compile_multilib: "both",
jarjar_rules: ":NetworkStackJarJarRules",
}
-
-android_library {
- name: "TetheringCoverageTestsLib",
- min_sdk_version: "30",
- static_libs: [
- "NetdStaticLibTestsLib",
- "NetworkStaticLibTestsLib",
- "NetworkStackTestsLib",
- "TetheringTestsLatestSdkLib",
- "TetheringIntegrationTestsLatestSdkLib",
- ],
- // Jarjar rules should normally be applied on final artifacts and not intermediate libraries as
- // applying different rules on intermediate libraries can cause conflicts when combining them
- // (the resulting artifact can end up with multiple incompatible implementations of the same
- // classes). But this library is used to combine tethering coverage tests with connectivity
- // coverage tests into a single coverage target. The tests need to use the same jarjar rules as
- // covered production code for coverage to be calculated properly, so jarjar is applied
- // separately on each set of tests.
- jarjar_rules: ":TetheringCoverageJarJarRules",
- manifest: "AndroidManifest_coverage.xml",
- visibility: [
- "//packages/modules/Connectivity/tests:__subpackages__"
- ],
-}
-
-// Combine NetworkStack and Tethering jarjar rules for coverage target. The jarjar files are
-// simply concatenated in the order specified in srcs.
-genrule {
- name: "TetheringCoverageJarJarRules",
- srcs: [
- ":TetheringTestsJarJarRules",
- ":NetworkStackJarJarRules",
- ],
- out: ["jarjar-rules-tethering-coverage.txt"],
- cmd: "cat $(in) > $(out)",
- visibility: ["//visibility:private"],
-}
-
-// Special version of the tethering tests that includes all tests necessary for code coverage
-// purposes. This is currently the union of TetheringTests, TetheringIntegrationTests and
-// NetworkStackTests.
-// TODO: remove in favor of ConnectivityCoverageTests, which includes below tests and more
-android_test {
- name: "TetheringCoverageTests",
- platform_apis: true,
- min_sdk_version: "30",
- target_sdk_version: "31",
- test_suites: ["device-tests", "mts-tethering"],
- test_config: "AndroidTest_Coverage.xml",
- defaults: ["libnetworkstackutilsjni_deps"],
- static_libs: [
- "modules-utils-native-coverage-listener",
- "TetheringCoverageTestsLib",
- ],
- jni_libs: [
- // For mockito extended
- "libdexmakerjvmtiagent",
- "libstaticjvmtiagent",
- // For NetworkStackUtils included in NetworkStackBase
- "libnetworkstackutilsjni",
- "libcom_android_networkstack_tethering_util_jni",
- ],
- compile_multilib: "both",
- manifest: "AndroidManifest_coverage.xml",
-}
diff --git a/Tethering/tests/integration/AndroidManifest_coverage.xml b/Tethering/tests/integration/AndroidManifest_coverage.xml
deleted file mode 100644
index 06de00d..0000000
--- a/Tethering/tests/integration/AndroidManifest_coverage.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.networkstack.tethering.tests.coverage">
-
- <application tools:replace="android:label"
- android:debuggable="true"
- android:label="Tethering coverage tests">
- <uses-library android:name="android.test.runner" />
- </application>
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.networkstack.tethering.tests.coverage"
- android:label="Tethering coverage tests">
- </instrumentation>
-</manifest>
diff --git a/Tethering/tests/integration/AndroidTest_Coverage.xml b/Tethering/tests/integration/AndroidTest_Coverage.xml
deleted file mode 100644
index 33c5b3d..0000000
--- a/Tethering/tests/integration/AndroidTest_Coverage.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<configuration description="Runs coverage tests for Tethering">
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
- <option name="test-file-name" value="TetheringCoverageTests.apk" />
- </target_preparer>
-
- <option name="test-tag" value="TetheringCoverageTests" />
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="com.android.networkstack.tethering.tests.coverage" />
- <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- <option name="hidden-api-checks" value="false"/>
- <option name="device-listeners" value="com.android.modules.utils.testing.NativeCoverageHackInstrumentationListener" />
- </test>
-</configuration>
diff --git a/Tethering/tests/jarjar-rules.txt b/Tethering/tests/jarjar-rules.txt
index a7c7488..cd8fd3a 100644
--- a/Tethering/tests/jarjar-rules.txt
+++ b/Tethering/tests/jarjar-rules.txt
@@ -7,6 +7,8 @@
rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
+# Keep other com.android.internal.util as-is
+rule com.android.internal.util.** @0
rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 6488421..43f1eaa 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -156,6 +156,8 @@
private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
private static final int DHCP_LEASE_TIME_SECS = 3600;
private static final boolean DEFAULT_USING_BPF_OFFLOAD = true;
+ private static final int DEFAULT_SUBNET_PREFIX_LENGTH = 0;
+ private static final int P2P_SUBNET_PREFIX_LENGTH = 25;
private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
@@ -230,6 +232,7 @@
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp);
+ when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
mIpServer = new IpServer(
IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
mCallback, mTetherConfig, mAddressCoordinator, mDependencies);
@@ -1312,6 +1315,12 @@
if (mIpServer.interfaceType() == TETHERING_NCM) {
assertTrue(params.changePrefixOnDecline);
}
+
+ if (mIpServer.interfaceType() == TETHERING_WIFI_P2P) {
+ assertEquals(P2P_SUBNET_PREFIX_LENGTH, params.leasesSubnetPrefixLength);
+ } else {
+ assertEquals(DEFAULT_SUBNET_PREFIX_LENGTH, params.leasesSubnetPrefixLength);
+ }
}
private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index e8bb315..7fcf2b2 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -616,4 +616,35 @@
assertArrayEquals(ncmRegexs, cfg.tetherableNcmRegexs);
}
+ @Test
+ public void testP2pLeasesSubnetPrefixLength() throws Exception {
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip))
+ .thenReturn(true);
+
+ final int defaultSubnetPrefixLength = 0;
+ final TetheringConfiguration defaultCfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertEquals(defaultSubnetPrefixLength, defaultCfg.getP2pLeasesSubnetPrefixLength());
+
+ final int prefixLengthTooSmall = -1;
+ when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
+ prefixLengthTooSmall);
+ final TetheringConfiguration tooSmallCfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertEquals(defaultSubnetPrefixLength, tooSmallCfg.getP2pLeasesSubnetPrefixLength());
+
+ final int prefixLengthTooLarge = 31;
+ when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
+ prefixLengthTooLarge);
+ final TetheringConfiguration tooLargeCfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertEquals(defaultSubnetPrefixLength, tooLargeCfg.getP2pLeasesSubnetPrefixLength());
+
+ final int p2pLeasesSubnetPrefixLength = 27;
+ when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
+ p2pLeasesSubnetPrefixLength);
+ final TetheringConfiguration p2pCfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertEquals(p2pLeasesSubnetPrefixLength, p2pCfg.getP2pLeasesSubnetPrefixLength());
+ }
}
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index dc646c3..8971f6b 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -37,21 +37,33 @@
// From kernel:include/net/ip.h
#define IP_DF 0x4000 // Flag: "Don't Fragment"
+// Used for iptables drops ingress clat packet. Beware of clat mark change may break the device
+// which is using the old clat mark in netd platform code. The reason is that the clat mark is a
+// mainline constant since T+ but netd iptable rules (ex: bandwidth control, firewall, and so on)
+// are set in stone.
+#define CLAT_MARK 0xdeadc1a7
+
DEFINE_BPF_MAP_GRW(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16, AID_SYSTEM)
static inline __always_inline int nat64(struct __sk_buff* skb, bool is_ethernet) {
- const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
- void* data = (void*)(long)skb->data;
- const void* data_end = (void*)(long)skb->data_end;
- const struct ethhdr* const eth = is_ethernet ? data : NULL; // used iff is_ethernet
- const struct ipv6hdr* const ip6 = is_ethernet ? (void*)(eth + 1) : data;
-
// Require ethernet dst mac address to be our unicast address.
if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
// Must be meta-ethernet IPv6 frame
if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_PIPE;
+ const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+
+ // Not clear if this is actually necessary considering we use DPA (Direct Packet Access),
+ // but we need to make sure we can read the IPv6 header reliably so that we can set
+ // skb->mark = 0xDeadC1a7 for packets we fail to offload.
+ try_make_readable(skb, l2_header_size + sizeof(struct ipv6hdr));
+
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ const struct ethhdr* const eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ const struct ipv6hdr* const ip6 = is_ethernet ? (void*)(eth + 1) : data;
+
// Must have (ethernet and) ipv6 header
if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_PIPE;
@@ -64,17 +76,6 @@
// Maximum IPv6 payload length that can be translated to IPv4
if (ntohs(ip6->payload_len) > 0xFFFF - sizeof(struct iphdr)) return TC_ACT_PIPE;
- switch (ip6->nexthdr) {
- case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
- case IPPROTO_UDP: // address means there is no need to update their checksums.
- case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers,
- case IPPROTO_ESP: // since there is never a checksum to update.
- break;
-
- default: // do not know how to handle anything else
- return TC_ACT_PIPE;
- }
-
ClatIngress6Key k = {
.iif = skb->ifindex,
.pfx96.in6_u.u6_addr32 =
@@ -90,6 +91,21 @@
if (!v) return TC_ACT_PIPE;
+ switch (ip6->nexthdr) {
+ case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
+ case IPPROTO_UDP: // address means there is no need to update their checksums.
+ case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers,
+ case IPPROTO_ESP: // since there is never a checksum to update.
+ break;
+
+ default: // do not know how to handle anything else
+ // Mark ingress non-offloaded clat packet for dropping in ip6tables bw_raw_PREROUTING.
+ // Non-offloaded clat packet is going to be handled by clat daemon and ip6tables. The
+ // duplicate one in ip6tables is not necessary.
+ skb->mark = CLAT_MARK;
+ return TC_ACT_PIPE;
+ }
+
struct ethhdr eth2; // used iff is_ethernet
if (is_ethernet) {
eth2 = *eth; // Copy over the ethernet header (src/dst mac)
@@ -132,7 +148,13 @@
// Packet mutations begin - point of no return, but if this first modification fails
// the packet is probably still pristine, so let clatd handle it.
- if (bpf_skb_change_proto(skb, htons(ETH_P_IP), 0)) return TC_ACT_PIPE;
+ if (bpf_skb_change_proto(skb, htons(ETH_P_IP), 0)) {
+ // Mark ingress non-offloaded clat packet for dropping in ip6tables bw_raw_PREROUTING.
+ // Non-offloaded clat packet is going to be handled by clat daemon and ip6tables. The
+ // duplicate one in ip6tables is not necessary.
+ skb->mark = CLAT_MARK;
+ return TC_ACT_PIPE;
+ }
// This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet.
//
@@ -198,13 +220,16 @@
DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_egress4_clat_rawip)
(struct __sk_buff* skb) {
+ // Must be meta-ethernet IPv4 frame
+ if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
+
+ // Possibly not needed, but for consistency with nat64 up above
+ try_make_readable(skb, sizeof(struct iphdr));
+
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
const struct iphdr* const ip4 = data;
- // Must be meta-ethernet IPv4 frame
- if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
-
// Must have ipv4 header
if (data + sizeof(*ip4) > data_end) return TC_ACT_PIPE;
diff --git a/framework-t/Sources.bp b/framework-t/Sources.bp
index 53b4163..b30ee80 100644
--- a/framework-t/Sources.bp
+++ b/framework-t/Sources.bp
@@ -124,13 +124,6 @@
],
}
-// TODO: remove this empty filegroup.
-filegroup {
- name: "framework-connectivity-tiramisu-sources",
- srcs: [],
- visibility: ["//frameworks/base"],
-}
-
filegroup {
name: "framework-connectivity-tiramisu-updatable-sources",
srcs: [
diff --git a/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java b/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
index 61b34d0..d9c9d74 100644
--- a/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
+++ b/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
@@ -20,7 +20,9 @@
import android.app.SystemServiceRegistry;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
+import android.net.mdns.aidl.IMDns;
import android.net.nsd.INsdManager;
+import android.net.nsd.MDnsManager;
import android.net.nsd.NsdManager;
/**
@@ -78,5 +80,14 @@
return new EthernetManager(context, service);
}
);
+
+ SystemServiceRegistry.registerStaticService(
+ MDnsManager.MDNS_SERVICE,
+ MDnsManager.class,
+ (serviceBinder) -> {
+ IMDns service = IMDns.Stub.asInterface(serviceBinder);
+ return new MDnsManager(service);
+ }
+ );
}
}
diff --git a/framework-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java
index e02ea89..2b76dd9 100644
--- a/framework-t/src/android/net/EthernetManager.java
+++ b/framework-t/src/android/net/EthernetManager.java
@@ -541,8 +541,7 @@
* Similarly, use {@link NetworkCapabilities.Builder} to build a {@code NetworkCapabilities}
* object for this network to put inside the {@code request}.
*
- * This function accepts an {@link OutcomeReceiver} that is called once the operation has
- * finished execution.
+ * The provided {@link OutcomeReceiver} is called once the operation has finished execution.
*
* @param iface the name of the interface to act upon.
* @param request the {@link EthernetNetworkUpdateRequest} used to set an ethernet network's
@@ -554,7 +553,8 @@
* information about the error.
* @throws SecurityException if the process doesn't hold
* {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
- * @throws UnsupportedOperationException if called on a non-automotive device or on an
+ * @throws UnsupportedOperationException if the {@link NetworkCapabilities} are updated on a
+ * non-automotive device or this function is called on an
* unsupported interface.
* @hide
*/
@@ -582,9 +582,9 @@
/**
* Enable a network interface.
*
- * Enables a previously disabled network interface.
- * This function accepts an {@link OutcomeReceiver} that is called once the operation has
- * finished execution.
+ * Enables a previously disabled network interface. An attempt to enable an already-enabled
+ * interface is ignored.
+ * The provided {@link OutcomeReceiver} is called once the operation has finished execution.
*
* @param iface the name of the interface to enable.
* @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
@@ -619,10 +619,9 @@
/**
* Disable a network interface.
*
- * Disables the use of a network interface to fulfill network requests. If the interface
- * currently serves a request, the network will be torn down.
- * This function accepts an {@link OutcomeReceiver} that is called once the operation has
- * finished execution.
+ * Disables the specified interface. If this interface is in use in a connected
+ * {@link android.net.Network}, then that {@code Network} will be torn down.
+ * The provided {@link OutcomeReceiver} is called once the operation has finished execution.
*
* @param iface the name of the interface to disable.
* @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
diff --git a/framework-t/src/android/net/nsd/MDnsManager.java b/framework-t/src/android/net/nsd/MDnsManager.java
new file mode 100644
index 0000000..c11e60c
--- /dev/null
+++ b/framework-t/src/android/net/nsd/MDnsManager.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.nsd;
+
+import android.annotation.NonNull;
+import android.net.mdns.aidl.DiscoveryInfo;
+import android.net.mdns.aidl.GetAddressInfo;
+import android.net.mdns.aidl.IMDns;
+import android.net.mdns.aidl.IMDnsEventListener;
+import android.net.mdns.aidl.RegistrationInfo;
+import android.net.mdns.aidl.ResolutionInfo;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+/**
+ * A manager class for mdns service.
+ *
+ * @hide
+ */
+public class MDnsManager {
+ private static final String TAG = MDnsManager.class.getSimpleName();
+ private final IMDns mMdns;
+
+ /** Service name for this. */
+ public static final String MDNS_SERVICE = "mdns";
+
+ private static final int NO_RESULT = -1;
+ private static final int NETID_UNSET = 0;
+
+ public MDnsManager(IMDns mdns) {
+ mMdns = mdns;
+ }
+
+ /**
+ * Start the MDNSResponder daemon.
+ */
+ public void startDaemon() {
+ try {
+ mMdns.startDaemon();
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Start mdns failed.", e);
+ }
+ }
+
+ /**
+ * Stop the MDNSResponder daemon.
+ */
+ public void stopDaemon() {
+ try {
+ mMdns.stopDaemon();
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Stop mdns failed.", e);
+ }
+ }
+
+ /**
+ * Start registering a service.
+ *
+ * @param id The operation ID.
+ * @param serviceName The service name to be registered.
+ * @param registrationType The service type to be registered.
+ * @param port The port on which the service accepts connections.
+ * @param txtRecord The txt record. Refer to {@code NsdServiceInfo#setTxtRecords} for details.
+ * @param interfaceIdx The interface index on which to register the service.
+ * @return {@code true} if registration is successful, else {@code false}.
+ */
+ public boolean registerService(int id, @NonNull String serviceName,
+ @NonNull String registrationType, int port, @NonNull byte[] txtRecord,
+ int interfaceIdx) {
+ final RegistrationInfo info = new RegistrationInfo(id, NO_RESULT, serviceName,
+ registrationType, port, txtRecord, interfaceIdx);
+ try {
+ mMdns.registerService(info);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Register service failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Start discovering services.
+ *
+ * @param id The operation ID.
+ * @param registrationType The service type to be discovered.
+ * @param interfaceIdx The interface index on which to discover for services.
+ * @return {@code true} if discovery is started successfully, else {@code false}.
+ */
+ public boolean discover(int id, @NonNull String registrationType, int interfaceIdx) {
+ final DiscoveryInfo info = new DiscoveryInfo(id, NO_RESULT, "" /* serviceName */,
+ registrationType, "" /* domainName */, interfaceIdx, NETID_UNSET);
+ try {
+ mMdns.discover(info);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Discover service failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Start resolving the target service.
+ *
+ * @param id The operation ID.
+ * @param serviceName The service name to be resolved.
+ * @param registrationType The service type to be resolved.
+ * @param domain The service domain to be resolved.
+ * @param interfaceIdx The interface index on which to resolve the service.
+ * @return {@code true} if resolution is started successfully, else {@code false}.
+ */
+ public boolean resolve(int id, @NonNull String serviceName, @NonNull String registrationType,
+ @NonNull String domain, int interfaceIdx) {
+ final ResolutionInfo info = new ResolutionInfo(id, NO_RESULT, serviceName,
+ registrationType, domain, "" /* serviceFullName */, "" /* hostname */, 0 /* port */,
+ new byte[0] /* txtRecord */, interfaceIdx);
+ try {
+ mMdns.resolve(info);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Resolve service failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Start getting the target service address.
+ *
+ * @param id The operation ID.
+ * @param hostname The fully qualified domain name of the host to be queried for.
+ * @param interfaceIdx The interface index on which to issue the query.
+ * @return {@code true} if getting address is started successful, else {@code false}.
+ */
+ public boolean getServiceAddress(int id, @NonNull String hostname, int interfaceIdx) {
+ final GetAddressInfo info = new GetAddressInfo(id, NO_RESULT, hostname,
+ "" /* address */, interfaceIdx, NETID_UNSET);
+ try {
+ mMdns.getServiceAddress(info);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Get service address failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Stop an operation which was requested before.
+ *
+ * @param id the operation id to be stopped.
+ * @return {@code true} if operation is stopped successfully, else {@code false}.
+ */
+ public boolean stopOperation(int id) {
+ try {
+ mMdns.stopOperation(id);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Stop operation failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Register an event listener.
+ *
+ * @param listener The listener to be registered.
+ */
+ public void registerEventListener(@NonNull IMDnsEventListener listener) {
+ try {
+ mMdns.registerEventListener(listener);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Register listener failed.", e);
+ }
+ }
+
+ /**
+ * Unregister an event listener.
+ *
+ * @param listener The listener to be unregistered.
+ */
+ public void unregisterEventListener(@NonNull IMDnsEventListener listener) {
+ try {
+ mMdns.unregisterEventListener(listener);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Unregister listener failed.", e);
+ }
+ }
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 209f372..33b44c8 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -235,7 +235,7 @@
public static final int DISABLE = 21;
/** @hide */
- public static final int NATIVE_DAEMON_EVENT = 22;
+ public static final int MDNS_SERVICE_EVENT = 22;
/** @hide */
public static final int REGISTER_CLIENT = 23;
@@ -268,7 +268,7 @@
EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
EVENT_NAMES.put(ENABLE, "ENABLE");
EVENT_NAMES.put(DISABLE, "DISABLE");
- EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT");
+ EVENT_NAMES.put(MDNS_SERVICE_EVENT, "MDNS_SERVICE_EVENT");
}
/** @hide */
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 8506db1..2621594 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -24,7 +24,6 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.Base64;
import android.util.Log;
import java.io.UnsupportedEncodingException;
@@ -106,13 +105,11 @@
/**
* Unpack txt information from a base-64 encoded byte array.
*
- * @param rawRecords The raw base64 encoded records string read from netd.
+ * @param txtRecordsRawBytes The raw base64 encoded byte array.
*
* @hide
*/
- public void setTxtRecords(@NonNull String rawRecords) {
- byte[] txtRecordsRawBytes = Base64.decode(rawRecords, Base64.DEFAULT);
-
+ public void setTxtRecords(@NonNull byte[] txtRecordsRawBytes) {
// There can be multiple TXT records after each other. Each record has to following format:
//
// byte type required meaning
diff --git a/framework/Android.bp b/framework/Android.bp
index f31a7d5..3703df8 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -86,6 +86,7 @@
"net-utils-device-common",
],
static_libs: [
+ "mdns_aidl_interface-lateststable-java",
"modules-utils-backgroundthread",
"modules-utils-build",
"modules-utils-preconditions",
diff --git a/service-t/Sources.bp b/service-t/Sources.bp
index 4e669b6..187eadf 100644
--- a/service-t/Sources.bp
+++ b/service-t/Sources.bp
@@ -14,27 +14,6 @@
// limitations under the License.
//
-// NetworkStats related libraries.
-
-filegroup {
- name: "services.connectivity-netstats-sources",
- srcs: [
- "src/com/android/server/net/NetworkIdentity*.java",
- "src/com/android/server/net/NetworkStats*.java",
- "src/com/android/server/net/BpfInterfaceMapUpdater.java",
- "src/com/android/server/net/InterfaceMapValue.java",
- "src/com/android/server/net/CookieTagMapKey.java",
- "src/com/android/server/net/CookieTagMapValue.java",
- "src/com/android/server/net/StatsMapKey.java",
- "src/com/android/server/net/StatsMapValue.java",
- "src/com/android/server/net/UidStatsMapKey.java",
- ],
- path: "src",
- visibility: [
- "//visibility:private",
- ],
-}
-
// For test code only.
filegroup {
name: "lib_networkStatsFactory_native",
@@ -59,37 +38,3 @@
],
}
-// Connectivity-T common libraries.
-
-// TODO: remove this empty filegroup.
-filegroup {
- name: "services.connectivity-tiramisu-sources",
- srcs: [],
- path: "src",
- visibility: ["//frameworks/base/services/core"],
-}
-
-cc_library_shared {
- name: "libcom_android_net_module_util_jni",
- min_sdk_version: "30",
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-unused-parameter",
- "-Wthread-safety",
- ],
- srcs: [
- "jni/onload.cpp",
- ],
- stl: "libc++_static",
- static_libs: [
- "libnet_utils_device_common_bpfjni",
- ],
- shared_libs: [
- "liblog",
- "libnativehelper",
- ],
- apex_available: [
- "//apex_available:platform",
- ],
-}
diff --git a/service-t/jni/onload.cpp b/service-t/jni/onload.cpp
deleted file mode 100644
index bca4697..0000000
--- a/service-t/jni/onload.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <nativehelper/JNIHelp.h>
-#include <log/log.h>
-
-namespace android {
-
-int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
- JNIEnv *env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- ALOGE("GetEnv failed");
- return JNI_ERR;
- }
-
- if (register_com_android_net_module_util_BpfMap(env,
- "com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
-
- return JNI_VERSION_1_6;
-}
-
-};
-
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index e4efa926..626c2eb 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -123,12 +123,8 @@
/** Return NsdService instance or null if current SDK is lower than T */
private NsdService createNsdService(final Context context) {
if (!SdkLevel.isAtLeastT()) return null;
- try {
- return NsdService.create(context);
- } catch (InterruptedException e) {
- Log.d(TAG, "Unable to get NSD service", e);
- return null;
- }
+
+ return NsdService.create(context);
}
/** Return Nearby service instance or null if current SDK is lower than T */
diff --git a/service-t/src/com/android/server/INativeDaemonConnectorCallbacks.java b/service-t/src/com/android/server/INativeDaemonConnectorCallbacks.java
deleted file mode 100644
index 0cf9dcd..0000000
--- a/service-t/src/com/android/server/INativeDaemonConnectorCallbacks.java
+++ /dev/null
@@ -1,25 +0,0 @@
-
-/*
- * Copyright (C) 2007 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;
-
-interface INativeDaemonConnectorCallbacks {
-
- void onDaemonConnected();
- boolean onCheckHoldWakeLock(int code);
- boolean onEvent(int code, String raw, String[] cooked);
-}
diff --git a/service-t/src/com/android/server/NativeDaemonConnector.java b/service-t/src/com/android/server/NativeDaemonConnector.java
deleted file mode 100644
index ec8d779..0000000
--- a/service-t/src/com/android/server/NativeDaemonConnector.java
+++ /dev/null
@@ -1,704 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.util.LocalLog;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.Objects;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Generic connector class for interfacing with a native daemon which uses the
- * {@code libsysutils} FrameworkListener protocol.
- */
-final class NativeDaemonConnector implements Runnable, Handler.Callback {
- private final static boolean VDBG = false;
-
- private final String TAG;
-
- private String mSocket;
- private OutputStream mOutputStream;
- private LocalLog mLocalLog;
-
- private volatile boolean mDebug = false;
- private volatile Object mWarnIfHeld;
-
- private final ResponseQueue mResponseQueue;
-
- private final PowerManager.WakeLock mWakeLock;
-
- private final Looper mLooper;
-
- private INativeDaemonConnectorCallbacks mCallbacks;
- private Handler mCallbackHandler;
-
- private AtomicInteger mSequenceNumber;
-
- private static final long DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
- private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
-
- /** Lock held whenever communicating with native daemon. */
- private final Object mDaemonLock = new Object();
-
- private final int BUFFER_SIZE = 4096;
-
- NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
- int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
- mCallbacks = callbacks;
- mSocket = socket;
- mResponseQueue = new ResponseQueue(responseQueueSize);
- mWakeLock = wl;
- if (mWakeLock != null) {
- mWakeLock.setReferenceCounted(true);
- }
- mSequenceNumber = new AtomicInteger(0);
- TAG = logTag != null ? logTag : "NativeDaemonConnector";
- mLocalLog = new LocalLog(maxLogSize);
- final HandlerThread thread = new HandlerThread(TAG);
- thread.start();
- mLooper = thread.getLooper();
- }
-
- /**
- * Enable Set debugging mode, which causes messages to also be written to both
- * {@link Log} in addition to internal log.
- */
- public void setDebug(boolean debug) {
- mDebug = debug;
- }
-
- /**
- * Like SystemClock.uptimeMillis, except truncated to an int so it will fit in a message arg.
- * Inaccurate across 49.7 days of uptime, but only used for debugging.
- */
- private int uptimeMillisInt() {
- return (int) SystemClock.uptimeMillis() & Integer.MAX_VALUE;
- }
-
- /**
- * Yell loudly if someone tries making future {@link #execute(Command)}
- * calls while holding a lock on the given object.
- */
- public void setWarnIfHeld(Object warnIfHeld) {
- if (mWarnIfHeld != null) {
- throw new IllegalStateException("warnIfHeld is already set.");
- }
- mWarnIfHeld = Objects.requireNonNull(warnIfHeld);
- }
-
- @Override
- public void run() {
- mCallbackHandler = new Handler(mLooper, this);
-
- while (true) {
- try {
- listenToSocket();
- } catch (Exception e) {
- loge("Error in NativeDaemonConnector: " + e);
- SystemClock.sleep(5000);
- }
- }
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- final String event = (String) msg.obj;
- final int start = uptimeMillisInt();
- final int sent = msg.arg1;
- try {
- if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
- log(String.format("Unhandled event '%s'", event));
- }
- } catch (Exception e) {
- loge("Error handling '" + event + "': " + e);
- } finally {
- if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
- mWakeLock.release();
- }
- final int end = uptimeMillisInt();
- if (start > sent && start - sent > WARN_EXECUTE_DELAY_MS) {
- loge(String.format("NDC event {%s} processed too late: %dms", event, start - sent));
- }
- if (end > start && end - start > WARN_EXECUTE_DELAY_MS) {
- loge(String.format("NDC event {%s} took too long: %dms", event, end - start));
- }
- }
- return true;
- }
-
- private LocalSocketAddress determineSocketAddress() {
- // If we're testing, set up a socket in a namespace that's accessible to test code.
- // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
- // production devices, even if said native daemons ill-advisedly pick a socket name that
- // starts with __test__, only allow this on debug builds.
- if (mSocket.startsWith("__test__") && Build.isDebuggable()) {
- return new LocalSocketAddress(mSocket);
- } else {
- return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
- }
- }
-
- private void listenToSocket() throws IOException {
- LocalSocket socket = null;
-
- try {
- socket = new LocalSocket();
- LocalSocketAddress address = determineSocketAddress();
-
- socket.connect(address);
-
- InputStream inputStream = socket.getInputStream();
- synchronized (mDaemonLock) {
- mOutputStream = socket.getOutputStream();
- }
-
- mCallbacks.onDaemonConnected();
-
- FileDescriptor[] fdList = null;
- byte[] buffer = new byte[BUFFER_SIZE];
- int start = 0;
-
- while (true) {
- int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
- if (count < 0) {
- loge("got " + count + " reading with start = " + start);
- break;
- }
- fdList = socket.getAncillaryFileDescriptors();
-
- // Add our starting point to the count and reset the start.
- count += start;
- start = 0;
-
- for (int i = 0; i < count; i++) {
- if (buffer[i] == 0) {
- // Note - do not log this raw message since it may contain
- // sensitive data
- final String rawEvent = new String(
- buffer, start, i - start, StandardCharsets.UTF_8);
-
- boolean releaseWl = false;
- try {
- final NativeDaemonEvent event =
- NativeDaemonEvent.parseRawEvent(rawEvent, fdList);
-
- log("RCV <- {" + event + "}");
-
- if (event.isClassUnsolicited()) {
- // TODO: migrate to sending NativeDaemonEvent instances
- if (mCallbacks.onCheckHoldWakeLock(event.getCode())
- && mWakeLock != null) {
- mWakeLock.acquire();
- releaseWl = true;
- }
- Message msg = mCallbackHandler.obtainMessage(
- event.getCode(), uptimeMillisInt(), 0, event.getRawEvent());
- if (mCallbackHandler.sendMessage(msg)) {
- releaseWl = false;
- }
- } else {
- mResponseQueue.add(event.getCmdNumber(), event);
- }
- } catch (IllegalArgumentException e) {
- log("Problem parsing message " + e);
- } finally {
- if (releaseWl) {
- mWakeLock.release();
- }
- }
-
- start = i + 1;
- }
- }
-
- if (start == 0) {
- log("RCV incomplete");
- }
-
- // We should end at the amount we read. If not, compact then
- // buffer and read again.
- if (start != count) {
- final int remaining = BUFFER_SIZE - start;
- System.arraycopy(buffer, start, buffer, 0, remaining);
- start = remaining;
- } else {
- start = 0;
- }
- }
- } catch (IOException ex) {
- loge("Communications error: " + ex);
- throw ex;
- } finally {
- synchronized (mDaemonLock) {
- if (mOutputStream != null) {
- try {
- loge("closing stream for " + mSocket);
- mOutputStream.close();
- } catch (IOException e) {
- loge("Failed closing output stream: " + e);
- }
- mOutputStream = null;
- }
- }
-
- try {
- if (socket != null) {
- socket.close();
- }
- } catch (IOException ex) {
- loge("Failed closing socket: " + ex);
- }
- }
- }
-
- /**
- * Wrapper around argument that indicates it's sensitive and shouldn't be
- * logged.
- */
- public static class SensitiveArg {
- private final Object mArg;
-
- public SensitiveArg(Object arg) {
- mArg = arg;
- }
-
- @Override
- public String toString() {
- return String.valueOf(mArg);
- }
- }
-
- /**
- * Make command for daemon, escaping arguments as needed.
- */
- @VisibleForTesting
- static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
- String cmd, Object... args) {
- if (cmd.indexOf('\0') >= 0) {
- throw new IllegalArgumentException("Unexpected command: " + cmd);
- }
- if (cmd.indexOf(' ') >= 0) {
- throw new IllegalArgumentException("Arguments must be separate from command");
- }
-
- rawBuilder.append(sequenceNumber).append(' ').append(cmd);
- logBuilder.append(sequenceNumber).append(' ').append(cmd);
- for (Object arg : args) {
- final String argString = String.valueOf(arg);
- if (argString.indexOf('\0') >= 0) {
- throw new IllegalArgumentException("Unexpected argument: " + arg);
- }
-
- rawBuilder.append(' ');
- logBuilder.append(' ');
-
- appendEscaped(rawBuilder, argString);
- if (arg instanceof SensitiveArg) {
- logBuilder.append("[scrubbed]");
- } else {
- appendEscaped(logBuilder, argString);
- }
- }
-
- rawBuilder.append('\0');
- }
-
- /**
- * Method that waits until all asychronous notifications sent by the native daemon have
- * been processed. This method must not be called on the notification thread or an
- * exception will be thrown.
- */
- public void waitForCallbacks() {
- if (Thread.currentThread() == mLooper.getThread()) {
- throw new IllegalStateException("Must not call this method on callback thread");
- }
-
- final CountDownLatch latch = new CountDownLatch(1);
- mCallbackHandler.post(new Runnable() {
- @Override
- public void run() {
- latch.countDown();
- }
- });
- try {
- latch.await();
- } catch (InterruptedException e) {
- Log.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
- }
- }
-
- /**
- * Issue the given command to the native daemon and return a single expected
- * response.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
- return execute(cmd.mCmd, cmd.mArguments.toArray());
- }
-
- /**
- * Issue the given command to the native daemon and return a single expected
- * response. Any arguments must be separated from base command so they can
- * be properly escaped.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent execute(String cmd, Object... args)
- throws NativeDaemonConnectorException {
- return execute(DEFAULT_TIMEOUT, cmd, args);
- }
-
- public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
- throws NativeDaemonConnectorException {
- final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
- if (events.length != 1) {
- throw new NativeDaemonConnectorException(
- "Expected exactly one response, but received " + events.length);
- }
- return events[0];
- }
-
- /**
- * Issue the given command to the native daemon and return any
- * {@link NativeDaemonEvent#isClassContinue()} responses, including the
- * final terminal response.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
- return executeForList(cmd.mCmd, cmd.mArguments.toArray());
- }
-
- /**
- * Issue the given command to the native daemon and return any
- * {@link NativeDaemonEvent#isClassContinue()} responses, including the
- * final terminal response. Any arguments must be separated from base
- * command so they can be properly escaped.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent[] executeForList(String cmd, Object... args)
- throws NativeDaemonConnectorException {
- return executeForList(DEFAULT_TIMEOUT, cmd, args);
- }
-
- /**
- * Issue the given command to the native daemon and return any {@linke
- * NativeDaemonEvent@isClassContinue()} responses, including the final
- * terminal response. Note that the timeout does not count time in deep
- * sleep. Any arguments must be separated from base command so they can be
- * properly escaped.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
- throws NativeDaemonConnectorException {
- if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
- Log.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
- + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
- }
-
- final long startTime = SystemClock.elapsedRealtime();
-
- final ArrayList<NativeDaemonEvent> events = new ArrayList<>();
-
- final StringBuilder rawBuilder = new StringBuilder();
- final StringBuilder logBuilder = new StringBuilder();
- final int sequenceNumber = mSequenceNumber.incrementAndGet();
-
- makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
-
- final String rawCmd = rawBuilder.toString();
- final String logCmd = logBuilder.toString();
-
- log("SND -> {" + logCmd + "}");
-
- synchronized (mDaemonLock) {
- if (mOutputStream == null) {
- throw new NativeDaemonConnectorException("missing output stream");
- } else {
- try {
- mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
- } catch (IOException e) {
- throw new NativeDaemonConnectorException("problem sending command", e);
- }
- }
- }
-
- NativeDaemonEvent event = null;
- do {
- event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
- if (event == null) {
- loge("timed-out waiting for response to " + logCmd);
- throw new NativeDaemonTimeoutException(logCmd, event);
- }
- if (VDBG) log("RMV <- {" + event + "}");
- events.add(event);
- } while (event.isClassContinue());
-
- final long endTime = SystemClock.elapsedRealtime();
- if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
- loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
- }
-
- if (event.isClassClientError()) {
- throw new NativeDaemonArgumentException(logCmd, event);
- }
- if (event.isClassServerError()) {
- throw new NativeDaemonFailureException(logCmd, event);
- }
-
- return events.toArray(new NativeDaemonEvent[events.size()]);
- }
-
- /**
- * Append the given argument to {@link StringBuilder}, escaping as needed,
- * and surrounding with quotes when it contains spaces.
- */
- @VisibleForTesting
- static void appendEscaped(StringBuilder builder, String arg) {
- final boolean hasSpaces = arg.indexOf(' ') >= 0;
- if (hasSpaces) {
- builder.append('"');
- }
-
- final int length = arg.length();
- for (int i = 0; i < length; i++) {
- final char c = arg.charAt(i);
-
- if (c == '"') {
- builder.append("\\\"");
- } else if (c == '\\') {
- builder.append("\\\\");
- } else {
- builder.append(c);
- }
- }
-
- if (hasSpaces) {
- builder.append('"');
- }
- }
-
- private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
- public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
- super(command, event);
- }
-
- @Override
- public IllegalArgumentException rethrowAsParcelableException() {
- throw new IllegalArgumentException(getMessage(), this);
- }
- }
-
- private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
- public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
- super(command, event);
- }
- }
-
- /**
- * Command builder that handles argument list building. Any arguments must
- * be separated from base command so they can be properly escaped.
- */
- public static class Command {
- private String mCmd;
- private ArrayList<Object> mArguments = new ArrayList<>();
-
- public Command(String cmd, Object... args) {
- mCmd = cmd;
- for (Object arg : args) {
- appendArg(arg);
- }
- }
-
- public Command appendArg(Object arg) {
- mArguments.add(arg);
- return this;
- }
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mLocalLog.dump(fd, pw, args);
- pw.println();
- mResponseQueue.dump(fd, pw, args);
- }
-
- private void log(String logstring) {
- if (mDebug) Log.d(TAG, logstring);
- mLocalLog.log(logstring);
- }
-
- private void loge(String logstring) {
- Log.e(TAG, logstring);
- mLocalLog.log(logstring);
- }
-
- private static class ResponseQueue {
-
- private static class PendingCmd {
- public final int cmdNum;
- public final String logCmd;
-
- public BlockingQueue<NativeDaemonEvent> responses =
- new ArrayBlockingQueue<NativeDaemonEvent>(10);
-
- // The availableResponseCount member is used to track when we can remove this
- // instance from the ResponseQueue.
- // This is used under the protection of a sync of the mPendingCmds object.
- // A positive value means we've had more writers retreive this object while
- // a negative value means we've had more readers. When we've had an equal number
- // (it goes to zero) we can remove this object from the mPendingCmds list.
- // Note that we may have more responses for this command (and more readers
- // coming), but that would result in a new PendingCmd instance being created
- // and added with the same cmdNum.
- // Also note that when this goes to zero it just means a parity of readers and
- // writers have retrieved this object - not that they are done using it. The
- // responses queue may well have more responses yet to be read or may get more
- // responses added to it. But all those readers/writers have retreived and
- // hold references to this instance already so it can be removed from
- // mPendingCmds queue.
- public int availableResponseCount;
-
- public PendingCmd(int cmdNum, String logCmd) {
- this.cmdNum = cmdNum;
- this.logCmd = logCmd;
- }
- }
-
- private final LinkedList<PendingCmd> mPendingCmds;
- private int mMaxCount;
-
- ResponseQueue(int maxCount) {
- mPendingCmds = new LinkedList<PendingCmd>();
- mMaxCount = maxCount;
- }
-
- public void add(int cmdNum, NativeDaemonEvent response) {
- PendingCmd found = null;
- synchronized (mPendingCmds) {
- for (PendingCmd pendingCmd : mPendingCmds) {
- if (pendingCmd.cmdNum == cmdNum) {
- found = pendingCmd;
- break;
- }
- }
- if (found == null) {
- // didn't find it - make sure our queue isn't too big before adding
- while (mPendingCmds.size() >= mMaxCount) {
- Log.e("NativeDaemonConnector.ResponseQueue",
- "more buffered than allowed: " + mPendingCmds.size() +
- " >= " + mMaxCount);
- // let any waiter timeout waiting for this
- PendingCmd pendingCmd = mPendingCmds.remove();
- Log.e("NativeDaemonConnector.ResponseQueue",
- "Removing request: " + pendingCmd.logCmd + " (" +
- pendingCmd.cmdNum + ")");
- }
- found = new PendingCmd(cmdNum, null);
- mPendingCmds.add(found);
- }
- found.availableResponseCount++;
- // if a matching remove call has already retrieved this we can remove this
- // instance from our list
- if (found.availableResponseCount == 0) mPendingCmds.remove(found);
- }
- try {
- found.responses.put(response);
- } catch (InterruptedException e) { }
- }
-
- // note that the timeout does not count time in deep sleep. If you don't want
- // the device to sleep, hold a wakelock
- public NativeDaemonEvent remove(int cmdNum, long timeoutMs, String logCmd) {
- PendingCmd found = null;
- synchronized (mPendingCmds) {
- for (PendingCmd pendingCmd : mPendingCmds) {
- if (pendingCmd.cmdNum == cmdNum) {
- found = pendingCmd;
- break;
- }
- }
- if (found == null) {
- found = new PendingCmd(cmdNum, logCmd);
- mPendingCmds.add(found);
- }
- found.availableResponseCount--;
- // if a matching add call has already retrieved this we can remove this
- // instance from our list
- if (found.availableResponseCount == 0) mPendingCmds.remove(found);
- }
- NativeDaemonEvent result = null;
- try {
- result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {}
- if (result == null) {
- Log.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
- }
- return result;
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Pending requests:");
- synchronized (mPendingCmds) {
- for (PendingCmd pendingCmd : mPendingCmds) {
- pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
- }
- }
- }
- }
-}
diff --git a/service-t/src/com/android/server/NativeDaemonConnectorException.java b/service-t/src/com/android/server/NativeDaemonConnectorException.java
deleted file mode 100644
index 4d8881c..0000000
--- a/service-t/src/com/android/server/NativeDaemonConnectorException.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.os.Parcel;
-
-/**
- * An exception that indicates there was an error with a
- * {@link NativeDaemonConnector} operation.
- */
-public class NativeDaemonConnectorException extends Exception {
- private String mCmd;
- private NativeDaemonEvent mEvent;
-
- public NativeDaemonConnectorException(String detailMessage) {
- super(detailMessage);
- }
-
- public NativeDaemonConnectorException(String detailMessage, Throwable throwable) {
- super(detailMessage, throwable);
- }
-
- public NativeDaemonConnectorException(String cmd, NativeDaemonEvent event) {
- super("command '" + cmd + "' failed with '" + event + "'");
- mCmd = cmd;
- mEvent = event;
- }
-
- public int getCode() {
- return mEvent != null ? mEvent.getCode() : -1;
- }
-
- public String getCmd() {
- return mCmd;
- }
-
- /**
- * Rethrow as a {@link RuntimeException} subclass that is handled by
- * {@link Parcel#writeException(Exception)}.
- */
- public IllegalArgumentException rethrowAsParcelableException() {
- throw new IllegalStateException(getMessage(), this);
- }
-}
diff --git a/service-t/src/com/android/server/NativeDaemonEvent.java b/service-t/src/com/android/server/NativeDaemonEvent.java
deleted file mode 100644
index 5683694..0000000
--- a/service-t/src/com/android/server/NativeDaemonEvent.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.util.ArrayList;
-
-/**
- * Parsed event from native side of {@link NativeDaemonConnector}.
- */
-public class NativeDaemonEvent {
-
- // TODO: keep class ranges in sync with ResponseCode.h
- // TODO: swap client and server error ranges to roughly mirror HTTP spec
-
- private final int mCmdNumber;
- private final int mCode;
- private final String mMessage;
- private final String mRawEvent;
- private final String mLogMessage;
- private String[] mParsed;
- private FileDescriptor[] mFdList;
-
- private NativeDaemonEvent(int cmdNumber, int code, String message,
- String rawEvent, String logMessage, FileDescriptor[] fdList) {
- mCmdNumber = cmdNumber;
- mCode = code;
- mMessage = message;
- mRawEvent = rawEvent;
- mLogMessage = logMessage;
- mParsed = null;
- mFdList = fdList;
- }
-
- static public final String SENSITIVE_MARKER = "{{sensitive}}";
-
- public int getCmdNumber() {
- return mCmdNumber;
- }
-
- public int getCode() {
- return mCode;
- }
-
- public String getMessage() {
- return mMessage;
- }
-
- public FileDescriptor[] getFileDescriptors() {
- return mFdList;
- }
-
- @Deprecated
- public String getRawEvent() {
- return mRawEvent;
- }
-
- @Override
- public String toString() {
- return mLogMessage;
- }
-
- /**
- * Test if event represents a partial response which is continued in
- * additional subsequent events.
- */
- public boolean isClassContinue() {
- return mCode >= 100 && mCode < 200;
- }
-
- /**
- * Test if event represents a command success.
- */
- public boolean isClassOk() {
- return mCode >= 200 && mCode < 300;
- }
-
- /**
- * Test if event represents a remote native daemon error.
- */
- public boolean isClassServerError() {
- return mCode >= 400 && mCode < 500;
- }
-
- /**
- * Test if event represents a command syntax or argument error.
- */
- public boolean isClassClientError() {
- return mCode >= 500 && mCode < 600;
- }
-
- /**
- * Test if event represents an unsolicited event from native daemon.
- */
- public boolean isClassUnsolicited() {
- return isClassUnsolicited(mCode);
- }
-
- private static boolean isClassUnsolicited(int code) {
- return code >= 600 && code < 700;
- }
-
- /**
- * Verify this event matches the given code.
- *
- * @throws IllegalStateException if {@link #getCode()} doesn't match.
- */
- public void checkCode(int code) {
- if (mCode != code) {
- throw new IllegalStateException("Expected " + code + " but was: " + this);
- }
- }
-
- /**
- * Parse the given raw event into {@link NativeDaemonEvent} instance.
- *
- * @throws IllegalArgumentException when line doesn't match format expected
- * from native side.
- */
- public static NativeDaemonEvent parseRawEvent(String rawEvent, FileDescriptor[] fdList) {
- final String[] parsed = rawEvent.split(" ");
- if (parsed.length < 2) {
- throw new IllegalArgumentException("Insufficient arguments");
- }
-
- int skiplength = 0;
-
- final int code;
- try {
- code = Integer.parseInt(parsed[0]);
- skiplength = parsed[0].length() + 1;
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("problem parsing code", e);
- }
-
- int cmdNumber = -1;
- if (isClassUnsolicited(code) == false) {
- if (parsed.length < 3) {
- throw new IllegalArgumentException("Insufficient arguemnts");
- }
- try {
- cmdNumber = Integer.parseInt(parsed[1]);
- skiplength += parsed[1].length() + 1;
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("problem parsing cmdNumber", e);
- }
- }
-
- String logMessage = rawEvent;
- if (parsed.length > 2 && parsed[2].equals(SENSITIVE_MARKER)) {
- skiplength += parsed[2].length() + 1;
- logMessage = parsed[0] + " " + parsed[1] + " {}";
- }
-
- final String message = rawEvent.substring(skiplength);
-
- return new NativeDaemonEvent(cmdNumber, code, message, rawEvent, logMessage, fdList);
- }
-
- /**
- * Filter the given {@link NativeDaemonEvent} list, returning
- * {@link #getMessage()} for any events matching the requested code.
- */
- public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
- final ArrayList<String> result = new ArrayList<>();
- for (NativeDaemonEvent event : events) {
- if (event.getCode() == matchCode) {
- result.add(event.getMessage());
- }
- }
- return result.toArray(new String[result.size()]);
- }
-
- /**
- * Find the Nth field of the event.
- *
- * This ignores and code or cmdNum, the first return value is given for N=0.
- * Also understands "\"quoted\" multiword responses" and tries them as a single field
- */
- public String getField(int n) {
- if (mParsed == null) {
- mParsed = unescapeArgs(mRawEvent);
- }
- n += 2; // skip code and command#
- if (n > mParsed.length) return null;
- return mParsed[n];
- }
-
- public static String[] unescapeArgs(String rawEvent) {
- final boolean DEBUG_ROUTINE = false;
- final String LOGTAG = "unescapeArgs";
- final ArrayList<String> parsed = new ArrayList<String>();
- final int length = rawEvent.length();
- int current = 0;
- int wordEnd = -1;
- boolean quoted = false;
-
- if (DEBUG_ROUTINE) Log.e(LOGTAG, "parsing '" + rawEvent + "'");
- if (rawEvent.charAt(current) == '\"') {
- quoted = true;
- current++;
- }
- while (current < length) {
- // find the end of the word
- char terminator = quoted ? '\"' : ' ';
- wordEnd = current;
- while (wordEnd < length && rawEvent.charAt(wordEnd) != terminator) {
- if (rawEvent.charAt(wordEnd) == '\\') {
- // skip the escaped char
- ++wordEnd;
- }
- ++wordEnd;
- }
- if (wordEnd > length) wordEnd = length;
- String word = rawEvent.substring(current, wordEnd);
- current += word.length();
- if (!quoted) {
- word = word.trim();
- } else {
- current++; // skip the trailing quote
- }
- // unescape stuff within the word
- word = word.replace("\\\\", "\\");
- word = word.replace("\\\"", "\"");
-
- if (DEBUG_ROUTINE) Log.e(LOGTAG, "found '" + word + "'");
- parsed.add(word);
-
- // find the beginning of the next word - either of these options
- int nextSpace = rawEvent.indexOf(' ', current);
- int nextQuote = rawEvent.indexOf(" \"", current);
- if (DEBUG_ROUTINE) {
- Log.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
- }
- if (nextQuote > -1 && nextQuote <= nextSpace) {
- quoted = true;
- current = nextQuote + 2;
- } else {
- quoted = false;
- if (nextSpace > -1) {
- current = nextSpace + 1;
- }
- } // else we just start the next word after the current and read til the end
- if (DEBUG_ROUTINE) {
- Log.e(LOGTAG, "next loop - current=" + current
- + ", length=" + length + ", quoted=" + quoted);
- }
- }
- return parsed.toArray(new String[parsed.size()]);
- }
-}
diff --git a/service-t/src/com/android/server/NativeDaemonTimeoutException.java b/service-t/src/com/android/server/NativeDaemonTimeoutException.java
deleted file mode 100644
index 658f7d6..0000000
--- a/service-t/src/com/android/server/NativeDaemonTimeoutException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 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;
-
-/**
- * An exception that indicates there was a timeout with a
- * {@link NativeDaemonConnector} operation.
- */
-public class NativeDaemonTimeoutException extends NativeDaemonConnectorException {
- public NativeDaemonTimeoutException(String command, NativeDaemonEvent event) {
- super(command, event);
- }
-}
-
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index ddf6d2c..4086e4e 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -16,15 +16,24 @@
package com.android.server;
+import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
+
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.mdns.aidl.DiscoveryInfo;
+import android.net.mdns.aidl.GetAddressInfo;
+import android.net.mdns.aidl.IMDnsEventListener;
+import android.net.mdns.aidl.RegistrationInfo;
+import android.net.mdns.aidl.ResolutionInfo;
import android.net.nsd.INsdManager;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
+import android.net.nsd.MDnsManager;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Handler;
@@ -33,7 +42,6 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -42,7 +50,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.net.module.util.DnsSdTxtRecord;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -50,9 +57,7 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.concurrent.CountDownLatch;
/**
* Network Service Discovery Service handles remote service discovery operation requests by
@@ -64,14 +69,18 @@
private static final String TAG = "NsdService";
private static final String MDNS_TAG = "mDnsConnector";
- private static final boolean DBG = true;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final long CLEANUP_DELAY_MS = 10000;
private static final int IFACE_IDX_ANY = 0;
private final Context mContext;
private final NsdStateMachine mNsdStateMachine;
- private final DaemonConnection mDaemon;
- private final NativeCallbackReceiver mDaemonCallback;
+ private final MDnsManager mMDnsManager;
+ private final MDnsEventCallback mMDnsEventCallback;
+ // WARNING : Accessing this value in any thread is not safe, it must only be changed in the
+ // state machine thread. If change this outside state machine, it will need to introduce
+ // synchronization.
+ private boolean mIsDaemonStarted = false;
/**
* Clients receiving asynchronous messages
@@ -100,10 +109,26 @@
}
private void maybeStartDaemon() {
- mDaemon.maybeStart();
+ if (mIsDaemonStarted) {
+ if (DBG) Log.d(TAG, "Daemon is already started.");
+ return;
+ }
+ mMDnsManager.registerEventListener(mMDnsEventCallback);
+ mMDnsManager.startDaemon();
+ mIsDaemonStarted = true;
maybeScheduleStop();
}
+ private void maybeStopDaemon() {
+ if (!mIsDaemonStarted) {
+ if (DBG) Log.d(TAG, "Daemon has not been started.");
+ return;
+ }
+ mMDnsManager.unregisterEventListener(mMDnsEventCallback);
+ mMDnsManager.stopDaemon();
+ mIsDaemonStarted = false;
+ }
+
private boolean isAnyRequestActive() {
return mIdToClientInfoMap.size() != 0;
}
@@ -198,7 +223,7 @@
}
break;
case NsdManager.DAEMON_CLEANUP:
- mDaemon.maybeStop();
+ maybeStopDaemon();
break;
// This event should be only sent by the legacy (target SDK < S) clients.
// Mark the sending client as legacy.
@@ -211,7 +236,6 @@
maybeStartDaemon();
}
break;
- case NsdManager.NATIVE_DAEMON_EVENT:
default:
Log.e(TAG, "Unhandled " + msg);
return NOT_HANDLED;
@@ -397,9 +421,8 @@
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
}
break;
- case NsdManager.NATIVE_DAEMON_EVENT:
- NativeEvent event = (NativeEvent) msg.obj;
- if (!handleNativeEvent(event.code, event.raw, event.cooked)) {
+ case MDNS_SERVICE_EVENT:
+ if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) {
return NOT_HANDLED;
}
break;
@@ -409,13 +432,11 @@
return HANDLED;
}
- private boolean handleNativeEvent(int code, String raw, String[] cooked) {
+ private boolean handleMDnsServiceEvent(int code, int id, Object obj) {
NsdServiceInfo servInfo;
- int id = Integer.parseInt(cooked[1]);
ClientInfo clientInfo = mIdToClientInfoMap.get(id);
if (clientInfo == null) {
- String name = NativeResponseCode.nameOf(code);
- Log.e(TAG, String.format("id %d for %s has no client mapping", id, name));
+ Log.e(TAG, String.format("id %d for %d has no client mapping", id, code));
return false;
}
@@ -425,27 +446,20 @@
// This can happen because of race conditions. For example,
// SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
// and we may get in this situation.
- String name = NativeResponseCode.nameOf(code);
- Log.d(TAG, String.format(
- "Notification %s for listener id %d that is no longer active",
- name, id));
+ Log.d(TAG, String.format("%d for listener id %d that is no longer active",
+ code, id));
return false;
}
if (DBG) {
- String name = NativeResponseCode.nameOf(code);
- Log.d(TAG, String.format("Native daemon message %s: %s", name, raw));
+ Log.d(TAG, String.format("MDns service event code:%d id=%d", code, id));
}
switch (code) {
- case NativeResponseCode.SERVICE_FOUND:
- /* NNN uniqueId serviceName regType domain interfaceIdx netId */
- servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
- final int foundNetId;
- try {
- foundNetId = Integer.parseInt(cooked[6]);
- } catch (NumberFormatException e) {
- Log.wtf(TAG, "Invalid network received from mdnsd: " + cooked[6]);
- break;
- }
+ case IMDnsEventListener.SERVICE_FOUND: {
+ final DiscoveryInfo info = (DiscoveryInfo) obj;
+ final String name = info.serviceName;
+ final String type = info.registrationType;
+ servInfo = new NsdServiceInfo(name, type);
+ final int foundNetId = info.netId;
if (foundNetId == 0L) {
// Ignore services that do not have a Network: they are not usable
// by apps, as they would need privileged permissions to use
@@ -455,74 +469,65 @@
servInfo.setNetwork(new Network(foundNetId));
clientInfo.onServiceFound(clientId, servInfo);
break;
- case NativeResponseCode.SERVICE_LOST:
- /* NNN uniqueId serviceName regType domain interfaceIdx netId */
- final int lostNetId;
- try {
- lostNetId = Integer.parseInt(cooked[6]);
- } catch (NumberFormatException e) {
- Log.wtf(TAG, "Invalid network received from mdnsd: " + cooked[6]);
- break;
- }
- servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
+ }
+ case IMDnsEventListener.SERVICE_LOST: {
+ final DiscoveryInfo info = (DiscoveryInfo) obj;
+ final String name = info.serviceName;
+ final String type = info.registrationType;
+ final int lostNetId = info.netId;
+ servInfo = new NsdServiceInfo(name, type);
// The network could be null if it was torn down when the service is lost
// TODO: avoid returning null in that case, possibly by remembering found
// services on the same interface index and their network at the time
servInfo.setNetwork(lostNetId == 0 ? null : new Network(lostNetId));
clientInfo.onServiceLost(clientId, servInfo);
break;
- case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
- /* NNN uniqueId errorCode */
+ }
+ case IMDnsEventListener.SERVICE_DISCOVERY_FAILED:
clientInfo.onDiscoverServicesFailed(
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
break;
- case NativeResponseCode.SERVICE_REGISTERED:
- /* NNN regId serviceName regType */
- servInfo = new NsdServiceInfo(cooked[2], null);
+ case IMDnsEventListener.SERVICE_REGISTERED: {
+ final RegistrationInfo info = (RegistrationInfo) obj;
+ final String name = info.serviceName;
+ servInfo = new NsdServiceInfo(name, null /* serviceType */);
clientInfo.onRegisterServiceSucceeded(clientId, servInfo);
break;
- case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
- /* NNN regId errorCode */
+ }
+ case IMDnsEventListener.SERVICE_REGISTRATION_FAILED:
clientInfo.onRegisterServiceFailed(
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
break;
- case NativeResponseCode.SERVICE_UPDATED:
- /* NNN regId */
- break;
- case NativeResponseCode.SERVICE_UPDATE_FAILED:
- /* NNN regId errorCode */
- break;
- case NativeResponseCode.SERVICE_RESOLVED:
- /* NNN resolveId fullName hostName port txtlen txtdata interfaceIdx */
+ case IMDnsEventListener.SERVICE_RESOLVED: {
+ final ResolutionInfo info = (ResolutionInfo) obj;
int index = 0;
- while (index < cooked[2].length() && cooked[2].charAt(index) != '.') {
- if (cooked[2].charAt(index) == '\\') {
+ final String fullName = info.serviceFullName;
+ while (index < fullName.length() && fullName.charAt(index) != '.') {
+ if (fullName.charAt(index) == '\\') {
++index;
}
++index;
}
- if (index >= cooked[2].length()) {
- Log.e(TAG, "Invalid service found " + raw);
+ if (index >= fullName.length()) {
+ Log.e(TAG, "Invalid service found " + fullName);
break;
}
- String name = cooked[2].substring(0, index);
- String rest = cooked[2].substring(index);
+ String name = fullName.substring(0, index);
+ String rest = fullName.substring(index);
String type = rest.replace(".local.", "");
- name = unescape(name);
-
clientInfo.mResolvedService.setServiceName(name);
clientInfo.mResolvedService.setServiceType(type);
- clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
- clientInfo.mResolvedService.setTxtRecords(cooked[6]);
+ clientInfo.mResolvedService.setPort(info.port);
+ clientInfo.mResolvedService.setTxtRecords(info.txtRecord);
// Network will be added after SERVICE_GET_ADDR_SUCCESS
stopResolveService(id);
removeRequestMap(clientId, id, clientInfo);
- int id2 = getUniqueId();
- if (getAddrInfo(id2, cooked[3], cooked[7] /* interfaceIdx */)) {
+ final int id2 = getUniqueId();
+ if (getAddrInfo(id2, info.hostname, info.interfaceIdx)) {
storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
} else {
clientInfo.onResolveServiceFailed(
@@ -530,7 +535,8 @@
clientInfo.mResolvedService = null;
}
break;
- case NativeResponseCode.SERVICE_RESOLUTION_FAILED:
+ }
+ case IMDnsEventListener.SERVICE_RESOLUTION_FAILED:
/* NNN resolveId errorCode */
stopResolveService(id);
removeRequestMap(clientId, id, clientInfo);
@@ -538,7 +544,7 @@
clientInfo.onResolveServiceFailed(
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
break;
- case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
+ case IMDnsEventListener.SERVICE_GET_ADDR_FAILED:
/* NNN resolveId errorCode */
stopGetAddrInfo(id);
removeRequestMap(clientId, id, clientInfo);
@@ -546,19 +552,15 @@
clientInfo.onResolveServiceFailed(
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
break;
- case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
+ case IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS: {
/* NNN resolveId hostname ttl addr interfaceIdx netId */
- Network network = null;
- try {
- final int netId = Integer.parseInt(cooked[6]);
- network = netId == 0L ? null : new Network(netId);
- } catch (NumberFormatException e) {
- Log.wtf(TAG, "Invalid network in GET_ADDR_SUCCESS: " + cooked[6], e);
- }
-
+ final GetAddressInfo info = (GetAddressInfo) obj;
+ final String address = info.address;
+ final int netId = info.netId;
+ final Network network = netId == NETID_UNSET ? null : new Network(netId);
InetAddress serviceHost = null;
try {
- serviceHost = InetAddress.getByName(cooked[4]);
+ serviceHost = InetAddress.getByName(address);
} catch (UnknownHostException e) {
Log.wtf(TAG, "Invalid host in GET_ADDR_SUCCESS", e);
}
@@ -579,6 +581,7 @@
removeRequestMap(clientId, id, clientInfo);
clientInfo.mResolvedService = null;
break;
+ }
default:
return false;
}
@@ -587,50 +590,66 @@
}
}
- private String unescape(String s) {
- StringBuilder sb = new StringBuilder(s.length());
- for (int i = 0; i < s.length(); ++i) {
- char c = s.charAt(i);
- if (c == '\\') {
- if (++i >= s.length()) {
- Log.e(TAG, "Unexpected end of escape sequence in: " + s);
- break;
- }
- c = s.charAt(i);
- if (c != '.' && c != '\\') {
- if (i + 2 >= s.length()) {
- Log.e(TAG, "Unexpected end of escape sequence in: " + s);
- break;
- }
- c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0'));
- i += 2;
- }
- }
- sb.append(c);
- }
- return sb.toString();
- }
-
@VisibleForTesting
- NsdService(Context ctx, Handler handler, DaemonConnectionSupplier fn, long cleanupDelayMs) {
+ NsdService(Context ctx, Handler handler, long cleanupDelayMs) {
mCleanupDelayMs = cleanupDelayMs;
mContext = ctx;
mNsdStateMachine = new NsdStateMachine(TAG, handler);
mNsdStateMachine.start();
- mDaemonCallback = new NativeCallbackReceiver();
- mDaemon = fn.get(mDaemonCallback);
+ mMDnsManager = ctx.getSystemService(MDnsManager.class);
+ mMDnsEventCallback = new MDnsEventCallback(mNsdStateMachine);
}
- public static NsdService create(Context context) throws InterruptedException {
+ public static NsdService create(Context context) {
HandlerThread thread = new HandlerThread(TAG);
thread.start();
Handler handler = new Handler(thread.getLooper());
- NsdService service =
- new NsdService(context, handler, DaemonConnection::new, CLEANUP_DELAY_MS);
- service.mDaemonCallback.awaitConnection();
+ NsdService service = new NsdService(context, handler, CLEANUP_DELAY_MS);
return service;
}
+ private static class MDnsEventCallback extends IMDnsEventListener.Stub {
+ private final StateMachine mStateMachine;
+
+ MDnsEventCallback(StateMachine sm) {
+ mStateMachine = sm;
+ }
+
+ @Override
+ public void onServiceRegistrationStatus(final RegistrationInfo status) {
+ mStateMachine.sendMessage(
+ MDNS_SERVICE_EVENT, status.result, status.id, status);
+ }
+
+ @Override
+ public void onServiceDiscoveryStatus(final DiscoveryInfo status) {
+ mStateMachine.sendMessage(
+ MDNS_SERVICE_EVENT, status.result, status.id, status);
+ }
+
+ @Override
+ public void onServiceResolutionStatus(final ResolutionInfo status) {
+ mStateMachine.sendMessage(
+ MDNS_SERVICE_EVENT, status.result, status.id, status);
+ }
+
+ @Override
+ public void onGettingServiceAddressStatus(final GetAddressInfo status) {
+ mStateMachine.sendMessage(
+ MDNS_SERVICE_EVENT, status.result, status.id, status);
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return this.HASH;
+ }
+ }
+
@Override
public INsdServiceConnector connect(INsdManagerCallback cb) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
@@ -711,140 +730,6 @@
return mUniqueId;
}
- /* These should be in sync with system/netd/server/ResponseCode.h */
- static final class NativeResponseCode {
- public static final int SERVICE_DISCOVERY_FAILED = 602;
- public static final int SERVICE_FOUND = 603;
- public static final int SERVICE_LOST = 604;
-
- public static final int SERVICE_REGISTRATION_FAILED = 605;
- public static final int SERVICE_REGISTERED = 606;
-
- public static final int SERVICE_RESOLUTION_FAILED = 607;
- public static final int SERVICE_RESOLVED = 608;
-
- public static final int SERVICE_UPDATED = 609;
- public static final int SERVICE_UPDATE_FAILED = 610;
-
- public static final int SERVICE_GET_ADDR_FAILED = 611;
- public static final int SERVICE_GET_ADDR_SUCCESS = 612;
-
- private static final SparseArray<String> CODE_NAMES = new SparseArray<>();
- static {
- CODE_NAMES.put(SERVICE_DISCOVERY_FAILED, "SERVICE_DISCOVERY_FAILED");
- CODE_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND");
- CODE_NAMES.put(SERVICE_LOST, "SERVICE_LOST");
- CODE_NAMES.put(SERVICE_REGISTRATION_FAILED, "SERVICE_REGISTRATION_FAILED");
- CODE_NAMES.put(SERVICE_REGISTERED, "SERVICE_REGISTERED");
- CODE_NAMES.put(SERVICE_RESOLUTION_FAILED, "SERVICE_RESOLUTION_FAILED");
- CODE_NAMES.put(SERVICE_RESOLVED, "SERVICE_RESOLVED");
- CODE_NAMES.put(SERVICE_UPDATED, "SERVICE_UPDATED");
- CODE_NAMES.put(SERVICE_UPDATE_FAILED, "SERVICE_UPDATE_FAILED");
- CODE_NAMES.put(SERVICE_GET_ADDR_FAILED, "SERVICE_GET_ADDR_FAILED");
- CODE_NAMES.put(SERVICE_GET_ADDR_SUCCESS, "SERVICE_GET_ADDR_SUCCESS");
- }
-
- static String nameOf(int code) {
- String name = CODE_NAMES.get(code);
- if (name == null) {
- return Integer.toString(code);
- }
- return name;
- }
- }
-
- private class NativeEvent {
- final int code;
- final String raw;
- final String[] cooked;
-
- NativeEvent(int code, String raw, String[] cooked) {
- this.code = code;
- this.raw = raw;
- this.cooked = cooked;
- }
- }
-
- class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
- private final CountDownLatch connected = new CountDownLatch(1);
-
- public void awaitConnection() throws InterruptedException {
- connected.await();
- }
-
- @Override
- public void onDaemonConnected() {
- connected.countDown();
- }
-
- @Override
- public boolean onCheckHoldWakeLock(int code) {
- return false;
- }
-
- @Override
- public boolean onEvent(int code, String raw, String[] cooked) {
- // TODO: NDC translates a message to a callback, we could enhance NDC to
- // directly interact with a state machine through messages
- NativeEvent event = new NativeEvent(code, raw, cooked);
- mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
- return true;
- }
- }
-
- interface DaemonConnectionSupplier {
- DaemonConnection get(NativeCallbackReceiver callback);
- }
-
- @VisibleForTesting
- public static class DaemonConnection {
- final NativeDaemonConnector mNativeConnector;
- boolean mIsStarted = false;
-
- DaemonConnection(NativeCallbackReceiver callback) {
- mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null);
- new Thread(mNativeConnector, MDNS_TAG).start();
- }
-
- /**
- * Executes the specified cmd on the daemon.
- */
- public boolean execute(Object... args) {
- if (DBG) {
- Log.d(TAG, "mdnssd " + Arrays.toString(args));
- }
- try {
- mNativeConnector.execute("mdnssd", args);
- } catch (NativeDaemonConnectorException e) {
- Log.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
- return false;
- }
- return true;
- }
-
- /**
- * Starts the daemon if it is not already started.
- */
- public void maybeStart() {
- if (mIsStarted) {
- return;
- }
- execute("start-service");
- mIsStarted = true;
- }
-
- /**
- * Stops the daemon if it is started.
- */
- public void maybeStop() {
- if (!mIsStarted) {
- return;
- }
- execute("stop-service");
- mIsStarted = false;
- }
- }
-
private boolean registerService(int regId, NsdServiceInfo service) {
if (DBG) {
Log.d(TAG, "registerService: " + regId + " " + service);
@@ -853,34 +738,26 @@
String type = service.getServiceType();
int port = service.getPort();
byte[] textRecord = service.getTxtRecord();
- String record = Base64.encodeToString(textRecord, Base64.DEFAULT).replace("\n", "");
- return mDaemon.execute("register", regId, name, type, port, record);
+ return mMDnsManager.registerService(regId, name, type, port, textRecord, IFACE_IDX_ANY);
}
private boolean unregisterService(int regId) {
- return mDaemon.execute("stop-register", regId);
- }
-
- private boolean updateService(int regId, DnsSdTxtRecord t) {
- if (t == null) {
- return false;
- }
- return mDaemon.execute("update", regId, t.size(), t.getRawData());
+ return mMDnsManager.stopOperation(regId);
}
private boolean discoverServices(int discoveryId, NsdServiceInfo serviceInfo) {
final Network network = serviceInfo.getNetwork();
+ final String type = serviceInfo.getServiceType();
final int discoverInterface = getNetworkInterfaceIndex(network);
if (network != null && discoverInterface == IFACE_IDX_ANY) {
Log.e(TAG, "Interface to discover service on not found");
return false;
}
- return mDaemon.execute("discover", discoveryId, serviceInfo.getServiceType(),
- discoverInterface);
+ return mMDnsManager.discover(discoveryId, type, discoverInterface);
}
private boolean stopServiceDiscovery(int discoveryId) {
- return mDaemon.execute("stop-discover", discoveryId);
+ return mMDnsManager.stopOperation(discoveryId);
}
private boolean resolveService(int resolveId, NsdServiceInfo service) {
@@ -892,7 +769,7 @@
Log.e(TAG, "Interface to resolve service on not found");
return false;
}
- return mDaemon.execute("resolve", resolveId, name, type, "local.", resolveInterface);
+ return mMDnsManager.resolve(resolveId, name, type, "local.", resolveInterface);
}
/**
@@ -933,16 +810,15 @@
}
private boolean stopResolveService(int resolveId) {
- return mDaemon.execute("stop-resolve", resolveId);
+ return mMDnsManager.stopOperation(resolveId);
}
- private boolean getAddrInfo(int resolveId, String hostname, String interfaceIdx) {
- // interfaceIdx is always obtained (as string) from the service resolved callback
- return mDaemon.execute("getaddrinfo", resolveId, hostname, interfaceIdx);
+ private boolean getAddrInfo(int resolveId, String hostname, int interfaceIdx) {
+ return mMDnsManager.getServiceAddress(resolveId, hostname, interfaceIdx);
}
private boolean stopGetAddrInfo(int resolveId) {
- return mDaemon.execute("stop-getaddrinfo", resolveId);
+ return mMDnsManager.stopOperation(resolveId);
}
@Override
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index d910629..eb22f78 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -23,14 +23,13 @@
import android.net.ConnectivityManager;
import android.net.ConnectivityResources;
import android.net.EthernetManager;
-import android.net.EthernetNetworkSpecifier;
import android.net.EthernetNetworkManagementException;
+import android.net.EthernetNetworkSpecifier;
import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkProperties;
-import android.net.Network;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index c291b3f..693d91a 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -29,8 +29,8 @@
import android.net.ConnectivityResources;
import android.net.EthernetManager;
import android.net.IEthernetServiceListener;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.INetd;
+import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.ITetheredInterfaceCallback;
import android.net.InterfaceConfigurationParcel;
import android.net.IpConfiguration;
@@ -57,6 +57,7 @@
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@@ -389,10 +390,33 @@
mHandler.post(() -> {
mIncludeTestInterfaces = include;
updateIfaceMatchRegexp();
+ if (!include) {
+ removeTestData();
+ }
mHandler.post(() -> trackAvailableInterfaces());
});
}
+ private void removeTestData() {
+ removeTestIpData();
+ removeTestCapabilityData();
+ }
+
+ private void removeTestIpData() {
+ final Iterator<String> iterator = mIpConfigurations.keySet().iterator();
+ while (iterator.hasNext()) {
+ final String iface = iterator.next();
+ if (iface.matches(TEST_IFACE_REGEXP)) {
+ mConfigStore.write(iface, null);
+ iterator.remove();
+ }
+ }
+ }
+
+ private void removeTestCapabilityData() {
+ mNetworkCapabilities.keySet().removeIf(iface -> iface.matches(TEST_IFACE_REGEXP));
+ }
+
public void requestTetheredInterface(ITetheredInterfaceCallback callback) {
mHandler.post(() -> {
if (!mTetheredInterfaceRequests.register(callback)) {
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index e90b29b..4b21569 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -108,5 +108,8 @@
# From filegroup framework-connectivity-protos
rule android.service.*Proto com.android.connectivity.@0
+# From mdns-aidl-interface
+rule android.net.mdns.aidl.** android.net.connectivity.@0
+
# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index 4517b5c..500c696 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -34,7 +34,6 @@
#include <netjniutils/netjniutils.h>
#include <private/android_filesystem_config.h>
-#include "libclat/bpfhelper.h"
#include "libclat/clatutils.h"
#include "nativehelper/scoped_utf_chars.h"
@@ -257,46 +256,6 @@
}
}
-int initTracker(const std::string& iface, const std::string& pfx96, const std::string& v4,
- const std::string& v6, net::clat::ClatdTracker* output) {
- strlcpy(output->iface, iface.c_str(), sizeof(output->iface));
- output->ifIndex = if_nametoindex(iface.c_str());
- if (output->ifIndex == 0) {
- ALOGE("interface %s not found", output->iface);
- return -1;
- }
-
- unsigned len = snprintf(output->v4iface, sizeof(output->v4iface),
- "%s%s", DEVICEPREFIX, iface.c_str());
- if (len >= sizeof(output->v4iface)) {
- ALOGE("interface name too long '%s'", output->v4iface);
- return -1;
- }
-
- output->v4ifIndex = if_nametoindex(output->v4iface);
- if (output->v4ifIndex == 0) {
- ALOGE("v4-interface %s not found", output->v4iface);
- return -1;
- }
-
- if (!inet_pton(AF_INET6, pfx96.c_str(), &output->pfx96)) {
- ALOGE("invalid IPv6 address specified for plat prefix: %s", pfx96.c_str());
- return -1;
- }
-
- if (!inet_pton(AF_INET, v4.c_str(), &output->v4)) {
- ALOGE("Invalid IPv4 address %s", v4.c_str());
- return -1;
- }
-
- if (!inet_pton(AF_INET6, v6.c_str(), &output->v6)) {
- ALOGE("Invalid source address %s", v6.c_str());
- return -1;
- }
-
- return 0;
-}
-
static jint com_android_server_connectivity_ClatCoordinator_startClatd(
JNIEnv* env, jobject clazz, jobject tunJavaFd, jobject readSockJavaFd,
jobject writeSockJavaFd, jstring iface, jstring pfx96, jstring v4, jstring v6) {
@@ -404,15 +363,6 @@
posix_spawnattr_destroy(&attr);
posix_spawn_file_actions_destroy(&fa);
- // 6. Start BPF if any
- if (!net::clat::initMaps()) {
- net::clat::ClatdTracker tracker = {};
- if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
- &tracker)) {
- net::clat::maybeStartBpf(tracker);
- }
- }
-
return pid;
}
@@ -467,14 +417,6 @@
return;
}
- if (!net::clat::initMaps()) {
- net::clat::ClatdTracker tracker = {};
- if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
- &tracker)) {
- net::clat::maybeStopBpf(tracker);
- }
- }
-
stopClatdProcess(pid);
}
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 17ee996..68e4dc4 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -19,19 +19,12 @@
cc_library_static {
name: "libclat",
defaults: ["netd_defaults"],
- header_libs: [
- "bpf_connectivity_headers",
- "libbase_headers",
- ],
srcs: [
- "TcUtils.cpp", // TODO: move to frameworks/libs/net
- "bpfhelper.cpp",
"clatutils.cpp",
],
stl: "libc++_static",
static_libs: [
"libip_checksum",
- "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
],
shared_libs: ["liblog"],
export_include_dirs: ["include"],
@@ -43,11 +36,7 @@
name: "libclat_test",
defaults: ["netd_defaults"],
test_suites: ["device-tests"],
- header_libs: [
- "bpf_connectivity_headers",
- ],
srcs: [
- "TcUtilsTest.cpp",
"clatutils_test.cpp",
],
static_libs: [
@@ -55,8 +44,6 @@
"libclat",
"libip_checksum",
"libnetd_test_tun_interface",
- "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
- "libtcutils",
],
shared_libs: [
"liblog",
diff --git a/service/native/libs/libclat/TcUtils.cpp b/service/native/libs/libclat/TcUtils.cpp
deleted file mode 100644
index cdfb763..0000000
--- a/service/native/libs/libclat/TcUtils.cpp
+++ /dev/null
@@ -1,390 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "TcUtils"
-
-#include "libclat/TcUtils.h"
-
-#include <arpa/inet.h>
-#include <linux/if.h>
-#include <linux/if_arp.h>
-#include <linux/netlink.h>
-#include <linux/pkt_cls.h>
-#include <linux/pkt_sched.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <log/log.h>
-
-#include "android-base/unique_fd.h"
-
-namespace android {
-namespace net {
-
-using std::max;
-
-// Sync from system/netd/server/NetlinkCommands.h
-const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
-const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
-
-static int doSIOCGIF(const std::string& interface, int opt) {
- base::unique_fd ufd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
-
- if (ufd < 0) {
- const int err = errno;
- ALOGE("socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)");
- return -err;
- };
-
- struct ifreq ifr = {};
- // We use strncpy() instead of strlcpy() since kernel has to be able
- // to handle non-zero terminated junk passed in by userspace anyway,
- // and this way too long interface names (more than IFNAMSIZ-1 = 15
- // characters plus terminating NULL) will not get truncated to 15
- // characters and zero-terminated and thus potentially erroneously
- // match a truncated interface if one were to exist.
- strncpy(ifr.ifr_name, interface.c_str(), sizeof(ifr.ifr_name));
-
- if (ioctl(ufd, opt, &ifr, sizeof(ifr))) return -errno;
-
- if (opt == SIOCGIFHWADDR) return ifr.ifr_hwaddr.sa_family;
- if (opt == SIOCGIFMTU) return ifr.ifr_mtu;
- return -EINVAL;
-}
-
-int hardwareAddressType(const std::string& interface) {
- return doSIOCGIF(interface, SIOCGIFHWADDR);
-}
-
-int deviceMTU(const std::string& interface) {
- return doSIOCGIF(interface, SIOCGIFMTU);
-}
-
-base::Result<bool> isEthernet(const std::string& interface) {
- int rv = hardwareAddressType(interface);
- if (rv < 0) {
- errno = -rv;
- return ErrnoErrorf("Get hardware address type of interface {} failed", interface);
- }
-
- switch (rv) {
- case ARPHRD_ETHER:
- return true;
- case ARPHRD_NONE:
- case ARPHRD_RAWIP: // in Linux 4.14+ rmnet support was upstreamed and this is 519
- case 530: // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
- return false;
- default:
- errno = EAFNOSUPPORT; // Address family not supported
- return ErrnoErrorf("Unknown hardware address type {} on interface {}", rv, interface);
- }
-}
-
-// TODO: use //system/netd/server/NetlinkCommands.cpp:openNetlinkSocket(protocol)
-// and //system/netd/server/SockDiag.cpp:checkError(fd)
-static int sendAndProcessNetlinkResponse(const void* req, int len) {
- base::unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
- if (fd == -1) {
- const int err = errno;
- ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)");
- return -err;
- }
-
- static constexpr int on = 1;
- int rv = setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on));
- if (rv) ALOGE("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, %d)", on);
-
- // this is needed to get sane strace netlink parsing, it allocates the pid
- rv = bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
- if (rv) {
- const int err = errno;
- ALOGE("bind(fd, {AF_NETLINK, 0, 0})");
- return -err;
- }
-
- // we do not want to receive messages from anyone besides the kernel
- rv = connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
- if (rv) {
- const int err = errno;
- ALOGE("connect(fd, {AF_NETLINK, 0, 0})");
- return -err;
- }
-
- rv = send(fd, req, len, 0);
- if (rv == -1) return -errno;
- if (rv != len) return -EMSGSIZE;
-
- struct {
- nlmsghdr h;
- nlmsgerr e;
- char buf[256];
- } resp = {};
-
- rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
-
- if (rv == -1) {
- const int err = errno;
- ALOGE("recv() failed");
- return -err;
- }
-
- if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
- ALOGE("recv() returned short packet: %d", rv);
- return -EMSGSIZE;
- }
-
- if (resp.h.nlmsg_len != (unsigned)rv) {
- ALOGE("recv() returned invalid header length: %d != %d", resp.h.nlmsg_len, rv);
- return -EBADMSG;
- }
-
- if (resp.h.nlmsg_type != NLMSG_ERROR) {
- ALOGE("recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
- return -EBADMSG;
- }
-
- return resp.e.error; // returns 0 on success
-}
-
-// ADD: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_EXCL|NLM_F_CREATE
-// REPLACE: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_CREATE|NLM_F_REPLACE
-// DEL: nlMsgType=RTM_DELQDISC nlMsgFlags=0
-int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags) {
- // This is the name of the qdisc we are attaching.
- // Some hoop jumping to make this compile time constant with known size,
- // so that the structure declaration is well defined at compile time.
-#define CLSACT "clsact"
- // sizeof() includes the terminating NULL
- static constexpr size_t ASCIIZ_LEN_CLSACT = sizeof(CLSACT);
-
- const struct {
- nlmsghdr n;
- tcmsg t;
- struct {
- nlattr attr;
- char str[NLMSG_ALIGN(ASCIIZ_LEN_CLSACT)];
- } kind;
- } req = {
- .n =
- {
- .nlmsg_len = sizeof(req),
- .nlmsg_type = nlMsgType,
- .nlmsg_flags = static_cast<__u16>(NETLINK_REQUEST_FLAGS | nlMsgFlags),
- },
- .t =
- {
- .tcm_family = AF_UNSPEC,
- .tcm_ifindex = ifIndex,
- .tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0),
- .tcm_parent = TC_H_CLSACT,
- },
- .kind =
- {
- .attr =
- {
- .nla_len = NLA_HDRLEN + ASCIIZ_LEN_CLSACT,
- .nla_type = TCA_KIND,
- },
- .str = CLSACT,
- },
- };
-#undef CLSACT
-
- return sendAndProcessNetlinkResponse(&req, sizeof(req));
-}
-
-// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
-// direct-action
-int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet) {
- // This is the name of the filter we're attaching (ie. this is the 'bpf'
- // packet classifier enabled by kernel config option CONFIG_NET_CLS_BPF.
- //
- // We go through some hoops in order to make this compile time constants
- // so that we can define the struct further down the function with the
- // field for this sized correctly already during the build.
-#define BPF "bpf"
- // sizeof() includes the terminating NULL
- static constexpr size_t ASCIIZ_LEN_BPF = sizeof(BPF);
-
- // This is to replicate program name suffix used by 'tc' Linux cli
- // when it attaches programs.
-#define FSOBJ_SUFFIX ":[*fsobj]"
-
- // This macro expands (from header files) to:
- // prog_clatd_schedcls_ingress6_clat_rawip:[*fsobj]
- // and is the name of the pinned ingress ebpf program for ARPHRD_RAWIP interfaces.
- // (also compatible with anything that has 0 size L2 header)
- static constexpr char name_clat_rx_rawip[] = CLAT_INGRESS6_PROG_RAWIP_NAME FSOBJ_SUFFIX;
-
- // This macro expands (from header files) to:
- // prog_clatd_schedcls_ingress6_clat_ether:[*fsobj]
- // and is the name of the pinned ingress ebpf program for ARPHRD_ETHER interfaces.
- // (also compatible with anything that has standard ethernet header)
- static constexpr char name_clat_rx_ether[] = CLAT_INGRESS6_PROG_ETHER_NAME FSOBJ_SUFFIX;
-
- // This macro expands (from header files) to:
- // prog_clatd_schedcls_egress4_clat_rawip:[*fsobj]
- // and is the name of the pinned egress ebpf program for ARPHRD_RAWIP interfaces.
- // (also compatible with anything that has 0 size L2 header)
- static constexpr char name_clat_tx_rawip[] = CLAT_EGRESS4_PROG_RAWIP_NAME FSOBJ_SUFFIX;
-
- // This macro expands (from header files) to:
- // prog_clatd_schedcls_egress4_clat_ether:[*fsobj]
- // and is the name of the pinned egress ebpf program for ARPHRD_ETHER interfaces.
- // (also compatible with anything that has standard ethernet header)
- static constexpr char name_clat_tx_ether[] = CLAT_EGRESS4_PROG_ETHER_NAME FSOBJ_SUFFIX;
-
-#undef FSOBJ_SUFFIX
-
- // The actual name we'll use is determined at run time via 'ethernet' and 'ingress'
- // booleans. We need to compile time allocate enough space in the struct
- // hence this macro magic to make sure we have enough space for either
- // possibility. In practice some of these are actually the same size.
- static constexpr size_t ASCIIZ_MAXLEN_NAME = max({
- sizeof(name_clat_rx_rawip),
- sizeof(name_clat_rx_ether),
- sizeof(name_clat_tx_rawip),
- sizeof(name_clat_tx_ether),
- });
-
- // These are not compile time constants: 'name' is used in strncpy below
- const char* const name_clat_rx = ethernet ? name_clat_rx_ether : name_clat_rx_rawip;
- const char* const name_clat_tx = ethernet ? name_clat_tx_ether : name_clat_tx_rawip;
- const char* const name = ingress ? name_clat_rx : name_clat_tx;
-
- struct {
- nlmsghdr n;
- tcmsg t;
- struct {
- nlattr attr;
- char str[NLMSG_ALIGN(ASCIIZ_LEN_BPF)];
- } kind;
- struct {
- nlattr attr;
- struct {
- nlattr attr;
- __u32 u32;
- } fd;
- struct {
- nlattr attr;
- char str[NLMSG_ALIGN(ASCIIZ_MAXLEN_NAME)];
- } name;
- struct {
- nlattr attr;
- __u32 u32;
- } flags;
- } options;
- } req = {
- .n =
- {
- .nlmsg_len = sizeof(req),
- .nlmsg_type = RTM_NEWTFILTER,
- .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
- },
- .t =
- {
- .tcm_family = AF_UNSPEC,
- .tcm_ifindex = ifIndex,
- .tcm_handle = TC_H_UNSPEC,
- .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
- ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
- .tcm_info = static_cast<__u32>((PRIO_CLAT << 16) | htons(proto)),
- },
- .kind =
- {
- .attr =
- {
- .nla_len = sizeof(req.kind),
- .nla_type = TCA_KIND,
- },
- .str = BPF,
- },
- .options =
- {
- .attr =
- {
- .nla_len = sizeof(req.options),
- .nla_type = NLA_F_NESTED | TCA_OPTIONS,
- },
- .fd =
- {
- .attr =
- {
- .nla_len = sizeof(req.options.fd),
- .nla_type = TCA_BPF_FD,
- },
- .u32 = static_cast<__u32>(bpfFd),
- },
- .name =
- {
- .attr =
- {
- .nla_len = sizeof(req.options.name),
- .nla_type = TCA_BPF_NAME,
- },
- // Visible via 'tc filter show', but
- // is overwritten by strncpy below
- .str = "placeholder",
- },
- .flags =
- {
- .attr =
- {
- .nla_len = sizeof(req.options.flags),
- .nla_type = TCA_BPF_FLAGS,
- },
- .u32 = TCA_BPF_FLAG_ACT_DIRECT,
- },
- },
- };
-#undef BPF
-
- strncpy(req.options.name.str, name, sizeof(req.options.name.str));
-
- return sendAndProcessNetlinkResponse(&req, sizeof(req));
-}
-
-// tc filter del dev .. in/egress prio 4 protocol ..
-int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) {
- const struct {
- nlmsghdr n;
- tcmsg t;
- } req = {
- .n =
- {
- .nlmsg_len = sizeof(req),
- .nlmsg_type = RTM_DELTFILTER,
- .nlmsg_flags = NETLINK_REQUEST_FLAGS,
- },
- .t =
- {
- .tcm_family = AF_UNSPEC,
- .tcm_ifindex = ifIndex,
- .tcm_handle = TC_H_UNSPEC,
- .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
- ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
- .tcm_info = (static_cast<uint32_t>(prio) << 16) |
- static_cast<uint32_t>(htons(proto)),
- },
- };
-
- return sendAndProcessNetlinkResponse(&req, sizeof(req));
-}
-
-} // namespace net
-} // namespace android
diff --git a/service/native/libs/libclat/TcUtilsTest.cpp b/service/native/libs/libclat/TcUtilsTest.cpp
deleted file mode 100644
index 08f3042..0000000
--- a/service/native/libs/libclat/TcUtilsTest.cpp
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright 2019 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.
- *
- * TcUtilsTest.cpp - unit tests for TcUtils.cpp
- */
-
-#include <gtest/gtest.h>
-
-#include "libclat/TcUtils.h"
-
-#include <linux/if_arp.h>
-#include <stdlib.h>
-#include <sys/wait.h>
-
-#include "bpf/BpfUtils.h"
-#include "bpf_shared.h"
-
-namespace android {
-namespace net {
-
-class TcUtilsTest : public ::testing::Test {
- public:
- void SetUp() {}
-};
-
-TEST_F(TcUtilsTest, HardwareAddressTypeOfNonExistingIf) {
- ASSERT_EQ(-ENODEV, hardwareAddressType("not_existing_if"));
-}
-
-TEST_F(TcUtilsTest, HardwareAddressTypeOfLoopback) {
- ASSERT_EQ(ARPHRD_LOOPBACK, hardwareAddressType("lo"));
-}
-
-// If wireless 'wlan0' interface exists it should be Ethernet.
-TEST_F(TcUtilsTest, HardwareAddressTypeOfWireless) {
- int type = hardwareAddressType("wlan0");
- if (type == -ENODEV) return;
-
- ASSERT_EQ(ARPHRD_ETHER, type);
-}
-
-// If cellular 'rmnet_data0' interface exists it should
-// *probably* not be Ethernet and instead be RawIp.
-TEST_F(TcUtilsTest, HardwareAddressTypeOfCellular) {
- int type = hardwareAddressType("rmnet_data0");
- if (type == -ENODEV) return;
-
- ASSERT_NE(ARPHRD_ETHER, type);
-
- // ARPHRD_RAWIP is 530 on some pre-4.14 Qualcomm devices.
- if (type == 530) return;
-
- ASSERT_EQ(ARPHRD_RAWIP, type);
-}
-
-TEST_F(TcUtilsTest, IsEthernetOfNonExistingIf) {
- auto res = isEthernet("not_existing_if");
- ASSERT_FALSE(res.ok());
- ASSERT_EQ(ENODEV, res.error().code());
-}
-
-TEST_F(TcUtilsTest, IsEthernetOfLoopback) {
- auto res = isEthernet("lo");
- ASSERT_FALSE(res.ok());
- ASSERT_EQ(EAFNOSUPPORT, res.error().code());
-}
-
-// If wireless 'wlan0' interface exists it should be Ethernet.
-// See also HardwareAddressTypeOfWireless.
-TEST_F(TcUtilsTest, IsEthernetOfWireless) {
- auto res = isEthernet("wlan0");
- if (!res.ok() && res.error().code() == ENODEV) return;
-
- ASSERT_RESULT_OK(res);
- ASSERT_TRUE(res.value());
-}
-
-// If cellular 'rmnet_data0' interface exists it should
-// *probably* not be Ethernet and instead be RawIp.
-// See also HardwareAddressTypeOfCellular.
-TEST_F(TcUtilsTest, IsEthernetOfCellular) {
- auto res = isEthernet("rmnet_data0");
- if (!res.ok() && res.error().code() == ENODEV) return;
-
- ASSERT_RESULT_OK(res);
- ASSERT_FALSE(res.value());
-}
-
-TEST_F(TcUtilsTest, DeviceMTUOfNonExistingIf) {
- ASSERT_EQ(-ENODEV, deviceMTU("not_existing_if"));
-}
-
-TEST_F(TcUtilsTest, DeviceMTUofLoopback) {
- ASSERT_EQ(65536, deviceMTU("lo"));
-}
-
-TEST_F(TcUtilsTest, GetClatEgress4MapFd) {
- int fd = getClatEgress4MapFd();
- ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatEgress4RawIpProgFd) {
- int fd = getClatEgress4ProgFd(RAWIP);
- ASSERT_GE(fd, 3);
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatEgress4EtherProgFd) {
- int fd = getClatEgress4ProgFd(ETHER);
- ASSERT_GE(fd, 3);
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatIngress6MapFd) {
- int fd = getClatIngress6MapFd();
- ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatIngress6RawIpProgFd) {
- int fd = getClatIngress6ProgFd(RAWIP);
- ASSERT_GE(fd, 3);
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-TEST_F(TcUtilsTest, GetClatIngress6EtherProgFd) {
- int fd = getClatIngress6ProgFd(ETHER);
- ASSERT_GE(fd, 3);
- EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
- close(fd);
-}
-
-// See Linux kernel source in include/net/flow.h
-#define LOOPBACK_IFINDEX 1
-
-TEST_F(TcUtilsTest, AttachReplaceDetachClsactLo) {
- // This attaches and detaches a configuration-less and thus no-op clsact
- // qdisc to loopback interface (and it takes fractions of a second)
- EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(0, tcQdiscReplaceDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(-EINVAL, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
-}
-
-static void checkAttachDetachBpfFilterClsactLo(const bool ingress, const bool ethernet) {
- // Older kernels return EINVAL instead of ENOENT due to lacking proper error propagation...
- const int errNOENT = android::bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
-
- int clatBpfFd = ingress ? getClatIngress6ProgFd(ethernet) : getClatEgress4ProgFd(ethernet);
- ASSERT_GE(clatBpfFd, 3);
-
- // This attaches and detaches a clsact plus ebpf program to loopback
- // interface, but it should not affect traffic by virtue of us not
- // actually populating the ebpf control map.
- // Furthermore: it only takes fractions of a second.
- EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
- EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
- if (ingress) {
- EXPECT_EQ(0, tcFilterAddDevIngressClatIpv6(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
- EXPECT_EQ(0, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- } else {
- EXPECT_EQ(0, tcFilterAddDevEgressClatIpv4(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
- EXPECT_EQ(0, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
- }
- EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
- EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
- EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
- EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
-
- close(clatBpfFd);
-}
-
-TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactEgressLo) {
- checkAttachDetachBpfFilterClsactLo(EGRESS, RAWIP);
-}
-
-TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactEgressLo) {
- checkAttachDetachBpfFilterClsactLo(EGRESS, ETHER);
-}
-
-TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactIngressLo) {
- checkAttachDetachBpfFilterClsactLo(INGRESS, RAWIP);
-}
-
-TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactIngressLo) {
- checkAttachDetachBpfFilterClsactLo(INGRESS, ETHER);
-}
-
-} // namespace net
-} // namespace android
diff --git a/service/native/libs/libclat/bpfhelper.cpp b/service/native/libs/libclat/bpfhelper.cpp
deleted file mode 100644
index 00785ad..0000000
--- a/service/native/libs/libclat/bpfhelper.cpp
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright 2021 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.
- *
- * main.c - main function
- */
-#define LOG_TAG "bpfhelper"
-
-#include "libclat/bpfhelper.h"
-
-#include <android-base/unique_fd.h>
-#include <log/log.h>
-
-#include "bpf/BpfMap.h"
-#include "libclat/TcUtils.h"
-
-#define DEVICEPREFIX "v4-"
-
-using android::base::unique_fd;
-using android::bpf::BpfMap;
-
-BpfMap<ClatEgress4Key, ClatEgress4Value> mClatEgress4Map;
-BpfMap<ClatIngress6Key, ClatIngress6Value> mClatIngress6Map;
-
-namespace android {
-namespace net {
-namespace clat {
-
-// TODO: have a clearMap function to remove all stubs while system server crash.
-// For long term, move bpf access into java and map initialization should live
-// ClatCoordinator constructor.
-int initMaps(void) {
- int rv = getClatEgress4MapFd();
- if (rv < 0) {
- ALOGE("getClatEgress4MapFd() failure: %s", strerror(-rv));
- return -rv;
- }
- mClatEgress4Map.reset(rv);
-
- rv = getClatIngress6MapFd();
- if (rv < 0) {
- ALOGE("getClatIngress6MapFd() failure: %s", strerror(-rv));
- mClatEgress4Map.reset(-1);
- return -rv;
- }
- mClatIngress6Map.reset(rv);
-
- return 0;
-}
-
-void maybeStartBpf(const ClatdTracker& tracker) {
- auto isEthernet = android::net::isEthernet(tracker.iface);
- if (!isEthernet.ok()) {
- ALOGE("isEthernet(%s[%d]) failure: %s", tracker.iface, tracker.ifIndex,
- isEthernet.error().message().c_str());
- return;
- }
-
- // This program will be attached to the v4-* interface which is a TUN and thus always rawip.
- int rv = getClatEgress4ProgFd(RAWIP);
- if (rv < 0) {
- ALOGE("getClatEgress4ProgFd(RAWIP) failure: %s", strerror(-rv));
- return;
- }
- unique_fd txRawIpProgFd(rv);
-
- rv = getClatIngress6ProgFd(isEthernet.value());
- if (rv < 0) {
- ALOGE("getClatIngress6ProgFd(%d) failure: %s", isEthernet.value(), strerror(-rv));
- return;
- }
- unique_fd rxProgFd(rv);
-
- ClatEgress4Key txKey = {
- .iif = tracker.v4ifIndex,
- .local4 = tracker.v4,
- };
- ClatEgress4Value txValue = {
- .oif = tracker.ifIndex,
- .local6 = tracker.v6,
- .pfx96 = tracker.pfx96,
- .oifIsEthernet = isEthernet.value(),
- };
-
- auto ret = mClatEgress4Map.writeValue(txKey, txValue, BPF_ANY);
- if (!ret.ok()) {
- ALOGE("mClatEgress4Map.writeValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- ClatIngress6Key rxKey = {
- .iif = tracker.ifIndex,
- .pfx96 = tracker.pfx96,
- .local6 = tracker.v6,
- };
- ClatIngress6Value rxValue = {
- // TODO: move all the clat code to eBPF and remove the tun interface entirely.
- .oif = tracker.v4ifIndex,
- .local4 = tracker.v4,
- };
-
- ret = mClatIngress6Map.writeValue(rxKey, rxValue, BPF_ANY);
- if (!ret.ok()) {
- ALOGE("mClatIngress6Map.writeValue failure: %s", strerror(ret.error().code()));
- ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok())
- ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- // We do tc setup *after* populating the maps, so scanning through them
- // can always be used to tell us what needs cleanup.
-
- // Usually the clsact will be added in RouteController::addInterfaceToPhysicalNetwork.
- // But clat is started before the v4- interface is added to the network. The clat startup have
- // to add clsact of v4- tun interface first for adding bpf filter in maybeStartBpf.
- // TODO: move "qdisc add clsact" of v4- tun interface out from ClatdController.
- rv = tcQdiscAddDevClsact(tracker.v4ifIndex);
- if (rv) {
- ALOGE("tcQdiscAddDevClsact(%d[%s]) failure: %s", tracker.v4ifIndex, tracker.v4iface,
- strerror(-rv));
- ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok())
- ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
- ret = mClatIngress6Map.deleteValue(rxKey);
- if (!ret.ok())
- ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- rv = tcFilterAddDevEgressClatIpv4(tracker.v4ifIndex, txRawIpProgFd, RAWIP);
- if (rv) {
- ALOGE("tcFilterAddDevEgressClatIpv4(%d[%s], RAWIP) failure: %s", tracker.v4ifIndex,
- tracker.v4iface, strerror(-rv));
-
- // The v4- interface clsact is not deleted for unwinding error because once it is created
- // with interface addition, the lifetime is till interface deletion. Moreover, the clsact
- // has no clat filter now. It should not break anything.
-
- ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok())
- ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
- ret = mClatIngress6Map.deleteValue(rxKey);
- if (!ret.ok())
- ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- rv = tcFilterAddDevIngressClatIpv6(tracker.ifIndex, rxProgFd, isEthernet.value());
- if (rv) {
- ALOGE("tcFilterAddDevIngressClatIpv6(%d[%s], %d) failure: %s", tracker.ifIndex,
- tracker.iface, isEthernet.value(), strerror(-rv));
- rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
- if (rv) {
- ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
- tracker.v4iface, strerror(-rv));
- }
-
- // The v4- interface clsact is not deleted. See the reason in the error unwinding code of
- // the egress filter attaching of v4- tun interface.
-
- ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok())
- ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
- ret = mClatIngress6Map.deleteValue(rxKey);
- if (!ret.ok())
- ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
- return;
- }
-
- // success
-}
-
-void maybeStopBpf(const ClatdTracker& tracker) {
- int rv = tcFilterDelDevIngressClatIpv6(tracker.ifIndex);
- if (rv < 0) {
- ALOGE("tcFilterDelDevIngressClatIpv6(%d[%s]) failure: %s", tracker.ifIndex, tracker.iface,
- strerror(-rv));
- }
-
- rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
- if (rv < 0) {
- ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
- tracker.v4iface, strerror(-rv));
- }
-
- // We cleanup the maps last, so scanning through them can be used to
- // determine what still needs cleanup.
-
- ClatEgress4Key txKey = {
- .iif = tracker.v4ifIndex,
- .local4 = tracker.v4,
- };
-
- auto ret = mClatEgress4Map.deleteValue(txKey);
- if (!ret.ok()) ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
-
- ClatIngress6Key rxKey = {
- .iif = tracker.ifIndex,
- .pfx96 = tracker.pfx96,
- .local6 = tracker.v6,
- };
-
- ret = mClatIngress6Map.deleteValue(rxKey);
- if (!ret.ok()) ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
-}
-
-} // namespace clat
-} // namespace net
-} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/TcUtils.h b/service/native/libs/libclat/include/libclat/TcUtils.h
deleted file mode 100644
index 212838e..0000000
--- a/service/native/libs/libclat/include/libclat/TcUtils.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#pragma once
-
-#include <android-base/result.h>
-#include <errno.h>
-#include <linux/if_ether.h>
-#include <linux/if_link.h>
-#include <linux/rtnetlink.h>
-
-#include <string>
-
-#include "bpf/BpfUtils.h"
-#include "bpf_shared.h"
-
-namespace android {
-namespace net {
-
-// For better code clarity - do not change values - used for booleans like
-// with_ethernet_header or isEthernet.
-constexpr bool RAWIP = false;
-constexpr bool ETHER = true;
-
-// For better code clarity when used for 'bool ingress' parameter.
-constexpr bool EGRESS = false;
-constexpr bool INGRESS = true;
-
-// The priority of clat hook - must be after tethering.
-constexpr uint16_t PRIO_CLAT = 4;
-
-// this returns an ARPHRD_* constant or a -errno
-int hardwareAddressType(const std::string& interface);
-
-// return MTU or -errno
-int deviceMTU(const std::string& interface);
-
-base::Result<bool> isEthernet(const std::string& interface);
-
-inline int getClatEgress4MapFd(void) {
- const int fd = bpf::mapRetrieveRW(CLAT_EGRESS4_MAP_PATH);
- return (fd == -1) ? -errno : fd;
-}
-
-inline int getClatEgress4ProgFd(bool with_ethernet_header) {
- const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_EGRESS4_PROG_ETHER_PATH
- : CLAT_EGRESS4_PROG_RAWIP_PATH);
- return (fd == -1) ? -errno : fd;
-}
-
-inline int getClatIngress6MapFd(void) {
- const int fd = bpf::mapRetrieveRW(CLAT_INGRESS6_MAP_PATH);
- return (fd == -1) ? -errno : fd;
-}
-
-inline int getClatIngress6ProgFd(bool with_ethernet_header) {
- const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_INGRESS6_PROG_ETHER_PATH
- : CLAT_INGRESS6_PROG_RAWIP_PATH);
- return (fd == -1) ? -errno : fd;
-}
-
-int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags);
-
-inline int tcQdiscAddDevClsact(int ifIndex) {
- return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE);
-}
-
-inline int tcQdiscReplaceDevClsact(int ifIndex) {
- return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE);
-}
-
-inline int tcQdiscDelDevClsact(int ifIndex) {
- return doTcQdiscClsact(ifIndex, RTM_DELQDISC, 0);
-}
-
-// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
-// direct-action
-int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet);
-
-// tc filter add dev .. ingress prio 4 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
-inline int tcFilterAddDevIngressClatIpv6(int ifIndex, int bpfFd, bool ethernet) {
- return tcFilterAddDevBpf(ifIndex, INGRESS, ETH_P_IPV6, bpfFd, ethernet);
-}
-
-// tc filter add dev .. egress prio 4 protocol ip bpf object-pinned /sys/fs/bpf/... direct-action
-inline int tcFilterAddDevEgressClatIpv4(int ifIndex, int bpfFd, bool ethernet) {
- return tcFilterAddDevBpf(ifIndex, EGRESS, ETH_P_IP, bpfFd, ethernet);
-}
-
-// tc filter del dev .. in/egress prio .. protocol ..
-int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
-
-// tc filter del dev .. ingress prio 4 protocol ipv6
-inline int tcFilterDelDevIngressClatIpv6(int ifIndex) {
- return tcFilterDelDev(ifIndex, INGRESS, PRIO_CLAT, ETH_P_IPV6);
-}
-
-// tc filter del dev .. egress prio 4 protocol ip
-inline int tcFilterDelDevEgressClatIpv4(int ifIndex) {
- return tcFilterDelDev(ifIndex, EGRESS, PRIO_CLAT, ETH_P_IP);
-}
-
-} // namespace net
-} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/bpfhelper.h b/service/native/libs/libclat/include/libclat/bpfhelper.h
deleted file mode 100644
index c0328c0..0000000
--- a/service/native/libs/libclat/include/libclat/bpfhelper.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2021 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.
-
-#pragma once
-
-#include <arpa/inet.h>
-#include <linux/if.h>
-
-namespace android {
-namespace net {
-namespace clat {
-
-struct ClatdTracker {
- unsigned ifIndex;
- char iface[IFNAMSIZ];
- unsigned v4ifIndex;
- char v4iface[IFNAMSIZ];
- in_addr v4;
- in6_addr v6;
- in6_addr pfx96;
-};
-
-int initMaps(void);
-void maybeStartBpf(const ClatdTracker& tracker);
-void maybeStopBpf(const ClatdTracker& tracker);
-
-} // namespace clat
-} // namespace net
-} // namespace android
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 0528f29..d79bdb8 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -2864,12 +2864,16 @@
}
private void enforceNetworkFactoryPermission() {
+ // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
+ if (getCallingUid() == Process.BLUETOOTH_UID) return;
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_FACTORY,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private void enforceNetworkFactoryOrSettingsPermission() {
+ // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
+ if (getCallingUid() == Process.BLUETOOTH_UID) return;
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_FACTORY,
@@ -2877,6 +2881,8 @@
}
private void enforceNetworkFactoryOrTestNetworksPermission() {
+ // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
+ if (getCallingUid() == Process.BLUETOOTH_UID) return;
enforceAnyPermissionOf(
android.Manifest.permission.MANAGE_TEST_NETWORKS,
android.Manifest.permission.NETWORK_FACTORY,
@@ -2889,7 +2895,8 @@
|| PERMISSION_GRANTED == mContext.checkPermission(
android.Manifest.permission.NETWORK_SETTINGS, pid, uid)
|| PERMISSION_GRANTED == mContext.checkPermission(
- NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid);
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid)
+ || uid == Process.BLUETOOTH_UID;
}
private boolean checkSettingsPermission() {
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index ce955fd..b06c8aa 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -29,7 +29,6 @@
import android.net.NetworkCapabilities;
import android.net.NetworkSpecifier;
import android.net.TelephonyNetworkSpecifier;
-import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -94,11 +93,7 @@
@NonNull final TelephonyManager t) {
mContext = c;
mTelephonyManager = t;
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
- mTelephonyManagerShim = new TelephonyManagerShimImpl(mTelephonyManager);
- } else {
- mTelephonyManagerShim = null;
- }
+ mTelephonyManagerShim = TelephonyManagerShimImpl.newInstance(mTelephonyManager);
mThread = new HandlerThread(TAG);
mThread.start();
mHandler = new Handler(mThread.getLooper()) {};
@@ -192,36 +187,30 @@
private void addCarrierPrivilegesListener(int logicalSlotIndex, Executor executor,
CarrierPrivilegesListenerShim listener) {
- if (mTelephonyManagerShim == null) {
- return;
- }
try {
mTelephonyManagerShim.addCarrierPrivilegesListener(
logicalSlotIndex, executor, listener);
} catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ // Should not happen since CarrierPrivilegeAuthenticator is only used on T+
Log.e(TAG, "addCarrierPrivilegesListener API is not available");
}
}
private void removeCarrierPrivilegesListener(CarrierPrivilegesListenerShim listener) {
- if (mTelephonyManagerShim == null) {
- return;
- }
try {
mTelephonyManagerShim.removeCarrierPrivilegesListener(listener);
} catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ // Should not happen since CarrierPrivilegeAuthenticator is only used on T+
Log.e(TAG, "removeCarrierPrivilegesListener API is not available");
}
}
private String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
- if (mTelephonyManagerShim == null) {
- return null;
- }
try {
return mTelephonyManagerShim.getCarrierServicePackageNameForLogicalSlot(
logicalSlotIndex);
} catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ // Should not happen since CarrierPrivilegeAuthenticator is only used on T+
Log.e(TAG, "getCarrierServicePackageNameForLogicalSlot API is not available");
}
return null;
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 2e26ae4..8aa5990 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -18,6 +18,8 @@
import static android.net.INetd.IF_STATE_UP;
import static android.net.INetd.PERMISSION_SYSTEM;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
@@ -30,10 +32,19 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.TcUtils;
+import com.android.net.module.util.bpf.ClatEgress4Key;
+import com.android.net.module.util.bpf.ClatEgress4Value;
+import com.android.net.module.util.bpf.ClatIngress6Key;
+import com.android.net.module.util.bpf.ClatIngress6Value;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -71,11 +82,44 @@
private static final int INVALID_IFINDEX = 0;
+ // For better code clarity when used for 'bool ingress' parameter.
+ @VisibleForTesting
+ static final boolean EGRESS = false;
+ @VisibleForTesting
+ static final boolean INGRESS = true;
+
+ // For better code clarity when used for 'bool ether' parameter.
+ static final boolean RAWIP = false;
+ static final boolean ETHER = true;
+
+ // The priority of clat hook - must be after tethering.
+ @VisibleForTesting
+ static final int PRIO_CLAT = 4;
+
+ private static final String CLAT_EGRESS4_MAP_PATH = makeMapPath("egress4");
+ private static final String CLAT_INGRESS6_MAP_PATH = makeMapPath("ingress6");
+
+ private static String makeMapPath(String which) {
+ return "/sys/fs/bpf/map_clatd_clat_" + which + "_map";
+ }
+
+ private static String makeProgPath(boolean ingress, boolean ether) {
+ String path = "/sys/fs/bpf/prog_clatd_schedcls_"
+ + (ingress ? "ingress6" : "egress4")
+ + "_clat_"
+ + (ether ? "ether" : "rawip");
+ return path;
+ }
+
@NonNull
private final INetd mNetd;
@NonNull
private final Dependencies mDeps;
@Nullable
+ private final IBpfMap<ClatIngress6Key, ClatIngress6Value> mIngressMap;
+ @Nullable
+ private final IBpfMap<ClatEgress4Key, ClatEgress4Value> mEgressMap;
+ @Nullable
private ClatdTracker mClatdTracker = null;
@VisibleForTesting
@@ -195,6 +239,62 @@
public void untagSocket(long cookie) throws IOException {
native_untagSocket(cookie);
}
+
+ /** Get ingress6 BPF map. */
+ @Nullable
+ public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
+ // Pre-T devices don't use ClatCoordinator to access clat map. Since Nat464Xlat
+ // initializes a ClatCoordinator object to avoid redundant null pointer check
+ // while using, ignore the BPF map initialization on pre-T devices.
+ // TODO: probably don't initialize ClatCoordinator object on pre-T devices.
+ if (!SdkLevel.isAtLeastT()) return null;
+ try {
+ return new BpfMap<>(CLAT_INGRESS6_MAP_PATH,
+ BpfMap.BPF_F_RDWR, ClatIngress6Key.class, ClatIngress6Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create ingress6 map: " + e);
+ return null;
+ }
+ }
+
+ /** Get egress4 BPF map. */
+ @Nullable
+ public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
+ // Pre-T devices don't use ClatCoordinator to access clat map. Since Nat464Xlat
+ // initializes a ClatCoordinator object to avoid redundant null pointer check
+ // while using, ignore the BPF map initialization on pre-T devices.
+ // TODO: probably don't initialize ClatCoordinator object on pre-T devices.
+ if (!SdkLevel.isAtLeastT()) return null;
+ try {
+ return new BpfMap<>(CLAT_EGRESS4_MAP_PATH,
+ BpfMap.BPF_F_RDWR, ClatEgress4Key.class, ClatEgress4Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create egress4 map: " + e);
+ return null;
+ }
+ }
+
+ /** Checks if the network interface uses an ethernet L2 header. */
+ public boolean isEthernet(String iface) throws IOException {
+ return TcUtils.isEthernet(iface);
+ }
+
+ /** Add a clsact qdisc. */
+ public void tcQdiscAddDevClsact(int ifIndex) throws IOException {
+ TcUtils.tcQdiscAddDevClsact(ifIndex);
+ }
+
+ /** Attach a tc bpf filter. */
+ public void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio, short proto,
+ String bpfProgPath) throws IOException {
+ TcUtils.tcFilterAddDevBpf(ifIndex, ingress, prio, proto, bpfProgPath);
+ }
+
+ /** Delete a tc filter. */
+ public void tcFilterDelDev(int ifIndex, boolean ingress, short prio, short proto)
+ throws IOException {
+ TcUtils.tcFilterDelDev(ifIndex, ingress, prio, proto);
+ }
}
@VisibleForTesting
@@ -268,6 +368,129 @@
public ClatCoordinator(@NonNull Dependencies deps) {
mDeps = deps;
mNetd = mDeps.getNetd();
+ mIngressMap = mDeps.getBpfIngress6Map();
+ mEgressMap = mDeps.getBpfEgress4Map();
+ }
+
+ private void maybeStartBpf(final ClatdTracker tracker) {
+ if (mIngressMap == null || mEgressMap == null) return;
+
+ final boolean isEthernet;
+ try {
+ isEthernet = mDeps.isEthernet(tracker.iface);
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to call isEthernet for interface " + tracker.iface);
+ return;
+ }
+
+ final ClatEgress4Key txKey = new ClatEgress4Key(tracker.v4ifIndex, tracker.v4);
+ final ClatEgress4Value txValue = new ClatEgress4Value(tracker.ifIndex, tracker.v6,
+ tracker.pfx96, (short) (isEthernet ? 1 /* ETHER */ : 0 /* RAWIP */));
+ try {
+ mEgressMap.insertEntry(txKey, txValue);
+ } catch (ErrnoException | IllegalStateException e) {
+ Log.e(TAG, "Could not insert entry (" + txKey + ", " + txValue + ") on egress map: "
+ + e);
+ return;
+ }
+
+ final ClatIngress6Key rxKey = new ClatIngress6Key(tracker.ifIndex, tracker.pfx96,
+ tracker.v6);
+ final ClatIngress6Value rxValue = new ClatIngress6Value(tracker.v4ifIndex,
+ tracker.v4);
+ try {
+ mIngressMap.insertEntry(rxKey, rxValue);
+ } catch (ErrnoException | IllegalStateException e) {
+ Log.e(TAG, "Could not insert entry (" + rxKey + ", " + rxValue + ") ingress map: "
+ + e);
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e2) {
+ Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e2);
+ }
+ return;
+ }
+
+ // Usually the clsact will be added in netd RouteController::addInterfaceToPhysicalNetwork.
+ // But clat is started before the v4- interface is added to the network. The clat startup
+ // have to add clsact of v4- tun interface first for adding bpf filter in maybeStartBpf.
+ try {
+ // tc qdisc add dev .. clsact
+ mDeps.tcQdiscAddDevClsact(tracker.v4ifIndex);
+ } catch (IOException e) {
+ Log.e(TAG, "tc qdisc add dev (" + tracker.v4ifIndex + "[" + tracker.v4iface
+ + "]) failure: " + e);
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e2) {
+ Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e2);
+ }
+ try {
+ mIngressMap.deleteEntry(rxKey);
+ } catch (ErrnoException | IllegalStateException e3) {
+ Log.e(TAG, "Could not delete entry (" + rxKey + ") from ingress map: " + e3);
+ }
+ return;
+ }
+
+ // This program will be attached to the v4-* interface which is a TUN and thus always rawip.
+ try {
+ // tc filter add dev .. egress prio 4 protocol ip bpf object-pinned /sys/fs/bpf/...
+ // direct-action
+ mDeps.tcFilterAddDevBpf(tracker.v4ifIndex, EGRESS, (short) PRIO_CLAT, (short) ETH_P_IP,
+ makeProgPath(EGRESS, RAWIP));
+ } catch (IOException e) {
+ Log.e(TAG, "tc filter add dev (" + tracker.v4ifIndex + "[" + tracker.v4iface
+ + "]) egress prio PRIO_CLAT protocol ip failure: " + e);
+
+ // The v4- interface clsact is not deleted for unwinding error because once it is
+ // created with interface addition, the lifetime is till interface deletion. Moreover,
+ // the clsact has no clat filter now. It should not break anything.
+
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e2) {
+ Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e2);
+ }
+ try {
+ mIngressMap.deleteEntry(rxKey);
+ } catch (ErrnoException | IllegalStateException e3) {
+ Log.e(TAG, "Could not delete entry (" + rxKey + ") from ingress map: " + e3);
+ }
+ return;
+ }
+
+ try {
+ // tc filter add dev .. ingress prio 4 protocol ipv6 bpf object-pinned /sys/fs/bpf/...
+ // direct-action
+ mDeps.tcFilterAddDevBpf(tracker.ifIndex, INGRESS, (short) PRIO_CLAT,
+ (short) ETH_P_IPV6, makeProgPath(INGRESS, isEthernet));
+ } catch (IOException e) {
+ Log.e(TAG, "tc filter add dev (" + tracker.ifIndex + "[" + tracker.iface
+ + "]) ingress prio PRIO_CLAT protocol ipv6 failure: " + e);
+
+ // The v4- interface clsact is not deleted. See the reason in the error unwinding code
+ // of the egress filter attaching of v4- tun interface.
+
+ try {
+ mDeps.tcFilterDelDev(tracker.v4ifIndex, EGRESS, (short) PRIO_CLAT,
+ (short) ETH_P_IP);
+ } catch (IOException e2) {
+ Log.e(TAG, "tc filter del dev (" + tracker.v4ifIndex + "[" + tracker.v4iface
+ + "]) egress prio PRIO_CLAT protocol ip failure: " + e2);
+ }
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e3) {
+ Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e3);
+ }
+ try {
+ mIngressMap.deleteEntry(rxKey);
+ } catch (ErrnoException | IllegalStateException e4) {
+ Log.e(TAG, "Could not delete entry (" + rxKey + ") from ingress map: " + e4);
+ }
+ return;
+ }
}
/**
@@ -454,9 +677,48 @@
mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
pid, cookie);
+ // [7] Start BPF
+ maybeStartBpf(mClatdTracker);
+
return v6Str;
}
+ private void maybeStopBpf(final ClatdTracker tracker) {
+ if (mIngressMap == null || mEgressMap == null) return;
+
+ try {
+ mDeps.tcFilterDelDev(tracker.ifIndex, INGRESS, (short) PRIO_CLAT, (short) ETH_P_IPV6);
+ } catch (IOException e) {
+ Log.e(TAG, "tc filter del dev (" + tracker.ifIndex + "[" + tracker.iface
+ + "]) ingress prio PRIO_CLAT protocol ipv6 failure: " + e);
+ }
+
+ try {
+ mDeps.tcFilterDelDev(tracker.v4ifIndex, EGRESS, (short) PRIO_CLAT, (short) ETH_P_IP);
+ } catch (IOException e) {
+ Log.e(TAG, "tc filter del dev (" + tracker.v4ifIndex + "[" + tracker.v4iface
+ + "]) egress prio PRIO_CLAT protocol ip failure: " + e);
+ }
+
+ // We cleanup the maps last, so scanning through them can be used to
+ // determine what still needs cleanup.
+
+ final ClatEgress4Key txKey = new ClatEgress4Key(tracker.v4ifIndex, tracker.v4);
+ try {
+ mEgressMap.deleteEntry(txKey);
+ } catch (ErrnoException | IllegalStateException e) {
+ Log.e(TAG, "Could not delete entry (" + txKey + "): " + e);
+ }
+
+ final ClatIngress6Key rxKey = new ClatIngress6Key(tracker.ifIndex, tracker.pfx96,
+ tracker.v6);
+ try {
+ mIngressMap.deleteEntry(rxKey);
+ } catch (ErrnoException | IllegalStateException e) {
+ Log.e(TAG, "Could not delete entry (" + rxKey + "): " + e);
+ }
+ }
+
/**
* Stop clatd
*/
@@ -466,6 +728,7 @@
}
Log.i(TAG, "Stopping clatd pid=" + mClatdTracker.pid + " on " + mClatdTracker.iface);
+ maybeStopBpf(mClatdTracker);
mDeps.stopClatd(mClatdTracker.iface, mClatdTracker.pfx96.getHostAddress(),
mClatdTracker.v4.getHostAddress(), mClatdTracker.v6.getHostAddress(),
mClatdTracker.pid);
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 2885ba7..62b3add 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -127,9 +127,17 @@
@GuardedBy("this")
private final Set<Integer> mUidsAllowedOnRestrictedNetworks = new ArraySet<>();
+ // Store PackageManager for each user.
+ // Keys are users, Values are PackageManagers which get from each user.
@GuardedBy("this")
private final Map<UserHandle, PackageManager> mUsersPackageManager = new ArrayMap<>();
+ // Store appIds traffic permissions for each user.
+ // Keys are users, Values are SparseArrays where each entry maps an appId to the permissions
+ // that appId has within that user.
+ @GuardedBy("this")
+ private final Map<UserHandle, SparseIntArray> mUsersTrafficPermissions = new ArrayMap<>();
+
private static final int SYSTEM_APPID = SYSTEM_UID;
private static final int MAX_PERMISSION_UPDATE_LOGS = 40;
@@ -292,14 +300,24 @@
sendUidsNetworkPermission(uids, true /* add */);
}
- private void updateAppIdsTrafficPermission(final SparseIntArray appIds,
- final SparseIntArray extraAppIds) {
- for (int i = 0; i < extraAppIds.size(); i++) {
- final int appId = extraAppIds.keyAt(i);
- final int permission = extraAppIds.valueAt(i);
- appIds.put(appId, appIds.get(appId) | permission);
+ /**
+ * Calculates permissions for appIds.
+ * Maps each appId to the union of all traffic permissions that the appId has in all users.
+ *
+ * @return The appIds traffic permissions.
+ */
+ private synchronized SparseIntArray makeAppIdsTrafficPermForAllUsers() {
+ final SparseIntArray appIds = new SparseIntArray();
+ // Check appIds permissions from each user.
+ for (UserHandle user : mUsersTrafficPermissions.keySet()) {
+ final SparseIntArray userAppIds = mUsersTrafficPermissions.get(user);
+ for (int i = 0; i < userAppIds.size(); i++) {
+ final int appId = userAppIds.keyAt(i);
+ final int permission = userAppIds.valueAt(i);
+ appIds.put(appId, appIds.get(appId) | permission);
+ }
}
- sendAppIdsTrafficPermission(appIds);
+ return appIds;
}
private SparseIntArray getSystemTrafficPerm() {
@@ -363,6 +381,10 @@
// mUidsAllowedOnRestrictedNetworks.
updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext));
+ // Read system traffic permissions when a user removed and put them to USER_ALL because they
+ // are not specific to any particular user.
+ mUsersTrafficPermissions.put(UserHandle.ALL, getSystemTrafficPerm());
+
final List<UserHandle> usrs = mUserManager.getUserHandles(true /* excludeDying */);
// Update netd permissions for all users.
for (UserHandle user : usrs) {
@@ -487,9 +509,16 @@
final SparseIntArray uids = makeUidsNetworkPerm(apps);
updateUidsNetworkPermission(uids);
- // App ids traffic permission
- final SparseIntArray appIds = makeAppIdsTrafficPerm(apps);
- updateAppIdsTrafficPermission(appIds, getSystemTrafficPerm());
+ // Add new user appIds permissions.
+ final SparseIntArray addedUserAppIds = makeAppIdsTrafficPerm(apps);
+ mUsersTrafficPermissions.put(user, addedUserAppIds);
+ // Generate appIds from all users and send result to netd.
+ final SparseIntArray appIds = makeAppIdsTrafficPermForAllUsers();
+ sendAppIdsTrafficPermission(appIds);
+
+ // Log user added
+ mPermissionUpdateLogs.log("New user(" + user.getIdentifier() + ") added: nPerm uids="
+ + uids + ", tPerm appIds=" + addedUserAppIds);
}
/**
@@ -502,6 +531,7 @@
public synchronized void onUserRemoved(@NonNull UserHandle user) {
mUsers.remove(user);
+ // Remove uids network permissions that belongs to the user.
final SparseIntArray removedUids = new SparseIntArray();
final SparseIntArray allUids = mUidToNetworkPerm.clone();
for (int i = 0; i < allUids.size(); i++) {
@@ -512,6 +542,27 @@
}
}
sendUidsNetworkPermission(removedUids, false /* add */);
+
+ // Remove appIds traffic permission that belongs to the user
+ final SparseIntArray removedUserAppIds = mUsersTrafficPermissions.remove(user);
+ // Generate appIds from left users.
+ final SparseIntArray appIds = makeAppIdsTrafficPermForAllUsers();
+ // Clear permission on those appIds belong to this user only, set the permission to
+ // PERMISSION_UNINSTALLED.
+ if (removedUserAppIds != null) {
+ for (int i = 0; i < removedUserAppIds.size(); i++) {
+ final int appId = removedUserAppIds.keyAt(i);
+ // Need to clear permission if the removed appId is not found in the array.
+ if (appIds.indexOfKey(appId) < 0) {
+ appIds.put(appId, PERMISSION_UNINSTALLED);
+ }
+ }
+ }
+ sendAppIdsTrafficPermission(appIds);
+
+ // Log user removed
+ mPermissionUpdateLogs.log("User(" + user.getIdentifier() + ") removed: nPerm uids="
+ + removedUids + ", tPerm appIds=" + removedUserAppIds);
}
/**
@@ -598,6 +649,39 @@
}
}
+ private synchronized void updateAppIdTrafficPermission(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ final int uidTrafficPerm = getTrafficPermissionForUid(uid);
+ final SparseIntArray userTrafficPerms =
+ mUsersTrafficPermissions.get(UserHandle.getUserHandleForUid(uid));
+ if (userTrafficPerms == null) {
+ Log.wtf(TAG, "Can't get user traffic permission from uid=" + uid);
+ return;
+ }
+ // Do not put PERMISSION_UNINSTALLED into the array. If no package left on the uid
+ // (PERMISSION_UNINSTALLED), remove the appId from the array. Otherwise, update the latest
+ // permission to the appId.
+ if (uidTrafficPerm == PERMISSION_UNINSTALLED) {
+ userTrafficPerms.delete(appId);
+ } else {
+ userTrafficPerms.put(appId, uidTrafficPerm);
+ }
+ }
+
+ private synchronized int getAppIdTrafficPermission(int appId) {
+ int permission = PERMISSION_NONE;
+ boolean installed = false;
+ for (UserHandle user : mUsersTrafficPermissions.keySet()) {
+ final SparseIntArray userApps = mUsersTrafficPermissions.get(user);
+ final int appIdx = userApps.indexOfKey(appId);
+ if (appIdx >= 0) {
+ permission |= userApps.valueAt(appIdx);
+ installed = true;
+ }
+ }
+ return installed ? permission : PERMISSION_UNINSTALLED;
+ }
+
/**
* Called when a package is added.
*
@@ -607,9 +691,12 @@
* @hide
*/
public synchronized void onPackageAdded(@NonNull final String packageName, final int uid) {
+ // Update uid permission.
+ updateAppIdTrafficPermission(uid);
+ // Get the appId permission from all users then send the latest permission to netd.
final int appId = UserHandle.getAppId(uid);
- final int trafficPerm = getTrafficPermissionForUid(uid);
- sendPackagePermissionsForAppId(appId, trafficPerm);
+ final int appIdTrafficPerm = getAppIdTrafficPermission(appId);
+ sendPackagePermissionsForAppId(appId, appIdTrafficPerm);
final int currentPermission = mUidToNetworkPerm.get(uid, PERMISSION_NONE);
final int permission = highestPermissionForUid(uid, currentPermission, packageName);
@@ -633,10 +720,12 @@
// package can bypass VPN.
updateVpnUid(uid, true /* add */);
mAllApps.add(appId);
+
+ // Log package added.
mPermissionUpdateLogs.log("Package add: name=" + packageName + ", uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
- + ", tPerm=" + permissionToString(trafficPerm));
+ + ", tPerm=" + permissionToString(appIdTrafficPerm));
}
private int highestUidNetworkPermission(int uid) {
@@ -664,9 +753,12 @@
* @hide
*/
public synchronized void onPackageRemoved(@NonNull final String packageName, final int uid) {
+ // Update uid permission.
+ updateAppIdTrafficPermission(uid);
+ // Get the appId permission from all users then send the latest permission to netd.
final int appId = UserHandle.getAppId(uid);
- final int trafficPerm = getTrafficPermissionForUid(uid);
- sendPackagePermissionsForAppId(appId, trafficPerm);
+ final int appIdTrafficPerm = getAppIdTrafficPermission(appId);
+ sendPackagePermissionsForAppId(appId, appIdTrafficPerm);
// If the newly-removed package falls within some VPN's uid range, update Netd with it.
// This needs to happen before the mUidToNetworkPerm update below, since
@@ -680,10 +772,13 @@
final int currentPermission = mUidToNetworkPerm.get(uid, PERMISSION_NONE);
final int permission = highestUidNetworkPermission(uid);
+
+ // Log package removed.
mPermissionUpdateLogs.log("Package remove: name=" + packageName + ", uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
- + ", tPerm=" + permissionToString(trafficPerm));
+ + ", tPerm=" + permissionToString(appIdTrafficPerm));
+
if (permission != currentPermission) {
final SparseIntArray apps = new SparseIntArray();
int sdkSandboxUid = -1;
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index b23074d..7bb7cb5 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -43,10 +43,23 @@
],
}
-// Connectivity coverage tests combines Tethering and Connectivity tests, each with their
-// respective jarjar rules applied.
-// Some tests may be duplicated (in particular static lib tests), as they need to be run under both
-// jarjared packages to cover both usages.
+// Combine Connectivity, NetworkStack and Tethering jarjar rules for coverage target.
+// The jarjar files are simply concatenated in the order specified in srcs.
+// jarjar stops at the first matching rule, so order of concatenation affects the output.
+genrule {
+ name: "ConnectivityCoverageJarJarRules",
+ srcs: [
+ "tethering-jni-jarjar-rules.txt",
+ ":connectivity-jarjar-rules",
+ ":TetheringTestsJarJarRules",
+ ":NetworkStackJarJarRules",
+ ],
+ out: ["jarjar-rules-connectivity-coverage.txt"],
+ // Concat files with a line break in the middle
+ cmd: "for src in $(in); do cat $${src}; echo; done > $(out)",
+ visibility: ["//visibility:private"],
+}
+
android_library {
name: "ConnectivityCoverageTestsLib",
min_sdk_version: "30",
@@ -54,8 +67,11 @@
"FrameworksNetTestsLib",
"NetdStaticLibTestsLib",
"NetworkStaticLibTestsLib",
+ "NetworkStackTestsLib",
+ "TetheringTestsLatestSdkLib",
+ "TetheringIntegrationTestsLatestSdkLib",
],
- jarjar_rules: ":connectivity-jarjar-rules",
+ jarjar_rules: ":ConnectivityCoverageJarJarRules",
manifest: "AndroidManifest_coverage.xml",
visibility: ["//visibility:private"],
}
@@ -80,7 +96,6 @@
"mockito-target-extended-minus-junit4",
"modules-utils-native-coverage-listener",
"ConnectivityCoverageTestsLib",
- "TetheringCoverageTestsLib",
],
jni_libs: [
// For mockito extended
diff --git a/tests/common/tethering-jni-jarjar-rules.txt b/tests/common/tethering-jni-jarjar-rules.txt
new file mode 100644
index 0000000..593ba14
--- /dev/null
+++ b/tests/common/tethering-jni-jarjar-rules.txt
@@ -0,0 +1,10 @@
+# Match the tethering jarjar rules for utils backed by
+# libcom_android_networkstack_tethering_util_jni, so that this JNI library can be used as-is in the
+# test. The alternative would be to build a test-specific JNI library
+# (libcom_android_connectivity_tests_coverage_jni ?) that registers classes following whatever
+# jarjar rules the test is using, but this is a bit less realistic (using a different JNI library),
+# and complicates the test build. It would be necessary if TetheringUtils had a different package
+# name in test code though, as the JNI library name is deducted from the TetheringUtils package.
+rule com.android.net.module.util.BpfMap* com.android.networkstack.tethering.util.BpfMap@1
+rule com.android.net.module.util.BpfUtils* com.android.networkstack.tethering.util.BpfUtils@1
+rule com.android.net.module.util.TcUtils* com.android.networkstack.tethering.util.TcUtils@1
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index d782008..875b4a2 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -1,4 +1,3 @@
# Bug template url: http://b/new?component=31808
-# Bug component: 685852 = per-file **IpSec*
set noparent
file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index a840242..f460180 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -149,7 +149,7 @@
private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
- protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 5_000; // 5 sec
+ protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec
private static final long BROADCAST_TIMEOUT_MS = 15_000;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 89a9bd6..b6218d2 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -38,6 +38,7 @@
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -99,6 +100,10 @@
return mBatterySaverSupported;
}
+ private static boolean isWear() {
+ return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+
/**
* As per CDD requirements, if the device doesn't support data saver mode then
* ConnectivityManager.getRestrictBackgroundStatus() will always return
@@ -107,6 +112,9 @@
* RESTRICT_BACKGROUND_STATUS_DISABLED or not.
*/
public static boolean isDataSaverSupported() {
+ if (isWear()) {
+ return false;
+ }
if (mDataSaverSupported == null) {
assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
try {
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index d40bc9f..9055861 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -590,6 +590,7 @@
}
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @AppModeFull(reason = "Cannot get installed packages in instant app mode")
@Test
public void testGetRedactedLinkPropertiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
@@ -675,6 +676,7 @@
}
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @AppModeFull(reason = "Cannot get installed packages in instant app mode")
@Test
public void testGetRedactedNetworkCapabilitiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 9cd8418..e8add6b 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -146,6 +146,9 @@
@After
fun tearDown() {
+ if (!kernelIsAtLeast(5, 4)) {
+ return;
+ }
agentsToCleanUp.forEach { it.unregister() }
callbacksToCleanUp.forEach { cm.unregisterNetworkCallback(it) }
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 2737258..30e0015 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -17,7 +17,9 @@
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
+import android.net.InetAddresses
import android.net.IpConfiguration
+import android.net.MacAddress
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
import android.platform.test.annotations.AppModeFull
@@ -32,6 +34,7 @@
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import android.content.Context
import org.junit.runner.RunWith
import kotlin.test.assertNull
import kotlin.test.fail
@@ -46,10 +49,15 @@
import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
import com.android.networkstack.apishim.EthernetManagerShimImpl
+import com.android.testutils.RouterAdvertisementResponder
+import com.android.testutils.TapPacketReader
+import com.android.testutils.waitForIdle
+import java.net.Inet6Address
import java.util.concurrent.Executor
import kotlin.test.assertFalse
import kotlin.test.assertEquals
import kotlin.test.assertTrue
+import java.net.NetworkInterface
private const val TIMEOUT_MS = 1000L
private const val NO_CALLBACK_TIMEOUT_MS = 200L
@@ -66,9 +74,40 @@
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
- private val createdIfaces = ArrayList<TestNetworkInterface>()
+ private val createdIfaces = ArrayList<EthernetTestInterface>()
private val addedListeners = ArrayList<InterfaceStateListener>()
+ private class EthernetTestInterface(
+ context: Context,
+ private val handler: Handler
+ ) {
+ private val tapInterface: TestNetworkInterface
+ private val packetReader: TapPacketReader
+ private val raResponder: RouterAdvertisementResponder
+ val interfaceName get() = tapInterface.interfaceName
+
+ init {
+ tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
+ val tnm = context.getSystemService(TestNetworkManager::class.java)
+ tnm.createTapInterface(false /* bringUp */)
+ }
+ val mtu = NetworkInterface.getByName(tapInterface.interfaceName).getMTU()
+ packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
+ raResponder = RouterAdvertisementResponder(packetReader)
+ raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"),
+ InetAddresses.parseNumericAddress("fe80::abcd") as Inet6Address)
+
+ packetReader.startAsyncForTest()
+ raResponder.start()
+ }
+
+ fun destroy() {
+ raResponder.stop()
+ handler.post({ packetReader.stop() })
+ handler.waitForIdle(TIMEOUT_MS)
+ }
+ }
+
private open class EthernetStateListener private constructor(
private val history: ArrayTrackRecord<CallbackEntry>
) : InterfaceStateListener,
@@ -101,7 +140,7 @@
return event as T
}
- fun expectCallback(iface: TestNetworkInterface, state: Int, role: Int) {
+ fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
expectCallback(InterfaceStateChanged(iface.interfaceName, state, role,
if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null))
}
@@ -116,23 +155,55 @@
}
}
+ @Before
+ fun setUp() {
+ setIncludeTestInterfaces(true)
+ }
+
+ @After
+ fun tearDown() {
+ setIncludeTestInterfaces(false)
+ for (iface in createdIfaces) {
+ iface.destroy()
+ }
+ for (listener in addedListeners) {
+ em.removeInterfaceStateListener(listener)
+ }
+ }
+
+ private fun addInterfaceStateListener(executor: Executor, listener: InterfaceStateListener) {
+ em.addInterfaceStateListener(executor, listener)
+ addedListeners.add(listener)
+ }
+
+ private fun createInterface(): EthernetTestInterface {
+ return EthernetTestInterface(context, Handler(Looper.getMainLooper()))
+ }
+
+ private fun setIncludeTestInterfaces(value: Boolean) {
+ runAsShell(NETWORK_SETTINGS) {
+ em.setIncludeTestInterfaces(value)
+ }
+ }
+
+ private fun removeInterface(iface: EthernetTestInterface) {
+ iface.destroy()
+ createdIfaces.remove(iface)
+ }
+
@Test
public fun testCallbacks() {
val executor = HandlerExecutor(Handler(Looper.getMainLooper()))
// If an interface exists when the callback is registered, it is reported on registration.
- val iface = runAsShell(MANAGE_TEST_NETWORKS) {
- createInterface()
- }
+ val iface = createInterface()
val listener = EthernetStateListener()
addInterfaceStateListener(executor, listener)
listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
// If an interface appears, existing callbacks see it.
// TODO: fix the up/up/down/up callbacks and only send down/up.
- val iface2 = runAsShell(MANAGE_TEST_NETWORKS) {
- createInterface()
- }
+ val iface2 = createInterface()
listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
@@ -149,66 +220,25 @@
listener.assertNoCallback()
}
- @Before
- fun setUp() {
- runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
- em.setIncludeTestInterfaces(true)
- }
- }
-
- @After
- fun tearDown() {
- runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
- em.setIncludeTestInterfaces(false)
- for (iface in createdIfaces) {
- if (iface.fileDescriptor.fileDescriptor.valid()) iface.fileDescriptor.close()
- }
- for (listener in addedListeners) {
- em.removeInterfaceStateListener(listener)
- }
- }
- }
-
- private fun addInterfaceStateListener(executor: Executor, listener: InterfaceStateListener) {
- em.addInterfaceStateListener(executor, listener)
- addedListeners.add(listener)
- }
-
- private fun createInterface(): TestNetworkInterface {
- val tnm = context.getSystemService(TestNetworkManager::class.java)
- return tnm.createTapInterface(false /* bringUp */).also { createdIfaces.add(it) }
- }
-
- private fun removeInterface(iface: TestNetworkInterface) {
- iface.fileDescriptor.close()
- createdIfaces.remove(iface)
- }
-
- private fun doTestGetInterfaceList() {
- em.setIncludeTestInterfaces(true)
+ @Test
+ public fun testGetInterfaceList() {
+ setIncludeTestInterfaces(true)
// Create two test interfaces and check the return list contains the interface names.
val iface1 = createInterface()
val iface2 = createInterface()
var ifaces = em.getInterfaceList()
assertTrue(ifaces.size > 0)
- assertTrue(ifaces.contains(iface1.getInterfaceName()))
- assertTrue(ifaces.contains(iface2.getInterfaceName()))
+ assertTrue(ifaces.contains(iface1.interfaceName))
+ assertTrue(ifaces.contains(iface2.interfaceName))
// Remove one existing test interface and check the return list doesn't contain the
// removed interface name.
removeInterface(iface1)
ifaces = em.getInterfaceList()
- assertFalse(ifaces.contains(iface1.getInterfaceName()))
- assertTrue(ifaces.contains(iface2.getInterfaceName()))
+ assertFalse(ifaces.contains(iface1.interfaceName))
+ assertTrue(ifaces.contains(iface2.interfaceName))
removeInterface(iface2)
}
-
- @Test
- public fun testGetInterfaceList() {
- runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
- doTestGetInterfaceList()
- }
- }
}
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 9fa146f..48cbd03 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -20,14 +20,18 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.TestableNetworkCallbackKt.anyNetwork;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -46,17 +50,23 @@
import android.net.TestNetworkInterface;
import android.net.VpnManager;
import android.net.cts.util.CtsNetUtils;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.os.Build;
import android.os.Process;
import android.platform.test.annotations.AppModeFull;
+import android.text.TextUtils;
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.HexDump;
import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl;
import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl;
+import com.android.networkstack.apishim.VpnManagerShimImpl;
+import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim;
import com.android.networkstack.apishim.common.Ikev2VpnProfileShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.networkstack.apishim.common.VpnManagerShim;
+import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.RecorderCallback.CallbackEntry;
@@ -64,6 +74,7 @@
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.junit.After;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -86,6 +97,9 @@
public class Ikev2VpnTest {
private static final String TAG = Ikev2VpnTest.class.getSimpleName();
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
// Test vectors for IKE negotiation in test mode.
private static final String SUCCESSFUL_IKE_INIT_RESP_V4 =
"46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000"
@@ -175,6 +189,8 @@
private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
private static final long TIMEOUT_MS = 15_000;
+ private VpnManagerShim mVmShim = VpnManagerShimImpl.newInstance(sContext);
+
private final X509Certificate mServerRootCa;
private final CertificateAndKey mUserCertKey;
@@ -204,51 +220,55 @@
}, Manifest.permission.MANAGE_TEST_NETWORKS);
}
- private Ikev2VpnProfile buildIkev2VpnProfileCommon(@NonNull Ikev2VpnProfile.Builder builder,
- boolean isRestrictedToTestNetworks,
+ private Ikev2VpnProfile buildIkev2VpnProfileCommon(
+ @NonNull Ikev2VpnProfileBuilderShim builderShim, boolean isRestrictedToTestNetworks,
boolean requiresValidation) throws Exception {
- if (isRestrictedToTestNetworks) {
- builder.restrictToTestNetworks();
- }
- builder.setBypassable(true)
+ builderShim.setBypassable(true)
.setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
.setProxy(TEST_PROXY_INFO)
.setMaxMtu(TEST_MTU)
.setMetered(false);
if (TestUtils.shouldTestTApis()) {
- Ikev2VpnProfileBuilderShimImpl.newInstance().setRequiresInternetValidation(
- builder, requiresValidation);
+ builderShim.setRequiresInternetValidation(requiresValidation);
}
+
+ // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden
+ // method and does not defined in shims.
+ // TODO: replace it in alternative way to remove the hidden method usage
+ final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
+ if (isRestrictedToTestNetworks) {
+ builder.restrictToTestNetworks();
+ }
+
return builder.build();
}
private Ikev2VpnProfile buildIkev2VpnProfilePsk(@NonNull String remote,
boolean isRestrictedToTestNetworks, boolean requiresValidation) throws Exception {
- final Ikev2VpnProfile.Builder builder =
- new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
-
+ final Ikev2VpnProfileBuilderShim builder =
+ Ikev2VpnProfileBuilderShimImpl.newInstance(remote, TEST_IDENTITY, null)
+ .setAuthPsk(TEST_PSK);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
requiresValidation);
}
private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
throws Exception {
- final Ikev2VpnProfile.Builder builder =
- new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
- .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
+ final Ikev2VpnProfileBuilderShim builder =
+ Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY, null)
+ .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
false /* requiresValidation */);
}
private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
throws Exception {
- final Ikev2VpnProfile.Builder builder =
- new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+ final Ikev2VpnProfileBuilderShim builder =
+ Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY, null)
.setAuthDigitalSignature(
mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
-
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
false /* requiresValidation */);
}
@@ -279,18 +299,42 @@
assertNull(profile.getServerRootCaCert());
assertNull(profile.getRsaPrivateKey());
assertNull(profile.getUserCert());
- final Ikev2VpnProfileShim<Ikev2VpnProfile> shim = Ikev2VpnProfileShimImpl.newInstance();
+ final Ikev2VpnProfileShim<Ikev2VpnProfile> shim = new Ikev2VpnProfileShimImpl(profile);
if (TestUtils.shouldTestTApis()) {
- assertEquals(requiresValidation, shim.isInternetValidationRequired(profile));
+ assertEquals(requiresValidation, shim.isInternetValidationRequired());
} else {
try {
- shim.isInternetValidationRequired(profile);
+ shim.isInternetValidationRequired();
fail("Only supported from API level 33");
} catch (UnsupportedApiLevelException expected) {
}
}
}
+ @IgnoreUpTo(SC_V2)
+ @Test
+ public void testBuildIkev2VpnProfileWithIkeTunnelConnectionParams() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+ assumeTrue(TestUtils.shouldTestTApis());
+
+ final IkeTunnelConnectionParams expectedParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS, CHILD_PARAMS);
+ final Ikev2VpnProfileBuilderShim ikeProfileBuilder =
+ Ikev2VpnProfileBuilderShimImpl.newInstance(null, null, expectedParams);
+ // Verify the other Ike options could not be set with IkeTunnelConnectionParams.
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ assertThrows(expected, () -> ikeProfileBuilder.setAuthPsk(TEST_PSK));
+ assertThrows(expected, () ->
+ ikeProfileBuilder.setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa));
+ assertThrows(expected, () -> ikeProfileBuilder.setAuthDigitalSignature(
+ mUserCertKey.cert, mUserCertKey.key, mServerRootCa));
+
+ final Ikev2VpnProfile profile = (Ikev2VpnProfile) ikeProfileBuilder.build().getProfile();
+
+ assertEquals(expectedParams,
+ new Ikev2VpnProfileShimImpl(profile).getIkeTunnelConnectionParams());
+ }
+
@Test
public void testBuildIkev2VpnProfilePsk() throws Exception {
doTestBuildIkev2VpnProfilePsk(true /* requiresValidation */);
@@ -420,7 +464,7 @@
}
private void checkStartStopVpnProfileBuildsNetworks(@NonNull IkeTunUtils tunUtils,
- boolean testIpv6, boolean requiresValidation)
+ boolean testIpv6, boolean requiresValidation, boolean testSessionKey)
throws Exception {
String serverAddr = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4;
String initResp = testIpv6 ? SUCCESSFUL_IKE_INIT_RESP_V6 : SUCCESSFUL_IKE_INIT_RESP_V4;
@@ -439,7 +483,13 @@
.clearCapabilities().addTransportType(TRANSPORT_VPN).build();
sCM.registerNetworkCallback(nr, cb);
- sVpnMgr.startProvisionedVpnProfile();
+ if (testSessionKey) {
+ // testSessionKey will never be true if running on <T
+ // startProvisionedVpnProfileSession() should return a non-null & non-empty random UUID.
+ assertFalse(TextUtils.isEmpty(mVmShim.startProvisionedVpnProfileSession()));
+ } else {
+ sVpnMgr.startProvisionedVpnProfile();
+ }
// Inject IKE negotiation
int expectedMsgId = 0;
@@ -482,16 +532,20 @@
private class VerifyStartStopVpnProfileTest implements TestNetworkRunnable.Test {
private final boolean mTestIpv6Only;
private final boolean mRequiresValidation;
+ private final boolean mTestSessionKey;
/**
* Constructs the test
*
* @param testIpv6Only if true, builds a IPv6-only test; otherwise builds a IPv4-only test
* @param requiresValidation whether this VPN should request platform validation
+ * @param testSessionKey if true, start VPN by calling startProvisionedVpnProfileSession()
*/
- VerifyStartStopVpnProfileTest(boolean testIpv6Only, boolean requiresValidation) {
+ VerifyStartStopVpnProfileTest(boolean testIpv6Only, boolean requiresValidation,
+ boolean testSessionKey) {
mTestIpv6Only = testIpv6Only;
mRequiresValidation = requiresValidation;
+ mTestSessionKey = testSessionKey;
}
@Override
@@ -500,7 +554,7 @@
final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor());
checkStartStopVpnProfileBuildsNetworks(
- tunUtils, mTestIpv6Only, mRequiresValidation);
+ tunUtils, mTestIpv6Only, mRequiresValidation, mTestSessionKey);
}
@Override
@@ -524,10 +578,14 @@
// Requires shell permission to update appops.
runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(false, false)));
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ false /* testIpv6Only */, false /* requiresValidation */,
+ false /* testSessionKey */)));
runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(false, true)));
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ false /* testIpv6Only */, true /* requiresValidation */,
+ false /* testSessionKey */)));
}
@Test
@@ -536,9 +594,31 @@
// Requires shell permission to update appops.
runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(true, false)));
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ true /* testIpv6Only */, false /* requiresValidation */,
+ false /* testSessionKey */)));
runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(true, true)));
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ true /* testIpv6Only */, true /* requiresValidation */,
+ false /* testSessionKey */)));
+ }
+
+ @IgnoreUpTo(SC_V2)
+ @Test
+ public void testStartProvisionedVpnProfileSession() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+ assumeTrue(TestUtils.shouldTestTApis());
+
+ // Requires shell permission to update appops.
+ runWithShellPermissionIdentity(
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ false /* testIpv6Only */, false /* requiresValidation */,
+ true /* testSessionKey */)));
+
+ runWithShellPermissionIdentity(
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ true /* testIpv6Only */, false /* requiresValidation */,
+ true /* testSessionKey */)));
}
private static class CertificateAndKey {
diff --git a/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java b/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java
new file mode 100644
index 0000000..b4ebcdb
--- /dev/null
+++ b/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.util;
+
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_UNUSED;
+
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+
+/** Shared testing parameters and util methods for testing IKE */
+public class IkeSessionTestUtils {
+ private static final String TEST_CLIENT_ADDR = "test.client.com";
+ private static final String TEST_SERVER_ADDR = "test.server.com";
+ private static final String TEST_SERVER = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
+
+ public static final IkeSaProposal SA_PROPOSAL = new IkeSaProposal.Builder()
+ .addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_3DES, KEY_LEN_UNUSED)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .build();
+ public static final ChildSaProposal CHILD_PROPOSAL = new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_NONE)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .build();
+
+ public static final IkeSessionParams IKE_PARAMS =
+ new IkeSessionParams.Builder()
+ .setServerHostname(TEST_SERVER)
+ .addSaProposal(SA_PROPOSAL)
+ .setLocalIdentification(new IkeFqdnIdentification(TEST_CLIENT_ADDR))
+ .setRemoteIdentification(new IkeFqdnIdentification(TEST_SERVER_ADDR))
+ .setAuthPsk("psk".getBytes())
+ .build();
+ public static final TunnelModeChildSessionParams CHILD_PARAMS =
+ new TunnelModeChildSessionParams.Builder()
+ .addSaProposal(CHILD_PROPOSAL)
+ .build();
+}
diff --git a/tests/cts/netpermission/internetpermission/Android.bp b/tests/cts/netpermission/internetpermission/Android.bp
new file mode 100644
index 0000000..37ad7cb
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 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_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsNetTestCasesInternetPermission",
+ defaults: ["cts_defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ static_libs: ["ctstestrunner-axt"],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+}
diff --git a/tests/cts/netpermission/internetpermission/AndroidManifest.xml b/tests/cts/netpermission/internetpermission/AndroidManifest.xml
new file mode 100644
index 0000000..45ef5bd
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.networkpermission.internetpermission.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.networkpermission.internetpermission.cts.InternetPermissionTest"
+ android:label="InternetPermissionTest"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!--
+ The CTS stubs package cannot be used as the target application here,
+ since that requires many permissions to be set. Instead, specify this
+ package itself as the target and include any stub activities needed.
+
+ This test package uses the default InstrumentationTestRunner, because
+ the InstrumentationCtsTestRunner is only available in the stubs
+ package. That runner cannot be added to this package either, since it
+ relies on hidden APIs.
+ -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.networkpermission.internetpermission.cts"
+ android:label="CTS tests for INTERNET permissions">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
+ </instrumentation>
+
+</manifest>
diff --git a/tests/cts/netpermission/internetpermission/AndroidTest.xml b/tests/cts/netpermission/internetpermission/AndroidTest.xml
new file mode 100644
index 0000000..3b23e72
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Config for CTS internet permission test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="not-shardable" value="true" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsNetTestCasesInternetPermission.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.networkpermission.internetpermission.cts" />
+ <option name="runtime-hint" value="10s" />
+ </test>
+</configuration>
diff --git a/tests/cts/netpermission/internetpermission/TEST_MAPPING b/tests/cts/netpermission/internetpermission/TEST_MAPPING
new file mode 100644
index 0000000..60877f4
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsNetTestCasesInternetPermission"
+ }
+ ]
+}
diff --git a/tests/cts/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java b/tests/cts/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java
new file mode 100644
index 0000000..2b7c8b5
--- /dev/null
+++ b/tests/cts/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.networkpermission.internetpermission;
+
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Socket;
+/**
+* Test that protected android.net.ConnectivityManager methods cannot be called without
+* permissions
+*/
+@RunWith(AndroidJUnit4.class)
+public class InternetPermissionTest {
+
+ /**
+ * Verify that create inet socket failed because of the permission is missing.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#INTERNET}.
+ */
+ @SmallTest
+ @Test
+ public void testCreateSocket() throws Exception {
+ try {
+ Socket socket = new Socket("example.com", 80);
+ fail("Ceate inet socket did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/netpermission/updatestatspermission/Android.bp b/tests/cts/netpermission/updatestatspermission/Android.bp
new file mode 100644
index 0000000..7a24886
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 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_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsNetTestCasesUpdateStatsPermission",
+ defaults: ["cts_defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ static_libs: ["ctstestrunner-axt"],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+}
diff --git a/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml b/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml
new file mode 100644
index 0000000..6babe8f
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.networkpermission.updatestatspermission.cts">
+
+ <!--
+ This CTS test is designed to test that an unprivileged app cannot get the
+ UPDATE_DEVICE_STATS permission even if it specified it in the manifest. the
+ UPDATE_DEVICE_STATS permission is a signature|privileged permission that CTS
+ test cannot have.
+ -->
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.networkpermission.updatestatspermission.cts.UpdateStatsPermissionTest"
+ android:label="UpdateStatsPermissionTest"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!--
+ The CTS stubs package cannot be used as the target application here,
+ since that requires many permissions to be set. Instead, specify this
+ package itself as the target and include any stub activities needed.
+
+ This test package uses the default InstrumentationTestRunner, because
+ the InstrumentationCtsTestRunner is only available in the stubs
+ package. That runner cannot be added to this package either, since it
+ relies on hidden APIs.
+ -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.networkpermission.updatestatspermission.cts"
+ android:label="CTS tests for UPDATE_DEVICE_STATS permissions">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
+ </instrumentation>
+
+</manifest>
diff --git a/tests/cts/netpermission/updatestatspermission/AndroidTest.xml b/tests/cts/netpermission/updatestatspermission/AndroidTest.xml
new file mode 100644
index 0000000..c47cad9
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Config for CTS update stats permission test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="not-shardable" value="true" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsNetTestCasesUpdateStatsPermission.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.networkpermission.updatestatspermission.cts" />
+ <option name="runtime-hint" value="10s" />
+ </test>
+</configuration>
diff --git a/tests/cts/netpermission/updatestatspermission/TEST_MAPPING b/tests/cts/netpermission/updatestatspermission/TEST_MAPPING
new file mode 100644
index 0000000..6d6dfe0
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsNetTestCasesUpdateStatsPermission"
+ }
+ ]
+}
diff --git a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
new file mode 100644
index 0000000..bea843c
--- /dev/null
+++ b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.networkpermission.updatestatspermission;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.TrafficStats;
+import android.os.Process;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.OutputStream;
+import java.net.Socket;
+
+/**
+* Test that protected android.net.ConnectivityManager methods cannot be called without
+* permissions
+*/
+@RunWith(AndroidJUnit4.class)
+public class UpdateStatsPermissionTest {
+
+ /**
+ * Verify that setCounterSet for a different uid failed because of the permission cannot be
+ * granted to a third-party app.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#UPDATE_DEVICE_STATS}.
+ */
+ @SmallTest
+ @Test
+ public void testUpdateDeviceStatsPermission() throws Exception {
+
+ // Set the current thread uid to a another uid. It should silently fail when tagging the
+ // socket since the current process doesn't have UPDATE_DEVICE_STATS permission.
+ TrafficStats.setThreadStatsTag(0);
+ TrafficStats.setThreadStatsUid(/*root uid*/ 0);
+ Socket socket = new Socket("example.com", 80);
+ TrafficStats.tagSocket(socket);
+
+ // Transfer 1K of data to a remote host and verify the stats is still billed to the current
+ // uid.
+ final int byteCount = 1024;
+
+ socket.setTcpNoDelay(true);
+ socket.setSoLinger(true, 0);
+ OutputStream out = socket.getOutputStream();
+ byte[] buf = new byte[byteCount];
+ final long uidTxBytesBefore = TrafficStats.getUidTxBytes(Process.myUid());
+ out.write(buf);
+ out.close();
+ socket.close();
+ long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
+ long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
+ assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta="
+ + uidTxDeltaBytes + " >= " + byteCount, uidTxDeltaBytes >= byteCount);
+ }
+
+ static final int UNSUPPORTED = -1;
+
+ /**
+ * Verify that get TrafficStats of a different uid failed because of the permission is not
+ * granted to a third-party app.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#UPDATE_DEVICE_STATS}.
+ */
+ @SmallTest
+ @Test
+ public void testGetStatsOfOtherUid() throws Exception {
+ // Test get stats of another uid failed since the current process does not have permission
+ assertEquals(UNSUPPORTED, TrafficStats.getUidRxBytes(/*root uid*/ 0));
+ }
+}
diff --git a/tests/native/Android.bp b/tests/native/Android.bp
index fc87815..a8d908a 100644
--- a/tests/native/Android.bp
+++ b/tests/native/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_test {
name: "connectivity_native_test",
test_suites: [
diff --git a/tests/native/OWNERS b/tests/native/OWNERS
new file mode 100644
index 0000000..8dfa455
--- /dev/null
+++ b/tests/native/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 31808
+set noparent
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index c27c973..545f7b9 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -76,7 +76,6 @@
"java/com/android/server/IpSecServiceParameterizedTest.java",
"java/com/android/server/IpSecServiceRefcountedResourceTest.java",
"java/com/android/server/IpSecServiceTest.java",
- "java/com/android/server/NativeDaemonConnectorTest.java",
"java/com/android/server/NetworkManagementServiceTest.java",
"java/com/android/server/NsdServiceTest.java",
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
@@ -131,6 +130,7 @@
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
"services.core-vpn",
+ "cts-net-utils"
],
libs: [
"android.net.ipsec.ike.stubs.module_lib",
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index 8559c20..8222ca1 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -16,6 +16,9 @@
package android.net;
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS;
+
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertArrayEquals;
@@ -25,6 +28,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.os.Build;
import android.test.mock.MockContext;
@@ -441,6 +445,33 @@
assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
}
+ @Test
+ public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS, CHILD_PARAMS);
+ // Config authentication related fields is not required while building with
+ // IkeTunnelConnectionParams.
+ final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ // Verify building without IkeTunnelConnectionParams
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ assertEquals(builder.build(), builder.build());
+
+ // Verify building with IkeTunnelConnectionParams
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS, CHILD_PARAMS);
+ final IkeTunnelConnectionParams tunnelParams2 =
+ new IkeTunnelConnectionParams(IKE_PARAMS, CHILD_PARAMS);
+ assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
+ new Ikev2VpnProfile.Builder(tunnelParams2).build());
+ }
+
+
private static class CertificateAndKey {
public final X509Certificate cert;
public final PrivateKey key;
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
index 943a559..360390d 100644
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
@@ -16,6 +16,9 @@
package com.android.internal.net;
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS;
+
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.testutils.ParcelUtils.assertParcelSane;
@@ -26,6 +29,7 @@
import static org.junit.Assert.assertTrue;
import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.os.Build;
import androidx.test.filters.SmallTest;
@@ -85,7 +89,8 @@
private VpnProfile getSampleIkev2Profile(String key) {
final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
- false /* excludesLocalRoutes */, true /* requiresPlatformValidation */);
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ null /* ikeTunConnParams */);
p.name = "foo";
p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
@@ -120,6 +125,35 @@
return p;
}
+ private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ new IkeTunnelConnectionParams(IKE_PARAMS, CHILD_PARAMS));
+
+ p.name = "foo";
+ p.server = "bar";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
@Test
public void testEquals() {
assertEquals(
@@ -134,13 +168,21 @@
public void testParcelUnparcel() {
if (isAtLeastT()) {
// excludeLocalRoutes, requiresPlatformValidation were added in T.
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 25);
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 26);
+ assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 26);
} else {
assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
}
}
@Test
+ public void testEncodeDecodeWithIkeTunConnParams() {
+ final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
public void testEncodeDecode() {
final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
diff --git a/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java b/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
deleted file mode 100644
index e2253a2..0000000
--- a/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static com.android.server.NativeDaemonConnector.appendEscaped;
-import static com.android.server.NativeDaemonConnector.makeCommand;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-import com.android.server.NativeDaemonConnector.SensitiveArg;
-
-/**
- * Tests for {@link NativeDaemonConnector}.
- */
-@MediumTest
-public class NativeDaemonConnectorTest extends AndroidTestCase {
- private static final String TAG = "NativeDaemonConnectorTest";
-
- public void testArgumentNormal() throws Exception {
- final StringBuilder builder = new StringBuilder();
-
- builder.setLength(0);
- appendEscaped(builder, "");
- assertEquals("", builder.toString());
-
- builder.setLength(0);
- appendEscaped(builder, "foo");
- assertEquals("foo", builder.toString());
-
- builder.setLength(0);
- appendEscaped(builder, "foo\"bar");
- assertEquals("foo\\\"bar", builder.toString());
-
- builder.setLength(0);
- appendEscaped(builder, "foo\\bar\\\"baz");
- assertEquals("foo\\\\bar\\\\\\\"baz", builder.toString());
- }
-
- public void testArgumentWithSpaces() throws Exception {
- final StringBuilder builder = new StringBuilder();
-
- builder.setLength(0);
- appendEscaped(builder, "foo bar");
- assertEquals("\"foo bar\"", builder.toString());
-
- builder.setLength(0);
- appendEscaped(builder, "foo\"bar\\baz foo");
- assertEquals("\"foo\\\"bar\\\\baz foo\"", builder.toString());
- }
-
- public void testArgumentWithUtf() throws Exception {
- final StringBuilder builder = new StringBuilder();
-
- builder.setLength(0);
- appendEscaped(builder, "caf\u00E9 c\u00F6ffee");
- assertEquals("\"caf\u00E9 c\u00F6ffee\"", builder.toString());
- }
-
- public void testSensitiveArgs() throws Exception {
- final StringBuilder rawBuilder = new StringBuilder();
- final StringBuilder logBuilder = new StringBuilder();
-
- rawBuilder.setLength(0);
- logBuilder.setLength(0);
- makeCommand(rawBuilder, logBuilder, 1, "foo", "bar", "baz");
- assertEquals("1 foo bar baz\0", rawBuilder.toString());
- assertEquals("1 foo bar baz", logBuilder.toString());
-
- rawBuilder.setLength(0);
- logBuilder.setLength(0);
- makeCommand(rawBuilder, logBuilder, 1, "foo", new SensitiveArg("bar"), "baz");
- assertEquals("1 foo bar baz\0", rawBuilder.toString());
- assertEquals("1 foo [scrubbed] baz", logBuilder.toString());
-
- rawBuilder.setLength(0);
- logBuilder.setLength(0);
- makeCommand(rawBuilder, logBuilder, 1, "foo", new SensitiveArg("foo bar"), "baz baz",
- new SensitiveArg("wat"));
- assertEquals("1 foo \"foo bar\" \"baz baz\" wat\0", rawBuilder.toString());
- assertEquals("1 foo [scrubbed] \"baz baz\" [scrubbed]", logBuilder.toString());
- }
-}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 5086943..3c228d0 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -19,10 +19,12 @@
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -36,6 +38,7 @@
import android.content.Context;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
+import android.net.nsd.MDnsManager;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Binder;
@@ -49,9 +52,6 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
-import com.android.server.NsdService.DaemonConnection;
-import com.android.server.NsdService.DaemonConnectionSupplier;
-import com.android.server.NsdService.NativeCallbackReceiver;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -63,10 +63,8 @@
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import java.util.LinkedList;
import java.util.Queue;
@@ -92,8 +90,7 @@
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Mock Context mContext;
@Mock ContentResolver mResolver;
- NativeCallbackReceiver mDaemonCallback;
- @Spy DaemonConnection mDaemon = new DaemonConnection(mDaemonCallback);
+ @Mock MDnsManager mMockMDnsM;
HandlerThread mThread;
TestHandler mHandler;
@@ -112,9 +109,17 @@
MockitoAnnotations.initMocks(this);
mThread = new HandlerThread("mock-service-handler");
mThread.start();
- doReturn(true).when(mDaemon).execute(any());
mHandler = new TestHandler(mThread.getLooper());
when(mContext.getContentResolver()).thenReturn(mResolver);
+ doReturn(MDnsManager.MDNS_SERVICE).when(mContext)
+ .getSystemServiceName(MDnsManager.class);
+ doReturn(mMockMDnsM).when(mContext).getSystemService(MDnsManager.MDNS_SERVICE);
+ doReturn(true).when(mMockMDnsM).registerService(
+ anyInt(), anyString(), anyString(), anyInt(), any(), anyInt());
+ doReturn(true).when(mMockMDnsM).stopOperation(anyInt());
+ doReturn(true).when(mMockMDnsM).discover(anyInt(), anyString(), anyInt());
+ doReturn(true).when(mMockMDnsM).resolve(
+ anyInt(), anyString(), anyString(), anyString(), anyInt());
}
@After
@@ -135,24 +140,25 @@
waitForIdle();
final INsdManagerCallback cb1 = getCallback();
final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1);
- verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommands("start-service");
+ verify(mMockMDnsM, times(1)).registerEventListener(any());
+ verify(mMockMDnsM, times(1)).startDaemon();
connectClient(service);
waitForIdle();
final INsdManagerCallback cb2 = getCallback();
final IBinder.DeathRecipient deathRecipient2 = verifyLinkToDeath(cb2);
- verify(mDaemon, times(1)).maybeStart();
+ // Daemon has been started, it should not try to start it again.
+ verify(mMockMDnsM, times(1)).registerEventListener(any());
+ verify(mMockMDnsM, times(1)).startDaemon();
deathRecipient1.binderDied();
// Still 1 client remains, daemon shouldn't be stopped.
waitForIdle();
- verify(mDaemon, never()).maybeStop();
+ verify(mMockMDnsM, never()).stopDaemon();
deathRecipient2.binderDied();
// All clients are disconnected, the daemon should be stopped.
verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
- verifyDaemonCommands("stop-service");
}
@Test
@@ -160,28 +166,30 @@
public void testNoDaemonStartedWhenClientsConnect() throws Exception {
final NsdService service = makeService();
- // Creating an NsdManager will not cause any cmds executed, which means
- // no daemon is started.
+ // Creating an NsdManager will not cause daemon startup.
connectClient(service);
waitForIdle();
- verify(mDaemon, never()).execute(any());
+ verify(mMockMDnsM, never()).registerEventListener(any());
+ verify(mMockMDnsM, never()).startDaemon();
final INsdManagerCallback cb1 = getCallback();
final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1);
- // Creating another NsdManager will not cause any cmds executed.
+ // Creating another NsdManager will not cause daemon startup either.
connectClient(service);
waitForIdle();
- verify(mDaemon, never()).execute(any());
+ verify(mMockMDnsM, never()).registerEventListener(any());
+ verify(mMockMDnsM, never()).startDaemon();
final INsdManagerCallback cb2 = getCallback();
final IBinder.DeathRecipient deathRecipient2 = verifyLinkToDeath(cb2);
- // If there is no active request, try to clean up the daemon
- // every time the client disconnects.
+ // If there is no active request, try to clean up the daemon but should not do it because
+ // daemon has not been started.
deathRecipient1.binderDied();
- verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
- reset(mDaemon);
+ verify(mMockMDnsM, never()).unregisterEventListener(any());
+ verify(mMockMDnsM, never()).stopDaemon();
deathRecipient2.binderDied();
- verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
+ verify(mMockMDnsM, never()).unregisterEventListener(any());
+ verify(mMockMDnsM, never()).stopDaemon();
}
private IBinder.DeathRecipient verifyLinkToDeath(INsdManagerCallback cb)
@@ -200,8 +208,8 @@
waitForIdle();
final INsdManagerCallback cb1 = getCallback();
final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1);
- verify(mDaemon, never()).maybeStart();
- verify(mDaemon, never()).execute(any());
+ verify(mMockMDnsM, never()).registerEventListener(any());
+ verify(mMockMDnsM, never()).startDaemon();
NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
request.setPort(2201);
@@ -210,29 +218,31 @@
NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
client.registerService(request, PROTOCOL, listener1);
waitForIdle();
- verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommands("start-service", "register 2 a_name a_type 2201");
+ verify(mMockMDnsM, times(1)).registerEventListener(any());
+ verify(mMockMDnsM, times(1)).startDaemon();
+ verify(mMockMDnsM, times(1)).registerService(
+ eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0));
// Client discovery request
NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
client.discoverServices("a_type", PROTOCOL, listener2);
waitForIdle();
- verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommand("discover 3 a_type 0");
+ verify(mMockMDnsM, times(1)).discover(eq(3), eq("a_type"), eq(0));
// Client resolve request
NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
client.resolveService(request, listener3);
waitForIdle();
- verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommand("resolve 4 a_name a_type local. 0");
+ verify(mMockMDnsM, times(1)).resolve(
+ eq(4), eq("a_name"), eq("a_type"), eq("local."), eq(0));
// Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
deathRecipient.binderDied();
verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
// checks that request are cleaned
- verifyDaemonCommands("stop-register 2", "stop-discover 3",
- "stop-resolve 4", "stop-service");
+ verify(mMockMDnsM, times(1)).stopOperation(eq(2));
+ verify(mMockMDnsM, times(1)).stopOperation(eq(3));
+ verify(mMockMDnsM, times(1)).stopOperation(eq(4));
}
@Test
@@ -246,20 +256,23 @@
NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
client.registerService(request, PROTOCOL, listener1);
waitForIdle();
- verify(mDaemon, times(1)).maybeStart();
+ verify(mMockMDnsM, times(1)).registerEventListener(any());
+ verify(mMockMDnsM, times(1)).startDaemon();
final INsdManagerCallback cb1 = getCallback();
final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1);
- verifyDaemonCommands("start-service", "register 2 a_name a_type 2201");
+ verify(mMockMDnsM, times(1)).registerService(
+ eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0));
client.unregisterService(listener1);
- verifyDaemonCommand("stop-register 2");
+ waitForIdle();
+ verify(mMockMDnsM, times(1)).stopOperation(eq(2));
verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
- verifyDaemonCommand("stop-service");
- reset(mDaemon);
+ reset(mMockMDnsM);
deathRecipient.binderDied();
- // Client disconnects, after CLEANUP_DELAY_MS, maybeStop the daemon.
- verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
+ // Client disconnects, daemon should not be stopped after CLEANUP_DELAY_MS.
+ verify(mMockMDnsM, never()).unregisterEventListener(any());
+ verify(mMockMDnsM, never()).stopDaemon();
}
private void waitForIdle() {
@@ -267,11 +280,7 @@
}
NsdService makeService() {
- DaemonConnectionSupplier supplier = (callback) -> {
- mDaemonCallback = callback;
- return mDaemon;
- };
- final NsdService service = new NsdService(mContext, mHandler, supplier, CLEANUP_DELAY_MS) {
+ final NsdService service = new NsdService(mContext, mHandler, CLEANUP_DELAY_MS) {
@Override
public INsdServiceConnector connect(INsdManagerCallback baseCb) {
// Wrap the callback in a transparent mock, to mock asBinder returning a
@@ -285,7 +294,6 @@
return super.connect(cb);
}
};
- verify(mDaemon, never()).execute(any(String.class));
return service;
}
@@ -297,34 +305,15 @@
return new NsdManager(mContext, service);
}
- void verifyDelayMaybeStopDaemon(long cleanupDelayMs) {
+ void verifyDelayMaybeStopDaemon(long cleanupDelayMs) throws Exception {
waitForIdle();
// Stop daemon shouldn't be called immediately.
- verify(mDaemon, never()).maybeStop();
+ verify(mMockMDnsM, never()).unregisterEventListener(any());
+ verify(mMockMDnsM, never()).stopDaemon();
+
// Clean up the daemon after CLEANUP_DELAY_MS.
- verify(mDaemon, timeout(cleanupDelayMs + TIMEOUT_MS)).maybeStop();
- }
-
- void verifyDaemonCommands(String... wants) {
- verifyDaemonCommand(String.join(" ", wants), wants.length);
- }
-
- void verifyDaemonCommand(String want) {
- verifyDaemonCommand(want, 1);
- }
-
- void verifyDaemonCommand(String want, int n) {
- waitForIdle();
- final ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
- verify(mDaemon, times(n)).execute(argumentsCaptor.capture());
- String got = "";
- for (Object o : argumentsCaptor.getAllValues()) {
- got += o + " ";
- }
- assertEquals(want, got.trim());
- // rearm deamon for next command verification
- reset(mDaemon);
- doReturn(true).when(mDaemon).execute(any());
+ verify(mMockMDnsM, timeout(cleanupDelayMs + TIMEOUT_MS)).unregisterEventListener(any());
+ verify(mMockMDnsM, timeout(cleanupDelayMs + TIMEOUT_MS)).stopDaemon();
}
public static class TestHandler extends Handler {
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 6c8b545..c3d64cb 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -17,11 +17,16 @@
package com.android.server.connectivity;
import static android.net.INetd.IF_STATE_UP;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
import static com.android.server.connectivity.ClatCoordinator.CLAT_MAX_MTU;
+import static com.android.server.connectivity.ClatCoordinator.EGRESS;
+import static com.android.server.connectivity.ClatCoordinator.INGRESS;
import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_PREFIX_LEN;
import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_STRING;
+import static com.android.server.connectivity.ClatCoordinator.PRIO_CLAT;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertEquals;
@@ -41,6 +46,11 @@
import androidx.test.filters.SmallTest;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.bpf.ClatEgress4Key;
+import com.android.net.module.util.bpf.ClatEgress4Value;
+import com.android.net.module.util.bpf.ClatIngress6Key;
+import com.android.net.module.util.bpf.ClatIngress6Value;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -98,8 +108,23 @@
private static final ParcelFileDescriptor PACKET_SOCK_PFD = new ParcelFileDescriptor(
new FileDescriptor());
+ private static final String EGRESS_PROG_PATH =
+ "/sys/fs/bpf/prog_clatd_schedcls_egress4_clat_rawip";
+ private static final String INGRESS_PROG_PATH =
+ "/sys/fs/bpf/prog_clatd_schedcls_ingress6_clat_ether";
+ private static final ClatEgress4Key EGRESS_KEY = new ClatEgress4Key(STACKED_IFINDEX,
+ INET4_LOCAL4);
+ private static final ClatEgress4Value EGRESS_VALUE = new ClatEgress4Value(BASE_IFINDEX,
+ INET6_LOCAL6, INET6_PFX96, (short) 1 /* oifIsEthernet, 1 = true */);
+ private static final ClatIngress6Key INGRESS_KEY = new ClatIngress6Key(BASE_IFINDEX,
+ INET6_PFX96, INET6_LOCAL6);
+ private static final ClatIngress6Value INGRESS_VALUE = new ClatIngress6Value(STACKED_IFINDEX,
+ INET4_LOCAL4);
+
@Mock private INetd mNetd;
@Spy private TestDependencies mDeps = new TestDependencies();
+ @Mock private IBpfMap<ClatIngress6Key, ClatIngress6Value> mIngressMap;
+ @Mock private IBpfMap<ClatEgress4Key, ClatEgress4Value> mEgressMap;
/**
* The dependency injection class is used to mock the JNI functions and system functions
@@ -298,6 +323,49 @@
fail("unsupported arg: " + cookie);
}
}
+
+ /** Get ingress6 BPF map. */
+ @Override
+ public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
+ return mIngressMap;
+ }
+
+ /** Get egress4 BPF map. */
+ @Override
+ public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
+ return mEgressMap;
+ }
+
+ /** Checks if the network interface uses an ethernet L2 header. */
+ public boolean isEthernet(String iface) throws IOException {
+ if (BASE_IFACE.equals(iface)) return true;
+
+ fail("unsupported arg: " + iface);
+ return false;
+ }
+
+ /** Add a clsact qdisc. */
+ @Override
+ public void tcQdiscAddDevClsact(int ifIndex) throws IOException {
+ // no-op
+ return;
+ }
+
+ /** Attach a tc bpf filter. */
+ @Override
+ public void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio, short proto,
+ String bpfProgPath) throws IOException {
+ // no-op
+ return;
+ }
+
+ /** Delete a tc filter. */
+ @Override
+ public void tcFilterDelDev(int ifIndex, boolean ingress, short prio, short proto)
+ throws IOException {
+ // no-op
+ return;
+ }
};
@NonNull
@@ -322,8 +390,8 @@
@Test
public void testStartStopClatd() throws Exception {
final ClatCoordinator coordinator = makeClatCoordinator();
- final InOrder inOrder = inOrder(mNetd, mDeps);
- clearInvocations(mNetd, mDeps);
+ final InOrder inOrder = inOrder(mNetd, mDeps, mIngressMap, mEgressMap);
+ clearInvocations(mNetd, mDeps, mIngressMap, mEgressMap);
// [1] Start clatd.
final String addr6For464xlat = coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
@@ -379,6 +447,13 @@
argThat(fd -> Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), fd)),
eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING));
+ inOrder.verify(mEgressMap).insertEntry(eq(EGRESS_KEY), eq(EGRESS_VALUE));
+ inOrder.verify(mIngressMap).insertEntry(eq(INGRESS_KEY), eq(INGRESS_VALUE));
+ inOrder.verify(mDeps).tcQdiscAddDevClsact(eq(STACKED_IFINDEX));
+ inOrder.verify(mDeps).tcFilterAddDevBpf(eq(STACKED_IFINDEX), eq(EGRESS),
+ eq((short) PRIO_CLAT), eq((short) ETH_P_IP), eq(EGRESS_PROG_PATH));
+ inOrder.verify(mDeps).tcFilterAddDevBpf(eq(BASE_IFINDEX), eq(INGRESS),
+ eq((short) PRIO_CLAT), eq((short) ETH_P_IPV6), eq(INGRESS_PROG_PATH));
inOrder.verifyNoMoreInteractions();
// [2] Start clatd again failed.
@@ -388,6 +463,12 @@
// [3] Expect clatd to stop successfully.
coordinator.clatStop();
+ inOrder.verify(mDeps).tcFilterDelDev(eq(BASE_IFINDEX), eq(INGRESS),
+ eq((short) PRIO_CLAT), eq((short) ETH_P_IPV6));
+ inOrder.verify(mDeps).tcFilterDelDev(eq(STACKED_IFINDEX), eq(EGRESS),
+ eq((short) PRIO_CLAT), eq((short) ETH_P_IP));
+ inOrder.verify(mEgressMap).deleteEntry(eq(EGRESS_KEY));
+ inOrder.verify(mIngressMap).deleteEntry(eq(INGRESS_KEY));
inOrder.verify(mDeps).stopClatd(eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(CLATD_PID));
inOrder.verify(mDeps).untagSocket(eq(RAW_SOCK_COOKIE));
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 6b379e8..fb821c3 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -117,23 +117,32 @@
public class PermissionMonitorTest {
private static final int MOCK_USER_ID1 = 0;
private static final int MOCK_USER_ID2 = 1;
+ private static final int MOCK_USER_ID3 = 2;
private static final UserHandle MOCK_USER1 = UserHandle.of(MOCK_USER_ID1);
private static final UserHandle MOCK_USER2 = UserHandle.of(MOCK_USER_ID2);
+ private static final UserHandle MOCK_USER3 = UserHandle.of(MOCK_USER_ID3);
private static final int MOCK_APPID1 = 10001;
private static final int MOCK_APPID2 = 10086;
+ private static final int MOCK_APPID3 = 10110;
private static final int SYSTEM_APPID1 = 1100;
private static final int SYSTEM_APPID2 = 1108;
private static final int VPN_APPID = 10002;
private static final int MOCK_UID11 = MOCK_USER1.getUid(MOCK_APPID1);
private static final int MOCK_UID12 = MOCK_USER1.getUid(MOCK_APPID2);
+ private static final int MOCK_UID13 = MOCK_USER1.getUid(MOCK_APPID3);
private static final int SYSTEM_APP_UID11 = MOCK_USER1.getUid(SYSTEM_APPID1);
private static final int VPN_UID = MOCK_USER1.getUid(VPN_APPID);
private static final int MOCK_UID21 = MOCK_USER2.getUid(MOCK_APPID1);
private static final int MOCK_UID22 = MOCK_USER2.getUid(MOCK_APPID2);
+ private static final int MOCK_UID23 = MOCK_USER2.getUid(MOCK_APPID3);
private static final int SYSTEM_APP_UID21 = MOCK_USER2.getUid(SYSTEM_APPID1);
+ private static final int MOCK_UID31 = MOCK_USER3.getUid(MOCK_APPID1);
+ private static final int MOCK_UID32 = MOCK_USER3.getUid(MOCK_APPID2);
+ private static final int MOCK_UID33 = MOCK_USER3.getUid(MOCK_APPID3);
private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
private static final String MOCK_PACKAGE1 = "appName1";
private static final String MOCK_PACKAGE2 = "appName2";
+ private static final String MOCK_PACKAGE3 = "appName3";
private static final String SYSTEM_PACKAGE1 = "sysName1";
private static final String SYSTEM_PACKAGE2 = "sysName2";
private static final String PARTITION_SYSTEM = "system";
@@ -191,6 +200,7 @@
mBpfMapMonitor = new BpfMapMonitor(mBpfNetMaps);
doReturn(List.of()).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
}
private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion,
@@ -283,6 +293,18 @@
mPermissionMonitor.onPackageAdded(packageName, uid);
}
+ private void removePackage(String packageName, int uid) {
+ final String[] oldPackages = mPackageManager.getPackagesForUid(uid);
+ // If the package isn't existed, no need to remove it.
+ if (!CollectionUtils.contains(oldPackages, packageName)) return;
+
+ // Remove the package if this uid is shared with other packages.
+ final String[] newPackages = Arrays.stream(oldPackages).filter(e -> !e.equals(packageName))
+ .toArray(String[]::new);
+ doReturn(newPackages).when(mPackageManager).getPackagesForUid(eq(uid));
+ mPermissionMonitor.onPackageRemoved(packageName, uid);
+ }
+
@Test
public void testHasPermission() {
PackageInfo app = systemPackageInfoWithPermissions();
@@ -791,6 +813,7 @@
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11);
+ doReturn(List.of(MOCK_USER1, MOCK_USER2)).when(mUserManager).getUserHandles(eq(true));
mPermissionMonitor.startMonitoring();
final Set<UidRange> vpnRange = Set.of(UidRange.createForUser(MOCK_USER1),
@@ -881,7 +904,7 @@
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
- // Install another package with the same uid and no permissions should not cause the app id
+ // Install another package with the same uid and no permissions should not cause the appId
// to lose permissions.
addPackage(MOCK_PACKAGE2, MOCK_UID11);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
@@ -1249,4 +1272,211 @@
assertTrue(isHigherNetworkPermission(PERMISSION_SYSTEM, PERMISSION_NETWORK));
assertFalse(isHigherNetworkPermission(PERMISSION_SYSTEM, PERMISSION_SYSTEM));
}
+
+ private void prepareMultiUserPackages() {
+ // MOCK_USER1 has installed 3 packages
+ // mockApp1 has no permission and share MOCK_APPID1.
+ // mockApp2 has INTERNET permission and share MOCK_APPID2.
+ // mockApp3 has UPDATE_DEVICE_STATS permission and share MOCK_APPID3.
+ final List<PackageInfo> pkgs1 = List.of(
+ buildPackageInfo("mockApp1", MOCK_UID11),
+ buildPackageInfo("mockApp2", MOCK_UID12, INTERNET),
+ buildPackageInfo("mockApp3", MOCK_UID13, UPDATE_DEVICE_STATS));
+
+ // MOCK_USER2 has installed 2 packages
+ // mockApp4 has UPDATE_DEVICE_STATS permission and share MOCK_APPID1.
+ // mockApp5 has INTERNET permission and share MOCK_APPID2.
+ final List<PackageInfo> pkgs2 = List.of(
+ buildPackageInfo("mockApp4", MOCK_UID21, UPDATE_DEVICE_STATS),
+ buildPackageInfo("mockApp5", MOCK_UID23, INTERNET));
+
+ // MOCK_USER3 has installed 1 packages
+ // mockApp6 has UPDATE_DEVICE_STATS permission and share MOCK_APPID2.
+ final List<PackageInfo> pkgs3 = List.of(
+ buildPackageInfo("mockApp6", MOCK_UID32, UPDATE_DEVICE_STATS));
+
+ doReturn(pkgs1).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID1));
+ doReturn(pkgs2).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID2));
+ doReturn(pkgs3).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID3));
+ }
+
+ private void addUserAndVerifyAppIdsPermissions(UserHandle user, int appId1Perm,
+ int appId2Perm, int appId3Perm) {
+ mPermissionMonitor.onUserAdded(user);
+ mBpfMapMonitor.expectTrafficPerm(appId1Perm, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(appId2Perm, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(appId3Perm, MOCK_APPID3);
+ }
+
+ private void removeUserAndVerifyAppIdsPermissions(UserHandle user, int appId1Perm,
+ int appId2Perm, int appId3Perm) {
+ mPermissionMonitor.onUserRemoved(user);
+ mBpfMapMonitor.expectTrafficPerm(appId1Perm, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(appId2Perm, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(appId3Perm, MOCK_APPID3);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_UserAddedRemoved() {
+ prepareMultiUserPackages();
+
+ // Add MOCK_USER1 and verify the permissions with each appIds.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER1, PERMISSION_NONE, PERMISSION_INTERNET,
+ PERMISSION_UPDATE_DEVICE_STATS);
+
+ // Add MOCK_USER2 and verify the permissions upgrade on MOCK_APPID1 & MOCK_APPID3.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_INTERNET, PERMISSION_TRAFFIC_ALL);
+
+ // Add MOCK_USER3 and verify the permissions upgrade on MOCK_APPID2.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER3, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_TRAFFIC_ALL, PERMISSION_TRAFFIC_ALL);
+
+ // Remove MOCK_USER2 and verify the permissions downgrade on MOCK_APPID1 & MOCK_APPID3.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_NONE, PERMISSION_TRAFFIC_ALL,
+ PERMISSION_UPDATE_DEVICE_STATS);
+
+ // Remove MOCK_USER1 and verify the permissions downgrade on all appIds.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER1, PERMISSION_UNINSTALLED,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_UNINSTALLED);
+
+ // Add MOCK_USER2 back and verify the permissions upgrade on MOCK_APPID1 & MOCK_APPID3.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_INTERNET);
+
+ // Remove MOCK_USER3 and verify the permissions downgrade on MOCK_APPID2.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER3, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_UNINSTALLED, PERMISSION_INTERNET);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_Multiuser_PackageAdded() throws Exception {
+ // Add two users with empty package list.
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ mPermissionMonitor.onUserAdded(MOCK_USER2);
+
+ final int[] netdPermissions = {PERMISSION_NONE, PERMISSION_INTERNET,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_TRAFFIC_ALL};
+ final String[][] grantPermissions = {new String[]{}, new String[]{INTERNET},
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{INTERNET, UPDATE_DEVICE_STATS}};
+
+ // Verify that the permission combination is expected when same appId package is installed
+ // on another user. List the expected permissions below.
+ // NONE + NONE = NONE
+ // NONE + INTERNET = INTERNET
+ // NONE + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ // NONE + ALL = ALL
+ // INTERNET + NONE = INTERNET
+ // INTERNET + INTERNET = INTERNET
+ // INTERNET + UPDATE_DEVICE_STATS = ALL
+ // INTERNET + ALL = ALL
+ // UPDATE_DEVICE_STATS + NONE = UPDATE_DEVICE_STATS
+ // UPDATE_DEVICE_STATS + INTERNET = ALL
+ // UPDATE_DEVICE_STATS + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ // UPDATE_DEVICE_STATS + ALL = ALL
+ // ALL + NONE = ALL
+ // ALL + INTERNET = ALL
+ // ALL + UPDATE_DEVICE_STATS = ALL
+ // ALL + ALL = ALL
+ for (int i = 0, num = 0; i < netdPermissions.length; i++) {
+ final int current = netdPermissions[i];
+ final String[] user1Perm = grantPermissions[i];
+ for (int j = 0; j < netdPermissions.length; j++) {
+ final int appId = MOCK_APPID1 + num;
+ final int added = netdPermissions[j];
+ final String[] user2Perm = grantPermissions[j];
+ // Add package on MOCK_USER1 and verify the permission is same as package granted.
+ addPackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId), user1Perm);
+ mBpfMapMonitor.expectTrafficPerm(current, appId);
+
+ // Add package which share the same appId on MOCK_USER2, and verify the permission
+ // has combined.
+ addPackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId), user2Perm);
+ mBpfMapMonitor.expectTrafficPerm((current | added), appId);
+ num++;
+ }
+ }
+ }
+
+ private void verifyAppIdPermissionsAfterPackageRemoved(int appId, int expectedPerm,
+ String[] user1Perm, String[] user2Perm) throws Exception {
+ // Add package on MOCK_USER1 and verify the permission is same as package granted.
+ addPackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId), user1Perm);
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Add two packages which share the same appId and don't declare permission on
+ // MOCK_USER2. Verify the permission has no change.
+ addPackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId));
+ addPackage(MOCK_PACKAGE3, MOCK_USER2.getUid(appId), user2Perm);
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Remove one packages from MOCK_USER2. Verify the permission has no change too.
+ removePackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Remove last packages from MOCK_USER2. Verify the permission has still no change.
+ removePackage(MOCK_PACKAGE3, MOCK_USER2.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_Multiuser_PackageRemoved() throws Exception {
+ // Add two users with empty package list.
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ mPermissionMonitor.onUserAdded(MOCK_USER2);
+
+ int appId = MOCK_APPID1;
+ // Verify that the permission combination is expected when same appId package is removed on
+ // another user. List the expected permissions below.
+ /***** NONE *****/
+ // NONE + NONE = NONE
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_NONE, new String[]{}, new String[]{});
+
+ /***** INTERNET *****/
+ // INTERNET + NONE = INTERNET
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_INTERNET, new String[]{INTERNET}, new String[]{});
+
+ // INTERNET + INTERNET = INTERNET
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_INTERNET, new String[]{INTERNET}, new String[]{INTERNET});
+
+ /***** UPDATE_DEVICE_STATS *****/
+ // UPDATE_DEVICE_STATS + NONE = UPDATE_DEVICE_STATS
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_UPDATE_DEVICE_STATS,
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{});
+
+ // UPDATE_DEVICE_STATS + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_UPDATE_DEVICE_STATS,
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{UPDATE_DEVICE_STATS});
+
+ /***** ALL *****/
+ // ALL + NONE = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{});
+
+ // ALL + INTERNET = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{INTERNET});
+
+ // ALL + UPDATE_DEVICE_STATS = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{UPDATE_DEVICE_STATS});
+
+ // ALL + ALL = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS},
+ new String[]{INTERNET, UPDATE_DEVICE_STATS});
+
+ /***** UNINSTALL *****/
+ // UNINSTALL + UNINSTALL = UNINSTALL
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId, PERMISSION_NONE, new String[]{}, new String[]{});
+ removePackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, appId);
+ }
}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index 4d3e4d3..dfb4fcc 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -20,9 +20,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -41,8 +41,8 @@
import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
-import android.net.EthernetNetworkSpecifier;
import android.net.EthernetNetworkManagementException;
+import android.net.EthernetNetworkSpecifier;
import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IpConfiguration;
import android.net.LinkAddress;
@@ -59,14 +59,11 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.test.TestLooper;
-import android.util.Pair;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.connectivity.resources.R;
import com.android.net.module.util.InterfaceParams;
-
import com.android.testutils.DevSdkIgnoreRule;
import org.junit.After;
@@ -79,6 +76,7 @@
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -362,7 +360,7 @@
assertFalse(ret);
verifyNoStopOrStart();
- listener.expectOnErrorWithMessage("can't be updated as it is not available");
+ listener.expectOnError();
}
@Test
@@ -376,7 +374,7 @@
assertFalse(ret);
verifyNoStopOrStart();
- listener.expectOnErrorWithMessage("No changes");
+ listener.expectOnError();
}
@Test
@@ -626,8 +624,6 @@
private static final class TestNetworkManagementListener
implements INetworkInterfaceOutcomeReceiver {
private final CompletableFuture<String> mResult = new CompletableFuture<>();
- private final CompletableFuture<EthernetNetworkManagementException> mError =
- new CompletableFuture<>();
@Override
public void onResult(@NonNull String iface) {
@@ -636,19 +632,21 @@
@Override
public void onError(@NonNull EthernetNetworkManagementException exception) {
- mError.complete(exception);
+ mResult.completeExceptionally(exception);
}
String expectOnResult() throws Exception {
return mResult.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
- EthernetNetworkManagementException expectOnError() throws Exception {
- return mError.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- }
-
- void expectOnErrorWithMessage(String msg) throws Exception {
- assertTrue(expectOnError().getMessage().contains(msg));
+ void expectOnError() throws Exception {
+ assertThrows(EthernetNetworkManagementException.class, () -> {
+ try {
+ mResult.get();
+ } catch (ExecutionException e) {
+ throw e.getCause();
+ }
+ });
}
@Override
@@ -723,7 +721,7 @@
mNetFactory.updateInterface(iface, ipConfiguration, capabilities, failedListener);
interruptingRunnable.run();
- failedListener.expectOnErrorWithMessage("aborted");
+ failedListener.expectOnError();
}
@Test
@@ -754,7 +752,7 @@
mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
verifyNoStopOrStart();
- listener.expectOnErrorWithMessage("can't be updated as it is not available");
+ listener.expectOnError();
}
@Test