ClatCoordinator: improve file descriptor clean up in error handling

- Make the file descriptor clean up better when an error happens.
- Remove unused TODO for iptables because it has been
  implemented by another approach. See b/218407445.

Test: atest com.android.server.connectivity.ClatCoordinatorTest
Change-Id: Ib8bfa8875ae774769ab7e416fa7e5fde140f58da
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 8cefd47..ea89be6 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -498,6 +498,31 @@
         }
     }
 
+    private void maybeCleanUp(ParcelFileDescriptor tunFd, ParcelFileDescriptor readSock6,
+            ParcelFileDescriptor writeSock6) {
+        if (tunFd != null) {
+            try {
+                tunFd.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Fail to close tun file descriptor " + e);
+            }
+        }
+        if (readSock6 != null) {
+            try {
+                readSock6.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Fail to close read socket " + e);
+            }
+        }
+        if (writeSock6 != null) {
+            try {
+                writeSock6.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Fail to close write socket " + e);
+            }
+        }
+    }
+
     /**
      * Start clatd for a given interface and NAT64 prefix.
      */
@@ -546,8 +571,15 @@
 
         // [3] Open, configure and bring up the tun interface.
         // Create the v4-... tun interface.
+
+        // Initialize all required file descriptors with null pointer. This makes the following
+        // error handling easier. Simply always call #maybeCleanUp for closing file descriptors,
+        // if any valid ones, in error handling.
+        ParcelFileDescriptor tunFd = null;
+        ParcelFileDescriptor readSock6 = null;
+        ParcelFileDescriptor writeSock6 = null;
+
         final String tunIface = CLAT_PREFIX + iface;
-        final ParcelFileDescriptor tunFd;
         try {
             tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
         } catch (IOException e) {
@@ -556,7 +588,7 @@
 
         final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
         if (tunIfIndex == INVALID_IFINDEX) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Fail to get interface index for interface " + tunIface);
         }
 
@@ -564,7 +596,7 @@
         try {
             mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
         } catch (RemoteException | ServiceSpecificException e) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
         }
 
@@ -575,19 +607,17 @@
             detectedMtu = mDeps.detectMtu(pfx96Str,
                 ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
         } catch (IOException e) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Detect MTU on " + tunIface + " failed: " + e);
         }
         final int mtu = adjustMtu(detectedMtu);
         Log.i(TAG, "ipv4 mtu is " + mtu);
 
-        // TODO: add setIptablesDropRule
-
         // Config tun interface mtu, address and bring up.
         try {
             mNetd.interfaceSetMtu(tunIface, mtu);
         } catch (RemoteException | ServiceSpecificException e) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
         }
         final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
@@ -599,14 +629,13 @@
         try {
             mNetd.interfaceSetCfg(ifConfig);
         } catch (RemoteException | ServiceSpecificException e) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
                     + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
         }
 
         // [4] Open and configure local 464xlat read/write sockets.
         // Opens a packet socket to receive IPv6 packets in clatd.
-        final ParcelFileDescriptor readSock6;
         try {
             // Use a JNI call to get native file descriptor instead of Os.socket() because we would
             // like to use ParcelFileDescriptor to manage file descriptor. But ctor
@@ -614,27 +643,23 @@
             // descriptor to initialize ParcelFileDescriptor object instead.
             readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
         } catch (IOException e) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Open packet socket failed: " + e);
         }
 
         // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
-        final ParcelFileDescriptor writeSock6;
         try {
             // Use a JNI call to get native file descriptor instead of Os.socket(). See above
             // reason why we use jniOpenPacketSocket6().
             writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
         } catch (IOException e) {
-            tunFd.close();
-            readSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Open raw socket failed: " + e);
         }
 
         final int ifIndex = mDeps.getInterfaceIndex(iface);
         if (ifIndex == INVALID_IFINDEX) {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Fail to get interface index for interface " + iface);
         }
 
@@ -642,9 +667,7 @@
         try {
             mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6Str, ifIndex);
         } catch (IOException e) {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("add anycast sockopt failed: " + e);
         }
 
@@ -653,9 +676,7 @@
         try {
             cookie = mDeps.tagSocketAsClat(writeSock6.getFileDescriptor());
         } catch (IOException e) {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("tag raw socket failed: " + e);
         }
 
@@ -663,9 +684,7 @@
         try {
             mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6Str, ifIndex);
         } catch (IOException e) {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("configure packet socket failed: " + e);
         }
 
@@ -679,9 +698,9 @@
             mDeps.untagSocket(cookie);
             throw new IOException("Error start clatd on " + iface + ": " + e);
         } finally {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            // The file descriptors have been duplicated (dup2) to clatd in native_startClatd().
+            // Close these file descriptor stubs which are unused anymore.
+            maybeCleanUp(tunFd, readSock6, writeSock6);
         }
 
         // [6] Initialize and store clatd tracker object.
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 3047a16..8dfe924 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
@@ -556,14 +557,28 @@
                 () -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
     }
 
-    private void checkNotStartClat(final TestDependencies deps, final boolean verifyTunFd,
-            final boolean verifyPacketSockFd, final boolean verifyRawSockFd) throws Exception {
+    private void checkNotStartClat(final TestDependencies deps, final boolean needToCloseTunFd,
+            final boolean needToClosePacketSockFd, final boolean needToCloseRawSockFd)
+            throws Exception {
         // [1] Expect that modified TestDependencies can't start clatd.
+        // Use precise check to make sure that there is no unexpected file descriptor closing.
         clearInvocations(TUN_PFD, RAW_SOCK_PFD, PACKET_SOCK_PFD);
         assertNotStartClat(deps);
-        if (verifyTunFd) verify(TUN_PFD).close();
-        if (verifyPacketSockFd) verify(PACKET_SOCK_PFD).close();
-        if (verifyRawSockFd) verify(RAW_SOCK_PFD).close();
+        if (needToCloseTunFd) {
+            verify(TUN_PFD).close();
+        } else {
+            verify(TUN_PFD, never()).close();
+        }
+        if (needToClosePacketSockFd) {
+            verify(PACKET_SOCK_PFD).close();
+        } else {
+            verify(PACKET_SOCK_PFD, never()).close();
+        }
+        if (needToCloseRawSockFd) {
+            verify(RAW_SOCK_PFD).close();
+        } else {
+            verify(RAW_SOCK_PFD, never()).close();
+        }
 
         // [2] Expect that unmodified TestDependencies can start clatd.
         // Used to make sure that the above modified TestDependencies has really broken the
@@ -582,8 +597,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -595,8 +610,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -607,8 +622,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -620,8 +635,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -632,8 +647,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -644,8 +659,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -657,8 +672,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -669,8 +684,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -682,8 +697,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -697,7 +712,7 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 }