From 68eab721fce399e5a3880bc9939b5ff0970739d7 Mon Sep 17 00:00:00 2001
From: Chalard Jean <jchalard@google.com>
Date: Thu, 16 Nov 2023 17:00:04 +0900
Subject: [PATCH] Apply forwarding rules before sending onAvailable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Ideally the rules should be set up before onAvailable is
sent, but onLocalNetworkChanged should be sent after.

The previous code had CSLocalAgentTests#testForwardingRules
flaky because it was waiting for onAvailable and immediately
checking that the routing rules have been set up.

There are other ways to fix this flake (e.g. wait for
onLocalNetworkChange in the test or add a timeout on
the verify(netd) call) but this patch probably implements
the best production code behavior.

Bug: 309688089
Test: CSLocalAgentTests#testForwardingRules 1'000 times
      Before this patch : flakes after 50~200 iterations
      After this patch : no flake in 1'000 iterations
Change-Id: I4dcccaa3607c1e28aa89f268f0c721d87b1e9466
---
 .../android/server/ConnectivityService.java   | 75 +++++++++++--------
 1 file changed, 44 insertions(+), 31 deletions(-)

diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index ea6d37e080..ba9ea868bb 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -10085,6 +10085,45 @@ public class ConnectivityService extends IConnectivityManager.Stub
         // Process default network changes if applicable.
         processDefaultNetworkChanges(changes);
 
+        // Update forwarding rules for the upstreams of local networks. Do this before sending
+        // onAvailable so that by the time onAvailable is sent the forwarding rules are set up.
+        // Don't send CALLBACK_LOCAL_NETWORK_INFO_CHANGED yet though : they should be sent after
+        // onAvailable so clients know what network the change is about. Store such changes in
+        // an array that's only allocated if necessary (because it's almost never necessary).
+        ArrayList<NetworkAgentInfo> localInfoChangedAgents = null;
+        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+            if (!nai.isLocalNetwork()) continue;
+            final NetworkRequest nr = nai.localNetworkConfig.getUpstreamSelector();
+            if (null == nr) continue; // No upstream for this local network
+            final NetworkRequestInfo nri = mNetworkRequests.get(nr);
+            final NetworkReassignment.RequestReassignment change = changes.getReassignment(nri);
+            if (null == change) continue; // No change in upstreams for this network
+            final String fromIface = nai.linkProperties.getInterfaceName();
+            if (!hasSameInterfaceName(change.mOldNetwork, change.mNewNetwork)
+                    || change.mOldNetwork.isDestroyed()) {
+                // There can be a change with the same interface name if the new network is the
+                // replacement for the old network that was unregisteredAfterReplacement.
+                try {
+                    if (null != change.mOldNetwork) {
+                        mRoutingCoordinatorService.removeInterfaceForward(fromIface,
+                                change.mOldNetwork.linkProperties.getInterfaceName());
+                    }
+                    // If the new upstream is already destroyed, there is no point in setting up
+                    // a forward (in fact, it might forward to the interface for some new network !)
+                    // Later when the upstream disconnects CS will try to remove the forward, which
+                    // is ignored with a benign log by RoutingCoordinatorService.
+                    if (null != change.mNewNetwork && !change.mNewNetwork.isDestroyed()) {
+                        mRoutingCoordinatorService.addInterfaceForward(fromIface,
+                                change.mNewNetwork.linkProperties.getInterfaceName());
+                    }
+                } catch (final RemoteException e) {
+                    loge("Can't update forwarding rules", e);
+                }
+            }
+            if (null == localInfoChangedAgents) localInfoChangedAgents = new ArrayList<>();
+            localInfoChangedAgents.add(nai);
+        }
+
         // Notify requested networks are available after the default net is switched, but
         // before LegacyTypeTracker sends legacy broadcasts
         for (final NetworkReassignment.RequestReassignment event :
@@ -10133,38 +10172,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
             notifyNetworkLosing(nai, now);
         }
 
-        // Update forwarding rules for the upstreams of local networks. Do this after sending
-        // onAvailable so that clients understand what network this is about.
-        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
-            if (!nai.isLocalNetwork()) continue;
-            final NetworkRequest nr = nai.localNetworkConfig.getUpstreamSelector();
-            if (null == nr) continue; // No upstream for this local network
-            final NetworkRequestInfo nri = mNetworkRequests.get(nr);
-            final NetworkReassignment.RequestReassignment change = changes.getReassignment(nri);
-            if (null == change) continue; // No change in upstreams for this network
-            final String fromIface = nai.linkProperties.getInterfaceName();
-            if (!hasSameInterfaceName(change.mOldNetwork, change.mNewNetwork)
-                    || change.mOldNetwork.isDestroyed()) {
-                // There can be a change with the same interface name if the new network is the
-                // replacement for the old network that was unregisteredAfterReplacement.
-                try {
-                    if (null != change.mOldNetwork) {
-                        mRoutingCoordinatorService.removeInterfaceForward(fromIface,
-                                change.mOldNetwork.linkProperties.getInterfaceName());
-                    }
-                    // If the new upstream is already destroyed, there is no point in setting up
-                    // a forward (in fact, it might forward to the interface for some new network !)
-                    // Later when the upstream disconnects CS will try to remove the forward, which
-                    // is ignored with a benign log by RoutingCoordinatorService.
-                    if (null != change.mNewNetwork && !change.mNewNetwork.isDestroyed()) {
-                        mRoutingCoordinatorService.addInterfaceForward(fromIface,
-                                change.mNewNetwork.linkProperties.getInterfaceName());
-                    }
-                } catch (final RemoteException e) {
-                    loge("Can't update forwarding rules", e);
-                }
+        // Send LOCAL_NETWORK_INFO_CHANGED callbacks now that onAvailable and onLost have been sent.
+        if (null != localInfoChangedAgents) {
+            for (final NetworkAgentInfo nai : localInfoChangedAgents) {
+                notifyNetworkCallbacks(nai,
+                        ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
             }
-            notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
         }
 
         updateLegacyTypeTrackerAndVpnLockdownForRematch(changes, nais);
-- 
GitLab