Merge changes Ia677f31a,I8d8f5752,Idc347bce into main
* changes:
Add abstract getValue() for class SvcParam
Use DnsPacket.toString() for DnsSvcbPacket
Test: A minor fix for testDnsSvcbRecord_svcParamMandatory
diff --git a/framework/src/android/net/QosSession.java b/framework/src/android/net/QosSession.java
index 25f3965..d1edae9 100644
--- a/framework/src/android/net/QosSession.java
+++ b/framework/src/android/net/QosSession.java
@@ -22,6 +22,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Provides identifying information of a QoS session. Sent to an application through
* {@link QosCallback}.
@@ -107,6 +110,7 @@
TYPE_EPS_BEARER,
TYPE_NR_BEARER,
})
+ @Retention(RetentionPolicy.SOURCE)
@interface QosSessionType {}
private QosSession(final Parcel in) {
diff --git a/nearby/framework/java/android/nearby/BroadcastRequest.java b/nearby/framework/java/android/nearby/BroadcastRequest.java
index 90f4d0f..6d6357d 100644
--- a/nearby/framework/java/android/nearby/BroadcastRequest.java
+++ b/nearby/framework/java/android/nearby/BroadcastRequest.java
@@ -88,6 +88,7 @@
* @hide
*/
@IntDef({MEDIUM_BLE})
+ @Retention(RetentionPolicy.SOURCE)
public @interface Medium {}
/**
diff --git a/nearby/framework/java/android/nearby/NearbyDevice.java b/nearby/framework/java/android/nearby/NearbyDevice.java
index e8fcc28..e7db0c5 100644
--- a/nearby/framework/java/android/nearby/NearbyDevice.java
+++ b/nearby/framework/java/android/nearby/NearbyDevice.java
@@ -25,6 +25,8 @@
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -149,6 +151,7 @@
* @hide
*/
@IntDef({Medium.BLE, Medium.BLUETOOTH})
+ @Retention(RetentionPolicy.SOURCE)
public @interface Medium {
int BLE = 1;
int BLUETOOTH = 2;
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index a70b303..070a2b6 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -34,6 +34,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.WeakHashMap;
@@ -63,6 +65,7 @@
ScanStatus.SUCCESS,
ScanStatus.ERROR,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface ScanStatus {
// The undetermined status, some modules may be initializing. Retry is suggested.
int UNKNOWN = 0;
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 bb32052..198b009 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
@@ -62,6 +62,8 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.Nullable;
+
import com.android.compatibility.common.util.AmUtils;
import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.DeviceConfigStateHelper;
@@ -283,8 +285,30 @@
}
protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
+ assertBackgroundNetworkAccess(expectAllowed, null);
+ }
+
+ /**
+ * Asserts whether the active network is available or not for the background app. If the network
+ * is unavailable, also checks whether it is blocked by the expected error.
+ *
+ * @param expectAllowed expect background network access to be allowed or not.
+ * @param expectedUnavailableError the expected error when {@code expectAllowed} is false. It's
+ * meaningful only when the {@code expectAllowed} is 'false'.
+ * Throws an IllegalArgumentException when {@code expectAllowed}
+ * is true and this parameter is not null. When the
+ * {@code expectAllowed} is 'false' and this parameter is null,
+ * this function does not compare error type of the networking
+ * access failure.
+ */
+ protected void assertBackgroundNetworkAccess(boolean expectAllowed,
+ @Nullable final String expectedUnavailableError) throws Exception {
assertBackgroundState();
- assertNetworkAccess(expectAllowed /* expectAvailable */, false /* needScreenOn */);
+ if (expectAllowed && expectedUnavailableError != null) {
+ throw new IllegalArgumentException("expectedUnavailableError is not null");
+ }
+ assertNetworkAccess(expectAllowed /* expectAvailable */, false /* needScreenOn */,
+ expectedUnavailableError);
}
protected void assertForegroundNetworkAccess() throws Exception {
@@ -407,12 +431,17 @@
*/
private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn)
throws Exception {
+ assertNetworkAccess(expectAvailable, needScreenOn, null);
+ }
+
+ private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn,
+ @Nullable final String expectedUnavailableError) throws Exception {
final int maxTries = 5;
String error = null;
int timeoutMs = 500;
for (int i = 1; i <= maxTries; i++) {
- error = checkNetworkAccess(expectAvailable);
+ error = checkNetworkAccess(expectAvailable, expectedUnavailableError);
if (error == null) return;
@@ -479,12 +508,15 @@
*
* @return error message with the mismatch (or empty if assertion passed).
*/
- private String checkNetworkAccess(boolean expectAvailable) throws Exception {
+ private String checkNetworkAccess(boolean expectAvailable,
+ @Nullable final String expectedUnavailableError) throws Exception {
final String resultData = mServiceClient.checkNetworkStatus();
- return checkForAvailabilityInResultData(resultData, expectAvailable);
+ return checkForAvailabilityInResultData(resultData, expectAvailable,
+ expectedUnavailableError);
}
- private String checkForAvailabilityInResultData(String resultData, boolean expectAvailable) {
+ private String checkForAvailabilityInResultData(String resultData, boolean expectAvailable,
+ @Nullable final String expectedUnavailableError) {
if (resultData == null) {
assertNotNull("Network status from app2 is null", resultData);
}
@@ -516,6 +548,10 @@
if (expectedState != state || expectedDetailedState != detailedState) {
errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
expectedState, expectedDetailedState, state, detailedState));
+ } else if (!expectAvailable && (expectedUnavailableError != null)
+ && !connectionCheckDetails.contains(expectedUnavailableError)) {
+ errors.append("Connection unavailable reason mismatch: expected "
+ + expectedUnavailableError + "\n");
}
if (errors.length() > 0) {
@@ -914,7 +950,7 @@
final String resultData = result.get(0).second;
if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
final String error = checkForAvailabilityInResultData(
- resultData, expectAvailable);
+ resultData, expectAvailable, null /* expectedUnavailableError */);
if (error != null) {
fail("Network is not available for activity in app2 (" + mUid + "): "
+ error);
@@ -949,7 +985,7 @@
final String resultData = result.get(0).second;
if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
final String error = checkForAvailabilityInResultData(
- resultData, expectAvailable);
+ resultData, expectAvailable, null /* expectedUnavailableError */);
if (error != null) {
Log.d(TAG, "Network state is unexpected, checking again. " + error);
// Right now we could end up in an unexpected state if expedited job
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index ab3cf14..82f4a65 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -32,8 +32,11 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.cts.util.CtsNetUtils;
import android.util.Log;
+import com.android.modules.utils.build.SdkLevel;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -46,6 +49,9 @@
public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
private Network mNetwork;
private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
+ private CtsNetUtils mCtsNetUtils;
+ private static final String GOOGLE_PRIVATE_DNS_SERVER = "dns.google";
+
@Rule
public final MeterednessConfigurationRule mMeterednessConfiguration
= new MeterednessConfigurationRule();
@@ -218,6 +224,26 @@
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
false /* hasCapability */, NET_CAPABILITY_NOT_METERED);
mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Before Android T, DNS queries over private DNS should be but are not restricted by Power
+ // Saver or Data Saver. The issue is fixed in mainline update and apps can no longer request
+ // DNS queries when its network is restricted by Power Saver. The fix takes effect backwards
+ // starting from Android T. But for Data Saver, the fix is not backward compatible since
+ // there are some platform changes involved. It is only available on devices that a specific
+ // trunk flag is enabled.
+ //
+ // This test can not only verify that the network traffic from apps is blocked at the right
+ // time, but also verify whether it is correctly blocked at the DNS stage, or at a later
+ // socket connection stage.
+ if (SdkLevel.isAtLeastT()) {
+ // Enable private DNS
+ mCtsNetUtils = new CtsNetUtils(mContext);
+ mCtsNetUtils.storePrivateDnsSetting();
+ mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
+ mCtsNetUtils.awaitPrivateDnsSetting(
+ "NetworkCallbackTest wait private DNS setting timeout", mNetwork,
+ GOOGLE_PRIVATE_DNS_SERVER, true);
+ }
}
@After
@@ -227,6 +253,10 @@
setRestrictBackground(false);
setBatterySaverMode(false);
unregisterNetworkCallback();
+
+ if (SdkLevel.isAtLeastT() && (mCtsNetUtils != null)) {
+ mCtsNetUtils.restorePrivateDnsSetting();
+ }
}
@RequiredProperties({DATA_SAVER_MODE})
@@ -235,6 +265,8 @@
try {
// Enable restrict background
setRestrictBackground(true);
+ // TODO: Verify expectedUnavailableError when aconfig support mainline.
+ // (see go/aconfig-in-mainline-problems)
assertBackgroundNetworkAccess(false);
assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
@@ -247,6 +279,7 @@
// Remove from whitelist
removeRestrictBackgroundWhitelist(mUid);
+ // TODO: Verify expectedUnavailableError when aconfig support mainline.
assertBackgroundNetworkAccess(false);
assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
@@ -278,7 +311,11 @@
try {
// Enable Power Saver
setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
+ if (SdkLevel.isAtLeastT()) {
+ assertBackgroundNetworkAccess(false, "java.net.UnknownHostException");
+ } else {
+ assertBackgroundNetworkAccess(false);
+ }
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
@@ -298,7 +335,11 @@
try {
// Enable Power Saver
setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
+ if (SdkLevel.isAtLeastT()) {
+ assertBackgroundNetworkAccess(false, "java.net.UnknownHostException");
+ } else {
+ assertBackgroundNetworkAccess(false);
+ }
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 48cfe77..ff801e5 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -56,11 +56,9 @@
import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV4_UDP;
import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_ESP;
import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_UDP;
-import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
import static com.android.testutils.MiscAsserts.assertThrows;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -149,7 +147,6 @@
import android.net.wifi.WifiInfo;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
-import android.os.ConditionVariable;
import android.os.INetworkManagementService;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -1983,22 +1980,6 @@
// a subsequent CL.
}
- @Test
- public void testStartLegacyVpnIpv6() throws Exception {
- setMockedUsers(PRIMARY_USER);
- final Vpn vpn = createVpn(PRIMARY_USER.id);
- final LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(EGRESS_IFACE);
- lp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
- final RouteInfo defaultRoute = new RouteInfo(
- new IpPrefix(Inet6Address.ANY, 0), null, EGRESS_IFACE);
- lp.addRoute(defaultRoute);
-
- // IllegalStateException thrown since legacy VPN only supports IPv4.
- assertThrows(IllegalStateException.class,
- () -> vpn.startLegacyVpn(mVpnProfile, EGRESS_NETWORK, lp));
- }
-
private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
setMockedUsers(PRIMARY_USER);
@@ -3112,23 +3093,15 @@
}
@Test
- public void testStartRacoonNumericAddress() throws Exception {
- startRacoon("1.2.3.4", "1.2.3.4");
- }
+ public void testStartLegacyVpnType() throws Exception {
+ setMockedUsers(PRIMARY_USER);
+ final Vpn vpn = createVpn(PRIMARY_USER.id);
+ final VpnProfile profile = new VpnProfile("testProfile" /* key */);
- @Test
- public void testStartRacoonHostname() throws Exception {
- startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve
- }
-
- @Test
- public void testStartPptp() throws Exception {
- startPptp(true /* useMppe */);
- }
-
- @Test
- public void testStartPptp_NoMppe() throws Exception {
- startPptp(false /* useMppe */);
+ profile.type = VpnProfile.TYPE_PPTP;
+ assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile));
+ profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK;
+ assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile));
}
private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
@@ -3138,125 +3111,6 @@
assertEquals(type, ti.getType());
}
- private void startPptp(boolean useMppe) throws Exception {
- final VpnProfile profile = new VpnProfile("testProfile" /* key */);
- profile.type = VpnProfile.TYPE_PPTP;
- profile.name = "testProfileName";
- profile.username = "userName";
- profile.password = "thePassword";
- profile.server = "192.0.2.123";
- profile.mppe = useMppe;
-
- doReturn(new Network[] { new Network(101) }).when(mConnectivityManager).getAllNetworks();
- doReturn(new Network(102)).when(mConnectivityManager).registerNetworkAgent(
- any(), // INetworkAgent
- any(), // NetworkInfo
- any(), // LinkProperties
- any(), // NetworkCapabilities
- any(), // LocalNetworkConfig
- any(), // NetworkScore
- any(), // NetworkAgentConfig
- anyInt()); // provider ID
-
- final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile);
- final TestDeps deps = (TestDeps) vpn.mDeps;
-
- testAndCleanup(() -> {
- final String[] mtpdArgs = deps.mtpdArgs.get(10, TimeUnit.SECONDS);
- final String[] argsPrefix = new String[]{
- EGRESS_IFACE, "pptp", profile.server, "1723", "name", profile.username,
- "password", profile.password, "linkname", "vpn", "refuse-eap", "nodefaultroute",
- "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270"
- };
- assertArrayEquals(argsPrefix, Arrays.copyOf(mtpdArgs, argsPrefix.length));
- if (useMppe) {
- assertEquals(argsPrefix.length + 2, mtpdArgs.length);
- assertEquals("+mppe", mtpdArgs[argsPrefix.length]);
- assertEquals("-pap", mtpdArgs[argsPrefix.length + 1]);
- } else {
- assertEquals(argsPrefix.length + 1, mtpdArgs.length);
- assertEquals("nomppe", mtpdArgs[argsPrefix.length]);
- }
-
- verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(
- any(), // INetworkAgent
- any(), // NetworkInfo
- any(), // LinkProperties
- any(), // NetworkCapabilities
- any(), // LocalNetworkConfig
- any(), // NetworkScore
- any(), // NetworkAgentConfig
- anyInt()); // provider ID
- }, () -> { // Cleanup
- vpn.mVpnRunner.exitVpnRunner();
- deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier
- vpn.mVpnRunner.join(10_000); // wait for up to 10s for the runner to die and cleanup
- });
- }
-
- public void startRacoon(final String serverAddr, final String expectedAddr)
- throws Exception {
- final ConditionVariable legacyRunnerReady = new ConditionVariable();
- final VpnProfile profile = new VpnProfile("testProfile" /* key */);
- profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK;
- profile.name = "testProfileName";
- profile.username = "userName";
- profile.password = "thePassword";
- profile.server = serverAddr;
- profile.ipsecIdentifier = "id";
- profile.ipsecSecret = "secret";
- profile.l2tpSecret = "l2tpsecret";
-
- when(mConnectivityManager.getAllNetworks())
- .thenReturn(new Network[] { new Network(101) });
-
- when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(),
- any(), any(), any(), anyInt())).thenAnswer(invocation -> {
- // The runner has registered an agent and is now ready.
- legacyRunnerReady.open();
- return new Network(102);
- });
- final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile);
- final TestDeps deps = (TestDeps) vpn.mDeps;
- try {
- // udppsk and 1701 are the values for TYPE_L2TP_IPSEC_PSK
- assertArrayEquals(
- new String[] { EGRESS_IFACE, expectedAddr, "udppsk",
- profile.ipsecIdentifier, profile.ipsecSecret, "1701" },
- deps.racoonArgs.get(10, TimeUnit.SECONDS));
- // literal values are hardcoded in Vpn.java for mtpd args
- assertArrayEquals(
- new String[] { EGRESS_IFACE, "l2tp", expectedAddr, "1701", profile.l2tpSecret,
- "name", profile.username, "password", profile.password,
- "linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns",
- "idle", "1800", "mtu", "1270", "mru", "1270" },
- deps.mtpdArgs.get(10, TimeUnit.SECONDS));
-
- // Now wait for the runner to be ready before testing for the route.
- ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
- ArgumentCaptor<NetworkCapabilities> ncCaptor =
- ArgumentCaptor.forClass(NetworkCapabilities.class);
- verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
- lpCaptor.capture(), ncCaptor.capture(), any(), any(), any(), anyInt());
-
- // In this test the expected address is always v4 so /32.
- // Note that the interface needs to be specified because RouteInfo objects stored in
- // LinkProperties objects always acquire the LinkProperties' interface.
- final RouteInfo expectedRoute = new RouteInfo(new IpPrefix(expectedAddr + "/32"),
- null, EGRESS_IFACE, RouteInfo.RTN_THROW);
- final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes();
- assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes,
- actualRoutes.contains(expectedRoute));
-
- assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY);
- } finally {
- // Now interrupt the thread, unblock the runner and clean up.
- vpn.mVpnRunner.exitVpnRunner();
- deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier
- vpn.mVpnRunner.join(10_000); // wait for up to 10s for the runner to die and cleanup
- }
- }
-
// Make it public and un-final so as to spy it
public class TestDeps extends Vpn.Dependencies {
public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
diff --git a/thread/service/java/com/android/server/thread/InfraInterfaceController.java b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
new file mode 100644
index 0000000..d7c49a0
--- /dev/null
+++ b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.thread;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+
+/** Controller for the infrastructure network interface. */
+public class InfraInterfaceController {
+ private static final String TAG = "InfraIfController";
+
+ static {
+ System.loadLibrary("service-thread-jni");
+ }
+
+ /**
+ * Creates a socket on the infrastructure network interface for sending/receiving ICMPv6
+ * Neighbor Discovery messages.
+ *
+ * @param infraInterfaceName the infrastructure network interface name.
+ * @return an ICMPv6 socket file descriptor on the Infrastructure network interface.
+ * @throws IOException when fails to create the socket.
+ */
+ public static ParcelFileDescriptor createIcmp6Socket(String infraInterfaceName)
+ throws IOException {
+ return ParcelFileDescriptor.adoptFd(nativeCreateIcmp6Socket(infraInterfaceName));
+ }
+
+ private static native int nativeCreateIcmp6Socket(String interfaceName) throws IOException;
+}
diff --git a/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
new file mode 100644
index 0000000..5d24eab
--- /dev/null
+++ b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "jniThreadInfra"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <inttypes.h>
+#include <linux/if_arp.h>
+#include <linux/ioctl.h>
+#include <log/log.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <private/android_filesystem_config.h>
+#include <signal.h>
+#include <spawn.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "jni.h"
+#include "nativehelper/JNIHelp.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+namespace android {
+static jint
+com_android_server_thread_InfraInterfaceController_createIcmp6Socket(JNIEnv *env, jobject clazz,
+ jstring interfaceName) {
+ ScopedUtfChars ifName(env, interfaceName);
+
+ struct icmp6_filter filter;
+ constexpr int kEnable = 1;
+ constexpr int kIpv6ChecksumOffset = 2;
+ constexpr int kHopLimit = 255;
+
+ // Initializes the ICMPv6 socket.
+ int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+ if (sock == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to create the socket (%s)",
+ strerror(errno));
+ return -1;
+ }
+
+ // Only accept Router Advertisements, Router Solicitations and Neighbor
+ // Advertisements.
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+ ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter);
+
+ if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt ICMP6_FILTER (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ // We want a source address and interface index.
+
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &kEnable, sizeof(kEnable)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVPKTINFO (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ if (setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &kIpv6ChecksumOffset,
+ sizeof(kIpv6ChecksumOffset)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_CHECKSUM (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ // We need to be able to reject RAs arriving from off-link.
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &kEnable, sizeof(kEnable)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVHOPLIMIT (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_UNICAST_HOPS (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "failed to create the setsockopt IPV6_MULTICAST_HOPS (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifName.c_str(), strlen(ifName.c_str()))) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt SO_BINDTODEVICE (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+/*
+ * JNI registration.
+ */
+
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeCreateIcmp6Socket", "(Ljava/lang/String;)I",
+ (void *)com_android_server_thread_InfraInterfaceController_createIcmp6Socket},
+};
+
+int register_com_android_server_thread_InfraInterfaceController(JNIEnv *env) {
+ return jniRegisterNativeMethods(env, "com/android/server/thread/InfraInterfaceController",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/thread/service/jni/onload.cpp b/thread/service/jni/onload.cpp
index 5081664..66add74 100644
--- a/thread/service/jni/onload.cpp
+++ b/thread/service/jni/onload.cpp
@@ -19,6 +19,7 @@
namespace android {
int register_com_android_server_thread_TunInterfaceController(JNIEnv* env);
+int register_com_android_server_thread_InfraInterfaceController(JNIEnv* env);
}
using namespace android;
@@ -33,5 +34,6 @@
ALOG_ASSERT(env != NULL, "Could not retrieve the env!");
register_com_android_server_thread_TunInterfaceController(env);
+ register_com_android_server_thread_InfraInterfaceController(env);
return JNI_VERSION_1_4;
}