Do not destroy socket when VPN interface address is still in use

Normally when an IP address is removed, all sockets associated with the
addresses are destroyed. This patchset changes this behavior such that
if the address in question is still being used by another interface that
belongs to the same underlying virtual network, the destroy operation is
skipped. This change is needed to support VPN seamless handover where the
VPN app will establish a second TUN interface (with different config)
before tearing down the existing interface. The intention is that during
this handover existing socket connections should not be disturbed. There
is a companion change in the framework side to make sure during such
handover, the VPN netId remains unchanged so routing still works.

Bug: 64692591
Test: cts-tradefed run commandAndExit cts-dev -m CtsHostsideNetworkTests -t com.android.cts.net.HostsideVpnTests
Test: system/netd/tests/runtests.sh
Change-Id: I02c6b0db5f15cd1aef3e3fa6f0c36e86b4f427fd
Merged-In: I02c6b0db5f15cd1aef3e3fa6f0c36e86b4f427fd
(cherry picked from commit acbb6b7bbea17c5653929ee5224bd4f8e16c0f69)
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index 87ad1bd..5bbfe3f 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -28,6 +28,8 @@
 #define LOG_TAG "Netd"
 #include "log/log.h"
 
+#include <android-base/strings.h>
+
 #include "cutils/misc.h"
 #include "resolv_netid.h"
 
@@ -345,6 +347,10 @@
 
 bool NetworkController::isVirtualNetwork(unsigned netId) const {
     android::RWLock::AutoRLock lock(mRWLock);
+    return isVirtualNetworkLocked(netId);
+}
+
+bool NetworkController::isVirtualNetworkLocked(unsigned netId) const {
     Network* network = getNetworkLocked(netId);
     return network && network->getType() == Network::VIRTUAL;
 }
@@ -466,6 +472,14 @@
     delete network;
     _resolv_delete_cache_for_net(netId);
 
+    for (auto iter = mIfindexToLastNetId.begin(); iter != mIfindexToLastNetId.end();) {
+        if (iter->second == netId) {
+            iter = mIfindexToLastNetId.erase(iter);
+        } else {
+            ++iter;
+        }
+    }
+
     updateTcpSocketMonitorPolling();
 
     return ret;
@@ -484,8 +498,18 @@
         ALOGE("interface %s already assigned to netId %u", interface, existingNetId);
         return -EBUSY;
     }
+    if (int ret = getNetworkLocked(netId)->addInterface(interface)) {
+        return ret;
+    }
 
-    return getNetworkLocked(netId)->addInterface(interface);
+    int ifIndex = RouteController::getIfIndex(interface);
+    if (ifIndex) {
+        mIfindexToLastNetId[ifIndex] = netId;
+    } else {
+        // Cannot happen, since addInterface() above will have failed.
+        ALOGE("inconceivable! added interface %s with no index", interface);
+    }
+    return 0;
 }
 
 int NetworkController::removeInterfaceFromNetwork(unsigned netId, const char* interface) {
@@ -583,6 +607,53 @@
     return modifyRoute(netId, interface, destination, nexthop, false, legacy, uid);
 }
 
+void NetworkController::addInterfaceAddress(unsigned ifIndex, const char* address) {
+    android::RWLock::AutoWLock lock(mRWLock);
+
+    if (ifIndex == 0) {
+        ALOGE("Attempting to add address %s without ifindex", address);
+        return;
+    }
+    mAddressToIfindices[address].insert(ifIndex);
+}
+
+// Returns whether we should call SOCK_DESTROY on the removed address.
+bool NetworkController::removeInterfaceAddress(unsigned ifindex, const char* address) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    // First, update mAddressToIfindices map
+    auto ifindicesIter = mAddressToIfindices.find(address);
+    if (ifindicesIter == mAddressToIfindices.end()) {
+        ALOGE("Removing unknown address %s from ifindex %u", address, ifindex);
+        return true;
+    }
+    std::unordered_set<unsigned>& ifindices = ifindicesIter->second;
+    if (ifindices.erase(ifindex) > 0) {
+        if (ifindices.size() == 0) {
+            mAddressToIfindices.erase(ifindicesIter);
+        }
+    } else {
+        ALOGE("No record of address %s on interface %u", address, ifindex);
+        return true;
+    }
+    // Then, check for VPN handover condition
+    if (mIfindexToLastNetId.find(ifindex) == mIfindexToLastNetId.end()) {
+        ALOGE("Interface index %u was never in a currently-connected netId", ifindex);
+        return true;
+    }
+    unsigned lastNetId = mIfindexToLastNetId[ifindex];
+    for (unsigned idx : ifindices) {
+        unsigned activeNetId = mIfindexToLastNetId[idx];
+        // If this IP address is still assigned to another interface in the same network,
+        // then we don't need to destroy sockets on it because they are likely still valid.
+        // For now we do this only on VPNs.
+        // TODO: evaluate extending this to all network types.
+        if (lastNetId == activeNetId && isVirtualNetworkLocked(activeNetId)) {
+            return false;
+        }
+    }
+    return true;
+}
+
 bool NetworkController::canProtectLocked(uid_t uid) const {
     return ((getPermissionForUserLocked(uid) & PERMISSION_SYSTEM) == PERMISSION_SYSTEM) ||
            mProtectableUsers.find(uid) != mProtectableUsers.end();
@@ -631,6 +702,23 @@
     }
     dw.decIndent();
 
+    dw.blankline();
+    dw.println("Interface <-> last network map:");
+    dw.incIndent();
+    for (const auto& i : mIfindexToLastNetId) {
+        dw.println("Ifindex: %u NetId: %u", i.first, i.second);
+    }
+    dw.decIndent();
+
+    dw.blankline();
+    dw.println("Interface addresses:");
+    dw.incIndent();
+    for (const auto& i : mAddressToIfindices) {
+        dw.println("address: %s ifindices: [%s]", i.first.c_str(),
+                android::base::Join(i.second, ", ").c_str());
+    }
+    dw.decIndent();
+
     dw.decIndent();
 
     dw.decIndent();