Merge "Move DhcpInfoTest to frameworks/base/tests/net/common" into rvc-dev
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index f1ea5d0..552331e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5889,6 +5889,7 @@
 
     private void processLinkPropertiesFromAgent(NetworkAgentInfo nai, LinkProperties lp) {
         lp.ensureDirectlyConnectedRoutes();
+        nai.clatd.setNat64PrefixFromRa(lp.getNat64Prefix());
     }
 
     private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties newLp,
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index e6b2d26..34d0bed 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -81,15 +81,23 @@
         RUNNING,      // start() called, and the stacked iface is known to be up.
     }
 
-    /** NAT64 prefix currently in use. Only valid in STARTING or RUNNING states. */
+    /**
+     * NAT64 prefix currently in use. Only valid in STARTING or RUNNING states.
+     * Used, among other things, to avoid updates when switching from a prefix learned from one
+     * source (e.g., RA) to the same prefix learned from another source (e.g., RA).
+     */
     private IpPrefix mNat64PrefixInUse;
     /** NAT64 prefix (if any) discovered from DNS via RFC 7050. */
     private IpPrefix mNat64PrefixFromDns;
+    /** NAT64 prefix (if any) learned from the network via RA. */
+    private IpPrefix mNat64PrefixFromRa;
     private String mBaseIface;
     private String mIface;
     private Inet6Address mIPv6Address;
     private State mState = State.IDLE;
 
+    private boolean mPrefixDiscoveryRunning;
+
     public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver,
             INetworkManagementService nmService) {
         mDnsResolver = dnsResolver;
@@ -139,15 +147,6 @@
     }
 
     /**
-     * @return true if we have started prefix discovery and not yet stopped it (regardless of
-     * whether it is still running or has succeeded).
-     * A true result corresponds to internal states DISCOVERING, STARTING and RUNNING.
-     */
-    public boolean isPrefixDiscoveryStarted() {
-        return mState == State.DISCOVERING || isStarted();
-    }
-
-    /**
      * @return true if clatd has been started and has not yet stopped.
      * A true result corresponds to internal states STARTING and RUNNING.
      */
@@ -181,7 +180,7 @@
             return;
         }
 
-        mNat64PrefixInUse = getNat64Prefix();
+        mNat64PrefixInUse = selectNat64Prefix();
         String addrStr = null;
         try {
             addrStr = mNetd.clatdStart(baseIface, mNat64PrefixInUse.toString());
@@ -196,6 +195,9 @@
         } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
             Slog.e(TAG, "Invalid IPv6 address " + addrStr);
         }
+        if (mPrefixDiscoveryRunning && !isPrefixDiscoveryNeeded()) {
+            stopPrefixDiscovery();
+        }
     }
 
     /**
@@ -218,10 +220,15 @@
         mNat64PrefixInUse = null;
         mIface = null;
         mBaseIface = null;
-        if (requiresClat(mNetwork)) {
+
+        if (isPrefixDiscoveryNeeded()) {
+            if (!mPrefixDiscoveryRunning) {
+                startPrefixDiscovery();
+            }
             mState = State.DISCOVERING;
         } else {
-            stopPrefixDiscovery();  // Enters IDLE state.
+            stopPrefixDiscovery();
+            mState = State.IDLE;
         }
     }
 
@@ -282,7 +289,7 @@
         } catch (RemoteException | ServiceSpecificException e) {
             Slog.e(TAG, "Error starting prefix discovery on netId " + getNetId() + ": " + e);
         }
-        mState = State.DISCOVERING;
+        mPrefixDiscoveryRunning = true;
     }
 
     private void stopPrefixDiscovery() {
@@ -291,11 +298,18 @@
         } catch (RemoteException | ServiceSpecificException e) {
             Slog.e(TAG, "Error stopping prefix discovery on netId " + getNetId() + ": " + e);
         }
-        mState = State.IDLE;
+        mPrefixDiscoveryRunning = false;
+    }
+
+    private boolean isPrefixDiscoveryNeeded() {
+        // If there is no NAT64 prefix in the RA, prefix discovery is always needed. It cannot be
+        // stopped after it succeeds, because stopping it will cause netd to report that the prefix
+        // has been removed, and that will cause us to stop clatd.
+        return requiresClat(mNetwork) && mNat64PrefixFromRa == null;
     }
 
     private void maybeHandleNat64PrefixChange() {
-        final IpPrefix newPrefix = getNat64Prefix();
+        final IpPrefix newPrefix = selectNat64Prefix();
         if (!Objects.equals(mNat64PrefixInUse, newPrefix)) {
             Slog.d(TAG, "NAT64 prefix changed from " + mNat64PrefixInUse + " to "
                     + newPrefix);
@@ -314,13 +328,11 @@
         // TODO: turn this class into a proper StateMachine. http://b/126113090
         switch (mState) {
             case IDLE:
-                if (requiresClat(mNetwork)) {
-                    // Network is detected to be IPv6-only.
-                    // TODO: consider going to STARTING directly if the NAT64 prefix is already
-                    // known. This would however result in clatd running without prefix discovery
-                    // running, which might be a surprising combination.
+                if (isPrefixDiscoveryNeeded()) {
                     startPrefixDiscovery();  // Enters DISCOVERING state.
-                    return;
+                    mState = State.DISCOVERING;
+                } else if (requiresClat(mNetwork)) {
+                    start();  // Enters STARTING state.
                 }
                 break;
 
@@ -333,6 +345,7 @@
                 if (!requiresClat(mNetwork)) {
                     // IPv4 address added. Go back to IDLE state.
                     stopPrefixDiscovery();
+                    mState = State.IDLE;
                     return;
                 }
                 break;
@@ -351,8 +364,20 @@
         }
     }
 
-    private IpPrefix getNat64Prefix() {
-        return mNat64PrefixFromDns;
+    /**
+     * Picks a NAT64 prefix to use. Always prefers the prefix from the RA if one is received from
+     * both RA and DNS, because the prefix in the RA has better security and updatability, and will
+     * almost always be received first anyway.
+     *
+     * Any network that supports legacy hosts will support discovering the DNS64 prefix via DNS as
+     * well. If the prefix from the RA is withdrawn, fall back to that for reliability purposes.
+     */
+    private IpPrefix selectNat64Prefix() {
+        return mNat64PrefixFromRa != null ? mNat64PrefixFromRa : mNat64PrefixFromDns;
+    }
+
+    public void setNat64PrefixFromRa(IpPrefix prefix) {
+        mNat64PrefixFromRa = prefix;
     }
 
     public void setNat64PrefixFromDns(IpPrefix prefix) {
@@ -367,7 +392,7 @@
     public void fixupLinkProperties(@NonNull LinkProperties oldLp, @NonNull LinkProperties lp) {
         // This must be done even if clatd is not running, because otherwise shouldStartClat would
         // never return true.
-        lp.setNat64Prefix(getNat64Prefix());
+        lp.setNat64Prefix(selectNat64Prefix());
 
         if (!isRunning()) {
             return;
diff --git a/tests/net/common/java/android/net/NetworkProviderTest.kt b/tests/net/common/java/android/net/NetworkProviderTest.kt
index 4601c4b..b7c47c2 100644
--- a/tests/net/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/net/common/java/android/net/NetworkProviderTest.kt
@@ -105,14 +105,43 @@
                 .build()
         val cb = ConnectivityManager.NetworkCallback()
         mCm.requestNetwork(nr, cb)
-        provider.expectCallback<OnNetworkRequested>() {
-            callback -> callback.request.getNetworkSpecifier() == specifier &&
+        provider.expectCallback<OnNetworkRequested>() { callback ->
+            callback.request.getNetworkSpecifier() == specifier &&
             callback.request.hasTransport(TRANSPORT_TEST)
         }
 
+        val initialScore = 40
+        val updatedScore = 60
+        val nc = NetworkCapabilities().apply {
+                addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+                addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                setNetworkSpecifier(specifier)
+        }
+        val lp = LinkProperties()
+        val config = NetworkAgentConfig.Builder().build()
+        val agent = object : NetworkAgent(context, mHandlerThread.looper, "TestAgent", nc, lp,
+                initialScore, config, provider) {}
+
+        provider.expectCallback<OnNetworkRequested>() { callback ->
+            callback.request.getNetworkSpecifier() == specifier &&
+            callback.score == initialScore &&
+            callback.id == agent.providerId
+        }
+
+        agent.sendNetworkScore(updatedScore)
+        provider.expectCallback<OnNetworkRequested>() { callback ->
+            callback.request.getNetworkSpecifier() == specifier &&
+            callback.score == updatedScore &&
+            callback.id == agent.providerId
+        }
+
         mCm.unregisterNetworkCallback(cb)
-        provider.expectCallback<OnNetworkRequestWithdrawn>() {
-            callback -> callback.request.getNetworkSpecifier() == specifier &&
+        provider.expectCallback<OnNetworkRequestWithdrawn>() { callback ->
+            callback.request.getNetworkSpecifier() == specifier &&
             callback.request.hasTransport(TRANSPORT_TEST)
         }
         mCm.unregisterNetworkProvider(provider)
diff --git a/tests/net/common/java/android/net/NetworkSpecifierTest.kt b/tests/net/common/java/android/net/NetworkSpecifierTest.kt
new file mode 100644
index 0000000..f3409f5
--- /dev/null
+++ b/tests/net/common/java/android/net/NetworkSpecifierTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+package android.net
+
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertTrue
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.Q)
+class NetworkSpecifierTest {
+    private class TestNetworkSpecifier(
+        val intData: Int = 123,
+        val stringData: String = "init"
+    ) : NetworkSpecifier() {
+        override fun canBeSatisfiedBy(other: NetworkSpecifier?): Boolean =
+                other != null &&
+                other is TestNetworkSpecifier &&
+                other.intData >= intData &&
+                stringData.equals(other.stringData)
+
+        override fun redact(): NetworkSpecifier = TestNetworkSpecifier(intData, "redact")
+    }
+
+    @Test
+    fun testRedact() {
+        val ns: TestNetworkSpecifier = TestNetworkSpecifier()
+        val redactNs = ns.redact()
+        assertTrue(redactNs is TestNetworkSpecifier)
+        assertEquals(ns.intData, redactNs.intData)
+        assertNotEquals(ns.stringData, redactNs.stringData)
+        assertTrue("redact".equals(redactNs.stringData))
+    }
+
+    @Test
+    fun testcanBeSatisfiedBy() {
+        val target: TestNetworkSpecifier = TestNetworkSpecifier()
+        assertFalse(target.canBeSatisfiedBy(null))
+        assertTrue(target.canBeSatisfiedBy(TestNetworkSpecifier()))
+        val otherNs = TelephonyNetworkSpecifier.Builder().setSubscriptionId(123).build()
+        assertFalse(target.canBeSatisfiedBy(otherNs))
+        assertTrue(target.canBeSatisfiedBy(TestNetworkSpecifier(intData = 999)))
+        assertFalse(target.canBeSatisfiedBy(TestNetworkSpecifier(intData = 1)))
+        assertFalse(target.canBeSatisfiedBy(TestNetworkSpecifier(stringData = "diff")))
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index dad0363..a478e68 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -6179,6 +6179,111 @@
         mCm.unregisterNetworkCallback(networkCallback);
     }
 
+    private void expectNat64PrefixChange(TestableNetworkCallback callback,
+            TestNetworkAgentWrapper agent, IpPrefix prefix) {
+        callback.expectLinkPropertiesThat(agent, x -> Objects.equals(x.getNat64Prefix(), prefix));
+    }
+
+    @Test
+    public void testNat64PrefixMultipleSources() throws Exception {
+        final String iface = "wlan0";
+        final String pref64FromRaStr = "64:ff9b::";
+        final String pref64FromDnsStr = "2001:db8:64::";
+        final IpPrefix pref64FromRa = new IpPrefix(InetAddress.getByName(pref64FromRaStr), 96);
+        final IpPrefix pref64FromDns = new IpPrefix(InetAddress.getByName(pref64FromDnsStr), 96);
+        final IpPrefix newPref64FromRa = new IpPrefix("2001:db8:64:64:64:64::/96");
+
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(request, callback);
+
+        final LinkProperties baseLp = new LinkProperties();
+        baseLp.setInterfaceName(iface);
+        baseLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64"));
+        baseLp.addDnsServer(InetAddress.getByName("2001:4860:4860::6464"));
+
+        reset(mMockNetd, mMockDnsResolver);
+        InOrder inOrder = inOrder(mMockNetd, mMockDnsResolver);
+
+        // If a network already has a NAT64 prefix on connect, clatd is started immediately and
+        // prefix discovery is never started.
+        LinkProperties lp = new LinkProperties(baseLp);
+        lp.setNat64Prefix(pref64FromRa);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mCellNetworkAgent.connect(false);
+        final Network network = mCellNetworkAgent.getNetwork();
+        int netId = network.getNetId();
+        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
+        inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
+        callback.assertNoCallback();
+        assertEquals(pref64FromRa, mCm.getLinkProperties(network).getNat64Prefix());
+
+        // If the RA prefix is withdrawn, clatd is stopped and prefix discovery is started.
+        lp.setNat64Prefix(null);
+        mCellNetworkAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mCellNetworkAgent, null);
+        inOrder.verify(mMockNetd).clatdStop(iface);
+        inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
+
+        // If the RA prefix appears while DNS discovery is in progress, discovery is stopped and
+        // clatd is started with the prefix from the RA.
+        lp.setNat64Prefix(pref64FromRa);
+        mCellNetworkAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromRa);
+        inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
+        inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
+
+        // Withdraw the RA prefix so we can test the case where an RA prefix appears after DNS
+        // discovery has succeeded.
+        lp.setNat64Prefix(null);
+        mCellNetworkAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mCellNetworkAgent, null);
+        inOrder.verify(mMockNetd).clatdStop(iface);
+        inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
+
+        mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */,
+                pref64FromDnsStr, 96);
+        expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromDns);
+        inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString());
+
+        // If the RA prefix reappears, clatd is restarted and prefix discovery is stopped.
+        lp.setNat64Prefix(pref64FromRa);
+        mCellNetworkAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromRa);
+        inOrder.verify(mMockNetd).clatdStop(iface);
+        inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
+        inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
+        inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
+
+        // If the RA prefix changes, clatd is restarted and prefix discovery is not started.
+        lp.setNat64Prefix(newPref64FromRa);
+        mCellNetworkAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mCellNetworkAgent, newPref64FromRa);
+        inOrder.verify(mMockNetd).clatdStop(iface);
+        inOrder.verify(mMockNetd).clatdStart(iface, newPref64FromRa.toString());
+        inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
+        inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
+
+        // If the RA prefix changes to the same value, nothing happens.
+        lp.setNat64Prefix(newPref64FromRa);
+        mCellNetworkAgent.sendLinkProperties(lp);
+        callback.assertNoCallback();
+        assertEquals(newPref64FromRa, mCm.getLinkProperties(network).getNat64Prefix());
+        inOrder.verify(mMockNetd, never()).clatdStop(iface);
+        inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString());
+        inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
+        inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
+
+        // The transition between no prefix and DNS prefix is tested in testStackedLinkProperties.
+
+        callback.assertNoCallback();
+        mCellNetworkAgent.disconnect();
+        mCm.unregisterNetworkCallback(callback);
+    }
+
     @Test
     public void testDataActivityTracking() throws Exception {
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index d0ebb52..5046b65 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -58,8 +58,10 @@
 
     static final String BASE_IFACE = "test0";
     static final String STACKED_IFACE = "v4-test0";
+    static final LinkAddress V6ADDR = new LinkAddress("2001:db8:1::f00/64");
     static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29");
     static final String NAT64_PREFIX = "64:ff9b::/96";
+    static final String OTHER_NAT64_PREFIX = "2001:db8:0:64::/96";
     static final int NETID = 42;
 
     @Mock ConnectivityService mConnectivity;
@@ -81,6 +83,14 @@
         };
     }
 
+    private void markNetworkConnected() {
+        mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
+    }
+
+    private void markNetworkDisconnected() {
+        mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, "", "");
+    }
+
     @Before
     public void setUp() throws Exception {
         mLooper = new TestLooper();
@@ -92,6 +102,7 @@
         mNai.linkProperties.setInterfaceName(BASE_IFACE);
         mNai.networkInfo = new NetworkInfo(null);
         mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
+        markNetworkConnected();
         when(mNai.connService()).thenReturn(mConnectivity);
         when(mNai.netAgentConfig()).thenReturn(mAgentConfig);
         when(mNai.handler()).thenReturn(mHandler);
@@ -139,7 +150,7 @@
             for (NetworkInfo.DetailedState state : supportedDetailedStates) {
                 mNai.networkInfo.setDetailedState(state, "reason", "extraInfo");
 
-                mNai.linkProperties.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
+                mNai.linkProperties.setNat64Prefix(new IpPrefix(OTHER_NAT64_PREFIX));
                 assertRequiresClat(false, mNai);
                 assertShouldStartClat(false, mNai);
 
@@ -176,11 +187,20 @@
         }
     }
 
-    @Test
-    public void testNormalStartAndStop() throws Exception {
+    private void makeClatUnnecessary(boolean dueToDisconnect) {
+        if (dueToDisconnect) {
+            markNetworkDisconnected();
+        } else {
+            mNai.linkProperties.addLinkAddress(ADDR);
+        }
+    }
+
+    private void checkNormalStartAndStop(boolean dueToDisconnect) throws Exception {
         Nat464Xlat nat = makeNat464Xlat();
         ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
 
+        mNai.linkProperties.addLinkAddress(V6ADDR);
+
         nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
 
         // Start clat.
@@ -200,6 +220,7 @@
         assertRunning(nat);
 
         // Stop clat (Network disconnects, IPv4 addr appears, ...).
+        makeClatUnnecessary(dueToDisconnect);
         nat.stop();
 
         verify(mNetd).clatdStop(eq(BASE_IFACE));
@@ -217,11 +238,23 @@
         verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
+    @Test
+    public void testNormalStartAndStopDueToDisconnect() throws Exception {
+        checkNormalStartAndStop(true);
+    }
+
+    @Test
+    public void testNormalStartAndStopDueToIpv4Addr() throws Exception {
+        checkNormalStartAndStop(false);
+    }
+
     private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception {
         Nat464Xlat nat = makeNat464Xlat();
         ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
         InOrder inOrder = inOrder(mNetd, mConnectivity);
 
+        mNai.linkProperties.addLinkAddress(V6ADDR);
+
         nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
 
         nat.start();
@@ -344,10 +377,11 @@
         verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
-    @Test
-    public void testStopBeforeClatdStarts() throws Exception {
+    private void checkStopBeforeClatdStarts(boolean dueToDisconnect) throws Exception {
         Nat464Xlat nat = makeNat464Xlat();
 
+        mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+
         nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
 
         nat.start();
@@ -356,6 +390,7 @@
         verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
 
         // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
+        makeClatUnnecessary(dueToDisconnect);
         nat.stop();
 
         verify(mNetd).clatdStop(eq(BASE_IFACE));
@@ -377,9 +412,20 @@
     }
 
     @Test
-    public void testStopAndClatdNeverStarts() throws Exception {
+    public void testStopDueToDisconnectBeforeClatdStarts() throws Exception {
+        checkStopBeforeClatdStarts(true);
+    }
+
+    @Test
+    public void testStopDueToIpv4AddrBeforeClatdStarts() throws Exception {
+        checkStopBeforeClatdStarts(false);
+    }
+
+    private void checkStopAndClatdNeverStarts(boolean dueToDisconnect) throws Exception {
         Nat464Xlat nat = makeNat464Xlat();
 
+        mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+
         nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
 
         nat.start();
@@ -388,6 +434,7 @@
         verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
 
         // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
+        makeClatUnnecessary(dueToDisconnect);
         nat.stop();
 
         verify(mNetd).clatdStop(eq(BASE_IFACE));
@@ -398,6 +445,57 @@
         verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
+    @Test
+    public void testStopDueToDisconnectAndClatdNeverStarts() throws Exception {
+        checkStopAndClatdNeverStarts(true);
+    }
+
+    @Test
+    public void testStopDueToIpv4AddressAndClatdNeverStarts() throws Exception {
+        checkStopAndClatdNeverStarts(false);
+    }
+
+    @Test
+    public void testNat64PrefixPreference() throws Exception {
+        final IpPrefix prefixFromDns = new IpPrefix(NAT64_PREFIX);
+        final IpPrefix prefixFromRa = new IpPrefix(OTHER_NAT64_PREFIX);
+
+        Nat464Xlat nat = makeNat464Xlat();
+
+        final LinkProperties emptyLp = new LinkProperties();
+        LinkProperties fixedupLp;
+
+        fixedupLp = new LinkProperties();
+        nat.setNat64PrefixFromDns(prefixFromDns);
+        nat.fixupLinkProperties(emptyLp, fixedupLp);
+        assertEquals(prefixFromDns, fixedupLp.getNat64Prefix());
+
+        fixedupLp = new LinkProperties();
+        nat.setNat64PrefixFromRa(prefixFromRa);
+        nat.fixupLinkProperties(emptyLp, fixedupLp);
+        assertEquals(prefixFromRa, fixedupLp.getNat64Prefix());
+
+        fixedupLp = new LinkProperties();
+        nat.setNat64PrefixFromRa(null);
+        nat.fixupLinkProperties(emptyLp, fixedupLp);
+        assertEquals(prefixFromDns, fixedupLp.getNat64Prefix());
+
+        fixedupLp = new LinkProperties();
+        nat.setNat64PrefixFromRa(prefixFromRa);
+        nat.fixupLinkProperties(emptyLp, fixedupLp);
+        assertEquals(prefixFromRa, fixedupLp.getNat64Prefix());
+
+        fixedupLp = new LinkProperties();
+        nat.setNat64PrefixFromDns(null);
+        nat.fixupLinkProperties(emptyLp, fixedupLp);
+        assertEquals(prefixFromRa, fixedupLp.getNat64Prefix());
+
+        fixedupLp = new LinkProperties();
+        nat.setNat64PrefixFromRa(null);
+        nat.fixupLinkProperties(emptyLp, fixedupLp);
+        assertEquals(null, fixedupLp.getNat64Prefix());
+    }
+
     static void assertIdle(Nat464Xlat nat) {
         assertTrue("Nat464Xlat was not IDLE", !nat.isStarted());
     }