Snap for 10597884 from 8d972b1737da5ff7ccc46d18aa33681537f3fd1a to mainline-networking-release

Change-Id: Ib0782edb77ecd7a3b3960786c68f66a3dc40bd44
diff --git a/OWNERS b/OWNERS
index 14d68d7..8d6e439 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,8 +1,13 @@
-# Bug component: 827526
-benedictwong@google.com
+# Bug component: 1364804
+
+amruthr@google.com #{LAST_RESORT_SUGGESTION}
+jackyu@google.com
+nagendranb@google.com
+rgreenwalt@google.com #{LAST_RESORT_SUGGESTION}
+sangyun@google.com
+
+benedictwong@google.com #{LAST_RESORT_SUGGESTION}
 evitayan@google.com
-jchalard@google.com
-lorenzo@google.com
-nharold@google.com
+nharold@google.com #{LAST_RESORT_SUGGESTION}
 
 include platform/packages/modules/common:/MODULES_OWNERS
diff --git a/src/java/com/android/internal/net/ipsec/ike/net/IkeConnectionController.java b/src/java/com/android/internal/net/ipsec/ike/net/IkeConnectionController.java
index 71c14be..2bdec16 100644
--- a/src/java/com/android/internal/net/ipsec/ike/net/IkeConnectionController.java
+++ b/src/java/com/android/internal/net/ipsec/ike/net/IkeConnectionController.java
@@ -38,6 +38,7 @@
 import android.annotation.IntDef;
 import android.app.PendingIntent;
 import android.net.ConnectivityManager;
+import android.net.IpPrefix;
 import android.net.IpSecManager;
 import android.net.IpSecManager.ResourceUnavailableException;
 import android.net.LinkAddress;
@@ -79,6 +80,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
@@ -162,7 +164,7 @@
     /** Available remote addresses that are v4. */
     private final List<Inet4Address> mRemoteAddressesV4 = new ArrayList<>();
     /** Available remote addresses that are v6. */
-    private final List<Inet6Address> mRemoteAddressesV6 = new ArrayList<>();
+    private final List<Ipv6AddrInfo> mRemoteAddressesV6 = new ArrayList<>();
 
     private final Set<IkeSaRecord> mIkeSaRecords = new HashSet<>();
 
@@ -217,6 +219,25 @@
         this(ikeContext, config, new Dependencies());
     }
 
+    private static class Ipv6AddrInfo {
+        public final Inet6Address address;
+        public final boolean isNat64Addr;
+
+        Ipv6AddrInfo(Inet6Address address, boolean isNat64Addr) {
+            this.address = address;
+            this.isNat64Addr = isNat64Addr;
+        }
+
+        @Override
+        public String toString() {
+            String result = address.toString();
+            if (isNat64Addr) {
+                return result + "(Nat64)";
+            }
+            return result;
+        }
+    }
+
     /** Config includes all configurations to build an IkeConnectionController */
     public static class Config {
         public final IkeSessionParams ikeParams;
@@ -481,7 +502,7 @@
                                         + mNetwork
                                         + " with null LinkProperties or null NetworkCapabilities"));
             }
-            resolveAndSetAvailableRemoteAddresses();
+            resolveAndSetAvailableRemoteAddresses(linkProperties);
             selectAndSetRemoteAddress(linkProperties);
 
             int remotePort =
@@ -616,6 +637,18 @@
             return;
         }
 
+        getIkeLog()
+                .d(
+                        TAG,
+                        "onNetworkSetByUser: network "
+                                + network
+                                + " ipVersion "
+                                + ipVersion
+                                + " encapType "
+                                + encapType
+                                + " keepaliveDelaySeconds "
+                                + keepaliveDelaySeconds);
+
         // This is call is directly from the IkeSessionStateMachine, and thus cannot be
         // accidentally called in a NetworkCallback. See
         // ConnectivityManager.NetworkCallback#onLinkPropertiesChanged() and
@@ -727,10 +760,32 @@
         if (address instanceof Inet4Address) {
             mRemoteAddressesV4.add((Inet4Address) address);
         } else {
-            mRemoteAddressesV6.add((Inet6Address) address);
+            mRemoteAddressesV6.add(
+                    new Ipv6AddrInfo((Inet6Address) address, false /* isNat64Addr */));
         }
     }
 
+    /**
+     * Adds a remote IPv6 address.
+     *
+     * <p>This MUST only be called in a test.
+     */
+    @VisibleForTesting
+    public void addRemoteAddressV6(Inet6Address address, boolean isNat64Addr) {
+        mRemoteAddressesV6.add(new Ipv6AddrInfo((Inet6Address) address, isNat64Addr));
+    }
+
+    /**
+     * Clear all remote address cache.
+     *
+     * <p>This MUST only be called in a test.
+     */
+    @VisibleForTesting
+    public void clearRemoteAddress() {
+        mRemoteAddressesV4.clear();
+        mRemoteAddressesV6.clear();
+    }
+
     /** Gets the remote addresses */
     public InetAddress getRemoteAddress() {
         return mRemoteAddress;
@@ -743,7 +798,11 @@
 
     /** Gets all the IPv6 remote addresses */
     public List<Inet6Address> getAllRemoteIpv6Addresses() {
-        return new ArrayList<>(mRemoteAddressesV6);
+        final List<Inet6Address> addresses = new ArrayList<>();
+        for (Ipv6AddrInfo info : mRemoteAddressesV6) {
+            addresses.add(info.address);
+        }
+        return addresses;
     }
 
     /** Gets the local port */
@@ -858,7 +917,8 @@
         }
     }
 
-    private void resolveAndSetAvailableRemoteAddresses() throws IOException {
+    private void resolveAndSetAvailableRemoteAddresses(LinkProperties linkProperties)
+            throws IOException {
         // TODO(b/149954916): Do DNS resolution asynchronously
         InetAddress[] allRemoteAddresses = null;
 
@@ -909,7 +969,10 @@
             if (remoteAddress instanceof Inet4Address) {
                 mRemoteAddressesV4.add((Inet4Address) remoteAddress);
             } else {
-                mRemoteAddressesV6.add((Inet6Address) remoteAddress);
+                Inet6Address address = (Inet6Address) remoteAddress;
+                IpPrefix ipPrefix = linkProperties.getNat64Prefix();
+                mRemoteAddressesV6.add(
+                        new Ipv6AddrInfo(address, ipPrefix != null && ipPrefix.contains(address)));
             }
         }
     }
@@ -958,11 +1021,11 @@
                 throw ShimUtils.getInstance().getDnsFailedException(
                         "IPv6 required but no global IPv6 address available");
             }
-            mRemoteAddress = mRemoteAddressesV6.get(0);
+            mRemoteAddress = mRemoteAddressesV6.get(0).address;
         } else if (isIpV4Preferred(mIkeParams, mNc) && canConnectWithIpv4) {
             mRemoteAddress = mRemoteAddressesV4.get(0);
         } else if (canConnectWithIpv6) {
-            mRemoteAddress = mRemoteAddressesV6.get(0);
+            mRemoteAddress = mRemoteAddressesV6.get(0).address;
         } else if (canConnectWithIpv4) {
             mRemoteAddress = mRemoteAddressesV4.get(0);
         } else {
@@ -1037,6 +1100,61 @@
         ShimUtils.getInstance().executeOrSendFatalError(r, mCallback);
     }
 
+    private static Set<Integer> getSupportedVersions(boolean isV4Supported, boolean isV6Supported) {
+        final Set<Integer> versions = new HashSet<>();
+
+        if (isV4Supported) {
+            versions.add(ESP_IP_VERSION_IPV4);
+        }
+        if (isV6Supported) {
+            versions.add(ESP_IP_VERSION_IPV6);
+        }
+
+        return versions;
+    }
+
+    /**
+     * Return whether DNS lookup is required during mobility update
+     *
+     * <p>Require DNS lookup when one of the following condition is true:
+     *
+     * <ul>
+     *   <li>The network has changed
+     *   <li>The locally supported versions misaligned with the cached remotely supported versions
+     *   <li>Neither of the two versions are supported locally or remotely
+     * </ul>
+     */
+    @VisibleForTesting
+    public boolean isDnsLookupRequiredWithGlobalRemoteAddress(
+            Network oldNetwork, Network network, LinkProperties linkProperties) {
+        // If the network changes, perform a new DNS lookup to ensure that the correct remote
+        // address is used. This ensures that DNS returns addresses for the correct address families
+        // (important if using a v4/v6-only network). This also ensures that DNS64 is handled
+        // correctly when switching between networks that may have different IPv6 prefixes.
+        if (!network.equals(oldNetwork)) {
+            return true;
+        }
+
+        final Set<Integer> localIpVersions =
+                getSupportedVersions(
+                        hasLocalIpV4Address(linkProperties), linkProperties.hasGlobalIpv6Address());
+        final Set<Integer> remoteIpVersionsCached =
+                getSupportedVersions(!mRemoteAddressesV4.isEmpty(), !mRemoteAddressesV6.isEmpty());
+
+        getIkeLog()
+                .d(
+                        TAG,
+                        "isDnsLookupRequiredWithGlobalRemoteAddress localIpVersions "
+                                + localIpVersions
+                                + " remoteIpVersionsCached "
+                                + remoteIpVersionsCached);
+
+        if (Objects.equals(localIpVersions, remoteIpVersionsCached) && !localIpVersions.isEmpty()) {
+            return false;
+        }
+        return true;
+    }
+
     // This method is never expected be called due to the capabilities change of the existing
     // underlying network. Only explicit user requests, network changes, addresses changes or
     // configuration changes (such as the protocol preference) will call into this method.
@@ -1059,13 +1177,16 @@
         mNetwork = network;
         mNc = networkCapabilities;
 
-        // If the network changes, perform a new DNS lookup to ensure that the correct remote
-        // address is used. This ensures that DNS returns addresses for the correct address families
-        // (important if using a v4/v6-only network). This also ensures that DNS64 is handled
-        // correctly when switching between networks that may have different IPv6 prefixes.
-        if (!mNetwork.equals(oldNetwork)) {
+        // Remove all NAT64 addresses since they might be out-of-date
+        for (Ipv6AddrInfo info : mRemoteAddressesV6) {
+            if (info.isNat64Addr) {
+                mRemoteAddressesV6.remove(info);
+            }
+        }
+
+        if (isDnsLookupRequiredWithGlobalRemoteAddress(oldNetwork, mNetwork, linkProperties)) {
             try {
-                resolveAndSetAvailableRemoteAddresses();
+                resolveAndSetAvailableRemoteAddresses(linkProperties);
             } catch (IOException e) {
                 mCallback.onError(wrapAsIkeException(e));
                 return;
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index 212b907..2003198 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -1,6 +1,8 @@
-# Bug component: 827526
+# Bug component: 1364804
 set noparent
 
-lorenzo@google.com
-nharold@google.com
-satk@google.com
+amruthr@google.com
+jackyu@google.com
+rgreenwalt@google.com #{LAST_RESORT_SUGGESTION}
+
+nharold@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/net/IkeConnectionControllerTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/net/IkeConnectionControllerTest.java
index 346c423..a60e081 100644
--- a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/net/IkeConnectionControllerTest.java
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/net/IkeConnectionControllerTest.java
@@ -1445,4 +1445,193 @@
         verifyKeepalive(true /* hasOldKeepalive */, true /* isKeepaliveExpected */);
         assertEquals(underpinnedNetwork, mIkeConnectionCtrl.getUnderpinnedNetwork());
     }
+
+    private void verifyIsDnsLookupRequired(
+            boolean isNetworkChanged,
+            boolean hasLocalV4,
+            boolean hasLocalV6,
+            boolean hasRemoteV4Cached,
+            boolean hasRemoteV6Cached,
+            boolean dnsExpected)
+            throws Exception {
+        final Network newNetwork = isNetworkChanged ? mock(Network.class) : mMockDefaultNetwork;
+
+        final Inet4Address localV4 = hasLocalV4 ? LOCAL_ADDRESS : null;
+        final Inet6Address localV6 = hasLocalV6 ? LOCAL_ADDRESS_V6 : null;
+        setupLocalAddressForNetwork(newNetwork, localV4, localV6);
+
+        mIkeConnectionCtrl.clearRemoteAddress();
+        if (hasRemoteV4Cached) {
+            mIkeConnectionCtrl.addRemoteAddress(REMOTE_ADDRESS);
+        }
+        if (hasRemoteV6Cached) {
+            mIkeConnectionCtrl.addRemoteAddress(REMOTE_ADDRESS_V6);
+        }
+
+        assertEquals(
+                dnsExpected,
+                mIkeConnectionCtrl.isDnsLookupRequiredWithGlobalRemoteAddress(
+                        mMockDefaultNetwork,
+                        newNetwork,
+                        mMockConnectManager.getLinkProperties(newNetwork)));
+    }
+
+    private void verifyIsDnsLookupRequired(
+            boolean hasLocalV4,
+            boolean hasLocalV6,
+            boolean hasRemoteV4Cached,
+            boolean hasRemoteV6Cached,
+            boolean dnsExpected)
+            throws Exception {
+        verifyIsDnsLookupRequired(
+                false /* isNetworkChanged */,
+                hasLocalV4,
+                hasLocalV6,
+                hasRemoteV4Cached,
+                hasRemoteV6Cached,
+                dnsExpected);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_NetworkChange() throws Exception {
+        verifyIsDnsLookupRequired(
+                true /* isNetworkChanged */,
+                true /* hasLocalV4 */,
+                true /* hasLocalV6 */,
+                true /* hasRemoteV4Cached */,
+                true /* hasRemoteV6Cached */,
+                true /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalV4V6_RemoteV4() throws Exception {
+        verifyIsDnsLookupRequired(
+                true /* hasLocalV4 */,
+                true /* hasLocalV6 */,
+                true /* hasRemoteV4Cached */,
+                false /* hasRemoteV6Cached */,
+                true /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalV4V6_RemoteV6() throws Exception {
+        verifyIsDnsLookupRequired(
+                true /* hasLocalV4 */,
+                true /* hasLocalV6 */,
+                false /* hasRemoteV4Cached */,
+                true /* hasRemoteV6Cached */,
+                true /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalV4_RemoteV6() throws Exception {
+        verifyIsDnsLookupRequired(
+                true /* hasLocalV4 */,
+                false /* hasLocalV6 */,
+                false /* hasRemoteV4Cached */,
+                true /* hasRemoteV6Cached */,
+                true /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalV6_RemoteV4() throws Exception {
+        verifyIsDnsLookupRequired(
+                false /* hasLocalV4 */,
+                true /* hasLocalV6 */,
+                true /* hasRemoteV4Cached */,
+                false /* hasRemoteV6Cached */,
+                true /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalNone_RemoteV4() throws Exception {
+        verifyIsDnsLookupRequired(
+                false /* hasLocalV4 */,
+                false /* hasLocalV6 */,
+                true /* hasRemoteV4Cached */,
+                false /* hasRemoteV6Cached */,
+                true /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalV6_RemoteNone() throws Exception {
+        verifyIsDnsLookupRequired(
+                false /* hasLocalV4 */,
+                true /* hasLocalV6 */,
+                false /* hasRemoteV4Cached */,
+                false /* hasRemoteV6Cached */,
+                true /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalNone_RemoteNone() throws Exception {
+        verifyIsDnsLookupRequired(
+                false /* hasLocalV4 */,
+                false /* hasLocalV6 */,
+                false /* hasRemoteV4Cached */,
+                false /* hasRemoteV6Cached */,
+                true /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalV4_RemoteV4() throws Exception {
+        verifyIsDnsLookupRequired(
+                true /* hasLocalV4 */,
+                false /* hasLocalV6 */,
+                true /* hasRemoteV4Cached */,
+                false /* hasRemoteV6Cached */,
+                false /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalV6_RemoteV6() throws Exception {
+        verifyIsDnsLookupRequired(
+                false /* hasLocalV4 */,
+                true /* hasLocalV6 */,
+                false /* hasRemoteV4Cached */,
+                true /* hasRemoteV6Cached */,
+                false /* dnsExpected */);
+    }
+
+    @Test
+    public void testIsDnsLookupRequired_LocalV4V6_RemoteV4V6() throws Exception {
+        verifyIsDnsLookupRequired(
+                true /* hasLocalV4 */,
+                true /* hasLocalV6 */,
+                true /* hasRemoteV4Cached */,
+                true /* hasRemoteV6Cached */,
+                false /* dnsExpected */);
+    }
+
+    private void verifyDnsLookupWithCachedIpv6Address(boolean isNat64, boolean dnsExpected)
+            throws Exception {
+        reset(mMockDefaultNetwork);
+        setupLocalAddressForNetwork(mMockDefaultNetwork, LOCAL_ADDRESS_V6);
+        setupRemoteAddressForNetwork(mMockDefaultNetwork, REMOTE_ADDRESS_V6);
+
+        mIkeConnectionCtrl.clearRemoteAddress();
+        mIkeConnectionCtrl.addRemoteAddressV6(REMOTE_ADDRESS_V6, isNat64);
+
+        IkeNetworkCallbackBase callback = enableMobilityAndReturnCb(true /* isDefaultNetwork */);
+        mIkeConnectionCtrl.onUnderlyingNetworkUpdated(
+                mMockDefaultNetwork,
+                mMockConnectManager.getLinkProperties(mMockDefaultNetwork),
+                mMockNetworkCapabilities);
+
+        if (dnsExpected) {
+            verify(mMockDefaultNetwork).getAllByName(anyString());
+        } else {
+            verify(mMockDefaultNetwork, never()).getAllByName(anyString());
+        }
+    }
+
+    @Test
+    public void testOnUnderlyingNetworkUpdatedWithGlobalV6Address() throws Exception {
+        verifyDnsLookupWithCachedIpv6Address(false /* isNat64 */, false /* dnsExpected */);
+    }
+
+    @Test
+    public void testOnUnderlyingNetworkUpdatedWithNat64V6Address() throws Exception {
+        verifyDnsLookupWithCachedIpv6Address(true /* isNat64 */, true /* dnsExpected */);
+    }
 }