Snap for 7379850 from bcfe90a6b1f1e1cb56bfbee5d8d77513232e5483 to sc-v2-release

Change-Id: If4ecb7906b48f92d1d53502f674d9ef387080b73
diff --git a/res/values/config.xml b/res/values/config.xml
index d6a11ab..805ca04 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -105,4 +105,20 @@
          increased until reaching the config_max_retry_timer. -->
     <integer name="config_evaluating_bandwidth_min_retry_timer_ms"></integer>
     <integer name="config_evaluating_bandwidth_max_retry_timer_ms"></integer>
+
+    <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
+         Those frames are identified by the field Eth-type having values
+         less than 0x600 -->
+    <bool name="config_apfDrop802_3Frames">true</bool>
+
+    <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
+         will be dropped
+         TODO: need to put proper values, these are for testing purposes only -->
+    <integer-array name="config_apfEthTypeDenyList">
+        <item>0x88A2</item>
+        <item>0x88A4</item>
+        <item>0x88B8</item>
+        <item>0x88CD</item>
+        <item>0x88E3</item>
+    </integer-array>
 </resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index b2967b9..bfb450e 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -77,6 +77,13 @@
             <item type="integer" name="config_evaluating_bandwidth_timeout_ms"/>
             <item type="integer" name="config_evaluating_bandwidth_min_retry_timer_ms"/>
             <item type="integer" name="config_evaluating_bandwidth_max_retry_timer_ms"/>
+
+            <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
+            Those frames are identified by the field Eth-type having values less than 0x600 -->
+            <item type="bool" name="config_apfDrop802_3Frames"/>
+            <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
+            will be dropped -->
+            <item type="array" name="config_apfEthTypeDenyList"/>
         </policy>
     </overlayable>
 </resources>
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index f7c728b..a57e99d 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -18,6 +18,7 @@
 
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
+import static android.net.util.NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION;
 import static android.net.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION;
 import static android.net.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION;
 import static android.net.util.SocketUtils.makePacketSocketAddress;
@@ -35,6 +36,7 @@
 import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.DhcpResults;
 import android.net.INetd;
@@ -93,6 +95,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.networkstack.R;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
 import com.android.networkstack.apishim.SocketUtilsShimImpl;
 import com.android.networkstack.apishim.common.NetworkInformationShim;
@@ -527,7 +530,7 @@
     private boolean mMulticastFiltering;
     private long mStartTimeMillis;
     private MacAddress mCurrentBssid;
-    private boolean mHasDisabledIPv6OnProvLoss;
+    private boolean mHasDisabledIpv6OrAcceptRaOnProvLoss;
 
     /**
      * Reading the snapshot is an asynchronous operation initiated by invoking
@@ -604,6 +607,16 @@
             return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
                     defaultEnabled);
         }
+
+        /**
+         * Create an APF filter if apfCapabilities indicates support for packet filtering using
+         * APF programs.
+         * @see ApfFilter#maybeCreate
+         */
+        public ApfFilter maybeCreateApfFilter(Context context, ApfFilter.ApfConfiguration config,
+                InterfaceParams ifParams, IpClientCallbacksWrapper cb) {
+            return ApfFilter.maybeCreate(context, config, ifParams, cb);
+        }
     }
 
     public IpClient(Context context, String ifName, IIpClientCallbacks callback,
@@ -881,6 +894,11 @@
         }
     }
 
+    private boolean shouldDisableAcceptRaOnProvisioningLoss() {
+        return mDependencies.isFeatureEnabled(mContext, IPCLIENT_DISABLE_ACCEPT_RA_VERSION,
+                true /* defaultEnabled */);
+    }
+
     @Override
     protected void onQuitting() {
         mCallback.onQuit();
@@ -1198,6 +1216,21 @@
         return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
     }
 
+    private void setIpv6AcceptRa(int acceptRa) {
+        try {
+            mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mInterfaceParams.name, "accept_ra",
+                    Integer.toString(acceptRa));
+        } catch (Exception e) {
+            Log.e(mTag, "Failed to set accept_ra to " + acceptRa);
+        }
+    }
+
+    private void restartIpv6WithAcceptRaDisabled() {
+        mInterfaceCtrl.disableIPv6();
+        setIpv6AcceptRa(0 /* accept_ra */);
+        startIPv6();
+    }
+
     // TODO: Investigate folding all this into the existing static function
     // LinkProperties.compareProvisioning() or some other single function that
     // takes two LinkProperties objects and returns a ProvisioningChange
@@ -1247,7 +1280,7 @@
         // Note that we can still be disconnected by IpReachabilityMonitor
         // if the IPv6 default gateway (but not the IPv6 DNS servers; see
         // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIPv6OnProvLoss
+        final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIpv6OrAcceptRaOnProvLoss
                 || (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
                         && !mCm.shouldAvoidBadWifi());
 
@@ -1275,18 +1308,31 @@
         if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
             // Although link properties have lost IPv6 default route in this case, if IPv4 is still
             // working with appropriate routes and DNS servers, we can keep the current connection
-            // without disconnecting from the network, just disable IPv6 on that given network until
-            // to the next provisioning. Disabling IPv6 will result in all IPv6 connectivity torn
-            // down and all IPv6 sockets being closed, the non-routable IPv6 DNS servers will be
-            // stripped out, so applications will be able to reconnect immediately over IPv4. See
-            // b/131781810.
+            // without disconnecting from the network, just disable IPv6 or accept_ra parameter on
+            // that given network until to the next provisioning.
+            //
+            // Disabling IPv6 stack will result in all IPv6 connectivity torn down and all IPv6
+            // sockets being closed, the non-routable IPv6 DNS servers will be stripped out, so
+            // applications will be able to reconnect immediately over IPv4. See b/131781810.
+            //
+            // Sometimes disabling IPv6 stack might introduce other issues(see b/179222860),
+            // instead disabling accept_ra will result in only IPv4 provisioning and IPv6 link
+            // local address left on the interface, so applications will be able to reconnect
+            // immediately over IPv4 and keep IPv6 link-local capable.
             if (newLp.isIpv4Provisioned()) {
-                mInterfaceCtrl.disableIPv6();
+                if (shouldDisableAcceptRaOnProvisioningLoss()) {
+                    restartIpv6WithAcceptRaDisabled();
+                } else {
+                    mInterfaceCtrl.disableIPv6();
+                }
                 mNetworkQuirkMetrics.setEvent(NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST);
                 mNetworkQuirkMetrics.statsWrite();
-                mHasDisabledIPv6OnProvLoss = true;
+                mHasDisabledIpv6OrAcceptRaOnProvLoss = true;
                 delta = PROV_CHANGE_STILL_PROVISIONED;
-                mLog.log("Disable IPv6 stack completely when the default router has gone");
+                mLog.log(shouldDisableAcceptRaOnProvisioningLoss()
+                        ? "Disabled accept_ra parameter "
+                        : "Disabled IPv6 stack completely "
+                        + "when the IPv6 default router has gone");
             } else {
                 delta = PROV_CHANGE_LOST_PROVISIONING;
             }
@@ -1839,7 +1885,8 @@
         @Override
         public void enter() {
             stopAllIP();
-            mHasDisabledIPv6OnProvLoss = false;
+            setIpv6AcceptRa(2 /* accept_ra */);
+            mHasDisabledIpv6OrAcceptRaOnProvLoss = false;
             mGratuitousNaTargetAddresses.clear();
 
             mLinkObserver.clearInterfaceParams();
@@ -2160,10 +2207,19 @@
             apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
             apfConfig.multicastFilter = mMulticastFiltering;
             // Get the Configuration for ApfFilter from Context
-            apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames();
-            apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList();
+            // Resource settings were moved from ApfCapabilities APIs to NetworkStack resources in S
+            if (ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R)) {
+                final Resources res = mContext.getResources();
+                apfConfig.ieee802_3Filter = res.getBoolean(R.bool.config_apfDrop802_3Frames);
+                apfConfig.ethTypeBlackList = res.getIntArray(R.array.config_apfEthTypeDenyList);
+            } else {
+                apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames();
+                apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList();
+            }
+
             apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec;
-            mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
+            mApfFilter = mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams,
+                    mCallback);
             // TODO: investigate the effects of any multicast filtering racing/interfering with the
             // rest of this IP configuration startup.
             if (mApfFilter == null) {
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 098ce2e..81d0c08 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -249,6 +249,12 @@
     public static final String IPCLIENT_GARP_NA_ROAMING_VERSION =
             "ipclient_garp_na_roaming_version";
 
+    /**
+     * Experiment flag to disable accept_ra parameter when IPv6 provisioning loss happens due to
+     * the default route has gone.
+     */
+    public static final String IPCLIENT_DISABLE_ACCEPT_RA_VERSION = "ipclient_disable_accept_ra";
+
     static {
         System.loadLibrary("networkstackutilsjni");
     }
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index f3419a3..c121b3d 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -2404,12 +2404,15 @@
         reset(mCb);
     }
 
-    private void doDualStackProvisioning() throws Exception {
+    private void doDualStackProvisioning(boolean shouldDisableAcceptRa) throws Exception {
         when(mCm.shouldAvoidBadWifi()).thenReturn(true);
 
         final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
                 .build();
+
+        setFeatureEnabled(NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION,
+                shouldDisableAcceptRa);
         // Enable rapid commit to accelerate DHCP handshake to shorten test duration,
         // not strictly necessary.
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
@@ -2419,9 +2422,9 @@
         performDualStackProvisioning();
     }
 
-    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
-    public void testIgnoreIpv6ProvisioningLoss() throws Exception {
-        doDualStackProvisioning();
+    @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck")
+    public void testIgnoreIpv6ProvisioningLoss_disableIPv6Stack() throws Exception {
+        doDualStackProvisioning(false /* shouldDisableAcceptRa */);
 
         final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
 
@@ -2450,9 +2453,45 @@
                 (long) NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST.ordinal());
     }
 
+    @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck")
+    public void testIgnoreIpv6ProvisioningLoss_disableAcceptRa() throws Exception {
+        doDualStackProvisioning(true /* shouldDisableAcceptRa */);
+
+        final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+
+        // Send RA with 0-lifetime and wait until all global IPv6 addresses, IPv6-related default
+        // route and DNS servers have been removed, then verify if there is IPv4-only, IPv6 link
+        // local address and route to fe80::/64 info left in the LinkProperties.
+        sendRouterAdvertisementWithZeroLifetime();
+        verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(
+                argThat(x -> {
+                    // Only IPv4 provisioned and IPv6 link-local address
+                    final boolean isIPv6LinkLocalAndIPv4OnlyProvisioned =
+                            (x.getLinkAddresses().size() == 2
+                                    && x.getDnsServers().size() == 1
+                                    && x.getAddresses().get(0) instanceof Inet4Address
+                                    && x.getDnsServers().get(0) instanceof Inet4Address);
+
+                    if (!isIPv6LinkLocalAndIPv4OnlyProvisioned) return false;
+                    lpFuture.complete(x);
+                    return true;
+                }));
+        final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertNotNull(lp);
+        assertEquals(lp.getAddresses().get(0), CLIENT_ADDR);
+        assertEquals(lp.getDnsServers().get(0), SERVER_ADDR);
+        assertTrue(lp.getAddresses().get(1).isLinkLocalAddress());
+
+        reset(mCb);
+
+        // Send an RA to verify that global IPv6 addresses won't be configured on the interface.
+        sendBasicRouterAdvertisement(false /* waitForRs */);
+        verify(mCb, timeout(TEST_TIMEOUT_MS).times(0)).onLinkPropertiesChange(any());
+    }
+
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDualStackProvisioning() throws Exception {
-        doDualStackProvisioning();
+        doDualStackProvisioning(false /* shouldDisableAcceptRa */);
 
         verify(mCb, never()).onProvisioningFailure(any());
     }
@@ -2672,7 +2711,7 @@
     public void testNoFdLeaks() throws Exception {
         // Shut down and restart IpClient once to ensure that any fds that are opened the first
         // time it runs do not cause the test to fail.
-        doDualStackProvisioning();
+        doDualStackProvisioning(false /* shouldDisableAcceptRa */);
         shutdownAndRecreateIpClient();
 
         // Unfortunately we cannot use a large number of iterations as it would make the test run
@@ -2680,7 +2719,7 @@
         final int iterations = 10;
         final int before = getNumOpenFds();
         for (int i = 0; i < iterations; i++) {
-            doDualStackProvisioning();
+            doDualStackProvisioning(false /* shouldDisableAcceptRa */);
             shutdownAndRecreateIpClient();
             // The last time this loop runs, mIpc will be shut down in tearDown.
         }
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index e991ea7..7260332 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -18,12 +18,14 @@
 
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -48,22 +50,30 @@
 import android.net.MacAddress;
 import android.net.NetworkStackIpMemoryStore;
 import android.net.RouteInfo;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter.ApfConfiguration;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.InitialConfiguration;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.util.InterfaceParams;
+import android.os.Build;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.networkstack.R;
 import com.android.server.NetworkObserver;
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService;
 import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.HandlerUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -85,6 +95,9 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IpClientTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     private static final String VALID = "VALID";
     private static final String INVALID = "INVALID";
     private static final String TEST_IFNAME = "test_wlan0";
@@ -631,6 +644,71 @@
         return out;
     }
 
+    private ApfConfiguration verifyApfFilterCreatedOnStart(IpClient ipc) {
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIPv4()
+                .withoutIpReachabilityMonitor()
+                .withInitialConfiguration(
+                        conf(links(TEST_LOCAL_ADDRESSES), prefixes(TEST_PREFIXES), ips()))
+                .withApfCapabilities(new ApfCapabilities(
+                        4 /* version */, 4096 /* maxProgramSize */, 4 /* format */))
+                .build();
+
+        ipc.startProvisioning(config);
+        final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass(
+                ApfConfiguration.class);
+        verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
+                any(), configCaptor.capture(), any(), any());
+
+        return configCaptor.getValue();
+    }
+
+    @Test @IgnoreAfter(Build.VERSION_CODES.R)
+    public void testApfConfiguration_R() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc);
+
+        assertEquals(ApfCapabilities.getApfDrop8023Frames(), config.ieee802_3Filter);
+        assertArrayEquals(ApfCapabilities.getApfEtherTypeBlackList(), config.ethTypeBlackList);
+
+        verify(mResources, never()).getBoolean(R.bool.config_apfDrop802_3Frames);
+        verify(mResources, never()).getIntArray(R.array.config_apfEthTypeDenyList);
+
+        verifyShutdown(ipc);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testApfConfiguration() throws Exception {
+        doReturn(true).when(mResources).getBoolean(R.bool.config_apfDrop802_3Frames);
+        final int[] ethTypeDenyList = new int[] { 0x88A2, 0x88A4 };
+        doReturn(ethTypeDenyList).when(mResources).getIntArray(
+                R.array.config_apfEthTypeDenyList);
+
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc);
+
+        assertTrue(config.ieee802_3Filter);
+        assertArrayEquals(ethTypeDenyList, config.ethTypeBlackList);
+
+        verifyShutdown(ipc);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testApfConfiguration_NoApfDrop8023Frames() throws Exception {
+        doReturn(false).when(mResources).getBoolean(R.bool.config_apfDrop802_3Frames);
+        final int[] ethTypeDenyList = new int[] { 0x88A3, 0x88A5 };
+        doReturn(ethTypeDenyList).when(mResources).getIntArray(
+                R.array.config_apfEthTypeDenyList);
+
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc);
+
+        assertFalse(config.ieee802_3Filter);
+        assertArrayEquals(ethTypeDenyList, config.ethTypeBlackList);
+
+        verifyShutdown(ipc);
+    }
+
     interface Fn<A,B> {
         B call(A a) throws Exception;
     }