Merge TP1A.220425.001

Change-Id: Ida0a124540b8b5a19dc215b3a648dd67ace78969
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 3ed6c0c..4ada67f 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -493,10 +493,6 @@
     private static final boolean NO_CALLBACKS = false;
     private static final boolean SEND_CALLBACKS = true;
 
-    // This must match the interface prefix in clatd.c.
-    // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
-    private static final String CLAT_PREFIX = "v4-";
-
     private static final int IMMEDIATE_FAILURE_DURATION = 0;
 
     private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1;
@@ -544,7 +540,6 @@
     private final String mTag;
     private final Context mContext;
     private final String mInterfaceName;
-    private final String mClatInterfaceName;
     @VisibleForTesting
     protected final IpClientCallbacksWrapper mCallback;
     private final Dependencies mDependencies;
@@ -712,7 +707,6 @@
 
         mContext = context;
         mInterfaceName = ifName;
-        mClatInterfaceName = CLAT_PREFIX + ifName;
         mDependencies = deps;
         mMetricsLog = deps.getIpConnectivityLog();
         mNetworkQuirkMetrics = deps.getNetworkQuirkMetrics();
@@ -762,44 +756,19 @@
                             updateGratuitousNaTargetSet(targetIp, false /* remove address */);
                         });
                     }
+
+                    @Override
+                    public void onClatInterfaceStateUpdate(boolean add) {
+                        // TODO: when clat interface was removed, consider sending a message to
+                        // the IpClient main StateMachine thread, in case "NDO enabled" state
+                        // becomes tied to more things that 464xlat operation.
+                        getHandler().post(() -> {
+                            mCallback.setNeighborDiscoveryOffload(add ? false : true);
+                        });
+                    }
                 },
-                config, mLog, mDependencies) {
-            @Override
-            public void onInterfaceAdded(String iface) {
-                super.onInterfaceAdded(iface);
-                if (mClatInterfaceName.equals(iface)) {
-                    mCallback.setNeighborDiscoveryOffload(false);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceAdded(" + iface + ")";
-                logMsg(msg);
-            }
-
-            @Override
-            public void onInterfaceRemoved(String iface) {
-                super.onInterfaceRemoved(iface);
-                // TODO: Also observe mInterfaceName going down and take some
-                // kind of appropriate action.
-                if (mClatInterfaceName.equals(iface)) {
-                    // TODO: consider sending a message to the IpClient main
-                    // StateMachine thread, in case "NDO enabled" state becomes
-                    // tied to more things that 464xlat operation.
-                    mCallback.setNeighborDiscoveryOffload(true);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceRemoved(" + iface + ")";
-                logMsg(msg);
-            }
-
-            private void logMsg(String msg) {
-                Log.d(mTag, msg);
-                getHandler().post(() -> mLog.log("OBSERVED " + msg));
-            }
-        };
+                config, mLog, mDependencies
+        );
 
         mLinkProperties = new LinkProperties();
         mLinkProperties.setInterfaceName(mInterfaceName);
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index 8a5ed2e..b1dbabd 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -42,6 +42,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.netlink.NduseroptMessage;
@@ -121,6 +122,14 @@
          * @param addr The removed IPv6 address.
          */
         void onIpv6AddressRemoved(Inet6Address addr);
+
+        /**
+         * Called when the clat interface was added/removed.
+         *
+         * @param add True: clat interface was added.
+         *            False: clat interface was removed.
+         */
+        void onClatInterfaceStateUpdate(boolean add);
     }
 
     /** Configuration parameters for IpClientLinkObserver. */
@@ -142,15 +151,21 @@
     private final Configuration mConfig;
     private final Handler mHandler;
     private final IpClient.Dependencies mDependencies;
-
+    private final String mClatInterfaceName;
     private final MyNetlinkMonitor mNetlinkMonitor;
 
+    private boolean mClatInterfaceExists;
+
+    // This must match the interface prefix in clatd.c.
+    // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
+    protected static final String CLAT_PREFIX = "v4-";
     private static final boolean DBG = false;
 
     public IpClientLinkObserver(Context context, Handler h, String iface, Callback callback,
             Configuration config, SharedLog log, IpClient.Dependencies deps) {
         mContext = context;
         mInterfaceName = iface;
+        mClatInterfaceName = CLAT_PREFIX + iface;
         mTag = "NetlinkTracker/" + mInterfaceName;
         mCallback = callback;
         mLinkProperties = new LinkProperties();
@@ -192,19 +207,22 @@
     }
 
     @Override
+    public void onInterfaceAdded(String iface) {
+        if (isNetlinkEventParsingEnabled()) return;
+        maybeLog("interfaceAdded", iface);
+        if (mClatInterfaceName.equals(iface)) {
+            mCallback.onClatInterfaceStateUpdate(true /* add interface */);
+        }
+    }
+
+    @Override
     public void onInterfaceRemoved(String iface) {
+        if (isNetlinkEventParsingEnabled()) return;
         maybeLog("interfaceRemoved", iface);
-        if (mInterfaceName.equals(iface)) {
-            // Our interface was removed. Clear our LinkProperties and tell our owner that they are
-            // now empty. Note that from the moment that the interface is removed, any further
-            // interface-specific messages (e.g., RTM_DELADDR) will not reach us, because the netd
-            // code that parses them will not be able to resolve the ifindex to an interface name.
-            final boolean linkState;
-            synchronized (this) {
-                clearLinkProperties();
-                linkState = getInterfaceLinkStateLocked();
-            }
-            mCallback.update(linkState);
+        if (mClatInterfaceName.equals(iface)) {
+            mCallback.onClatInterfaceStateUpdate(false /* remove interface */);
+        } else if (mInterfaceName.equals(iface)) {
+            updateInterfaceRemoved();
         }
     }
 
@@ -308,6 +326,19 @@
         }
     }
 
+    private void updateInterfaceRemoved() {
+        // Our interface was removed. Clear our LinkProperties and tell our owner that they are
+        // now empty. Note that from the moment that the interface is removed, any further
+        // interface-specific messages (e.g., RTM_DELADDR) will not reach us, because the netd
+        // code that parses them will not be able to resolve the ifindex to an interface name.
+        final boolean linkState;
+        synchronized (this) {
+            clearLinkProperties();
+            linkState = getInterfaceLinkStateLocked();
+        }
+        mCallback.update(linkState);
+    }
+
     /**
      * Returns a copy of this object's LinkProperties.
      */
@@ -503,14 +534,45 @@
             }
         }
 
+        private void updateClatInterfaceLinkState(@NonNull final StructIfinfoMsg ifinfoMsg,
+                @Nullable final String ifname, short nlMsgType) {
+            switch (nlMsgType) {
+                case NetlinkConstants.RTM_NEWLINK:
+                    if (mClatInterfaceExists) break;
+                    maybeLog("clatInterfaceAdded", ifname);
+                    mCallback.onClatInterfaceStateUpdate(true /* add interface */);
+                    mClatInterfaceExists = true;
+                    break;
+
+                case NetlinkConstants.RTM_DELLINK:
+                    if (!mClatInterfaceExists) break;
+                    maybeLog("clatInterfaceRemoved", ifname);
+                    mCallback.onClatInterfaceStateUpdate(false /* remove interface */);
+                    mClatInterfaceExists = false;
+                    break;
+
+                default:
+                    Log.e(mTag, "unsupported rtnetlink link msg type " + nlMsgType);
+                    break;
+            }
+        }
+
         private void processRtNetlinkLinkMessage(RtNetlinkLinkMessage msg) {
             if (!isNetlinkEventParsingEnabled()) return;
 
+            // Check if receiving netlink link state update for clat interface.
+            final String ifname = msg.getInterfaceName();
+            final short nlMsgType = msg.getHeader().nlmsg_type;
             final StructIfinfoMsg ifinfoMsg = msg.getIfinfoHeader();
+            if (mClatInterfaceName.equals(ifname)) {
+                updateClatInterfaceLinkState(ifinfoMsg, ifname, nlMsgType);
+                return;
+            }
+
             if (ifinfoMsg.family != AF_UNSPEC || ifinfoMsg.index != mIfindex) return;
             if ((ifinfoMsg.flags & IFF_LOOPBACK) != 0) return;
 
-            switch (msg.getHeader().nlmsg_type) {
+            switch (nlMsgType) {
                 case NetlinkConstants.RTM_NEWLINK:
                     final boolean state = (ifinfoMsg.flags & IFF_LOWER_UP) != 0;
                     maybeLog("interfaceLinkStateChanged", "ifindex " + mIfindex
@@ -519,10 +581,12 @@
                     break;
 
                 case NetlinkConstants.RTM_DELLINK:
+                    maybeLog("interfaceRemoved", ifname);
+                    updateInterfaceRemoved();
                     break;
 
                 default:
-                    Log.e(mTag, "Unknown rtnetlink link msg type " + msg.getHeader().nlmsg_type);
+                    Log.e(mTag, "Unknown rtnetlink link msg type " + nlMsgType);
                     break;
             }
         }
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 36c6162..9984a54 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -34,6 +34,7 @@
 
 java_defaults {
     name: "NetworkStackIntegrationTestsDefaults",
+    defaults: ["framework-connectivity-test-defaults"],
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index f423862..78d0bca 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -28,6 +28,7 @@
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
 import static android.net.dhcp.DhcpPacket.MIN_V6ONLY_WAIT_MS;
 import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
+import static android.net.ip.IpClientLinkObserver.CLAT_PREFIX;
 import static android.net.ip.IpReachabilityMonitor.MIN_NUD_SOLICIT_NUM;
 import static android.net.ip.IpReachabilityMonitor.NUD_MCAST_RESOLICIT_NUM;
 import static android.net.ip.IpReachabilityMonitor.nudEventTypeToInt;
@@ -670,6 +671,16 @@
         mHandler.post(() -> mPacketReader.start());
     }
 
+    private TestNetworkInterface setUpClatInterface(@NonNull String baseIface) throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        final TestNetworkInterface iface = runAsShell(MANAGE_TEST_NETWORKS, () -> {
+            final TestNetworkManager tnm =
+                    inst.getContext().getSystemService(TestNetworkManager.class);
+            return tnm.createTapInterface(false /* bringUp */, CLAT_PREFIX + baseIface);
+        });
+        return iface;
+    }
+
     private void teardownTapInterface() {
         if (mPacketReader != null) {
             mHandler.post(() -> mPacketReader.stop());  // Also closes the socket
@@ -1077,7 +1088,7 @@
         return getNextDhcpPacket();
     }
 
-    private void removeTapInterface(final FileDescriptor fd) {
+    private void removeTestInterface(final FileDescriptor fd) {
         try {
             Os.close(fd);
         } catch (ErrnoException e) {
@@ -1109,7 +1120,7 @@
     }
 
     private void doRestoreInitialMtuTest(final boolean shouldChangeMtu,
-            final boolean shouldRemoveTapInterface) throws Exception {
+            final boolean shouldRemoveTestInterface) throws Exception {
         final long currentTime = System.currentTimeMillis();
         int mtu = TEST_DEFAULT_MTU;
 
@@ -1132,11 +1143,11 @@
         // empty LinkProperties changes instead of one.
         reset(mCb);
 
-        if (shouldRemoveTapInterface) removeTapInterface(mTapFd);
+        if (shouldRemoveTestInterface) removeTestInterface(mTapFd);
         try {
             mIpc.shutdown();
             awaitIpClientShutdown();
-            if (shouldRemoveTapInterface) {
+            if (shouldRemoveTestInterface) {
                 verify(mNetd, never()).interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
             } else {
                 // Verify that MTU indeed has been restored or not.
@@ -1504,12 +1515,12 @@
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu() throws Exception {
-        doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTapInterface */);
+        doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTestInterface */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu_WithoutMtuChange() throws Exception {
-        doRestoreInitialMtuTest(false /* shouldChangeMtu */, false /* shouldRemoveTapInterface */);
+        doRestoreInitialMtuTest(false /* shouldChangeMtu */, false /* shouldRemoveTestInterface */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
@@ -1517,19 +1528,19 @@
         doThrow(new RemoteException("NetdNativeService::interfaceSetMtu")).when(mNetd)
                 .interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
 
-        doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTapInterface */);
+        doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTestInterface */);
         assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), TEST_MIN_MTU);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu_NotFoundInterfaceWhenStopping() throws Exception {
-        doRestoreInitialMtuTest(true /* shouldChangeMtu */, true /* shouldRemoveTapInterface */);
+        doRestoreInitialMtuTest(true /* shouldChangeMtu */, true /* shouldRemoveTestInterface */);
     }
 
     @Test
     public void testRestoreInitialInterfaceMtu_NotFoundInterfaceWhenStartingProvisioning()
             throws Exception {
-        removeTapInterface(mTapFd);
+        removeTestInterface(mTapFd);
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
                 .withoutIPv6()
@@ -1588,7 +1599,7 @@
 
         // Intend to remove the tap interface and force IpClient throw provisioning failure
         // due to that interface is not found.
-        removeTapInterface(mTapFd);
+        removeTestInterface(mTapFd);
         assertNull(InterfaceParams.getByName(mIfaceName));
 
         startIpClientProvisioning(config);
@@ -3752,4 +3763,25 @@
                         .build()
         );
     }
+
+    @Test
+    public void testIpClientLinkObserver_onClatInterfaceStateUpdate() throws Exception {
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIPv4()
+                .build();
+        startIpClientProvisioning(config);
+        doIpv6OnlyProvisioning();
+
+        reset(mCb);
+
+        // Add the clat interface and check the callback.
+        final TestNetworkInterface clatIface = setUpClatInterface(mIfaceName);
+        assertNotNull(clatIface);
+        assertTrue(clatIface.getInterfaceName().equals(CLAT_PREFIX + mIfaceName));
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).setNeighborDiscoveryOffload(false);
+
+        // Remove the clat interface and check the callback.
+        removeTestInterface(clatIface.getFileDescriptor().getFileDescriptor());
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).setNeighborDiscoveryOffload(true);
+    }
 }