From 22350c93b4bd50f74b69f12512e5d5f8a0580f54 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Sat, 7 Oct 2023 19:21:45 +0900 Subject: [PATCH] Add LocalNetworkInfo and send callbacks when it changes Test: CSLocalAgentTest Change-Id: I8caca97b891081f9212a01d428a34ed1a08d5126 --- .../android/net/LocalNetworkInfo.aidl | 20 +++ framework/jarjar-excludes.txt | 1 + .../src/android/net/ConnectivityManager.java | 56 ++++++-- .../src/android/net/LocalNetworkInfo.java | 96 ++++++++++++++ .../android/server/ConnectivityService.java | 62 +++++++-- .../testutils/TestableNetworkCallback.kt | 51 ++++++- .../connectivityservice/CSLocalAgentTests.kt | 125 ++++++++++++++---- 7 files changed, 355 insertions(+), 56 deletions(-) create mode 100644 framework/aidl-export/android/net/LocalNetworkInfo.aidl create mode 100644 framework/src/android/net/LocalNetworkInfo.java diff --git a/framework/aidl-export/android/net/LocalNetworkInfo.aidl b/framework/aidl-export/android/net/LocalNetworkInfo.aidl new file mode 100644 index 0000000000..fa0bc41ed3 --- /dev/null +++ b/framework/aidl-export/android/net/LocalNetworkInfo.aidl @@ -0,0 +1,20 @@ +/** + * + * Copyright (C) 2023 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; + +parcelable LocalNetworkInfo; diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt index bd513d2862..09abd17950 100644 --- a/framework/jarjar-excludes.txt +++ b/framework/jarjar-excludes.txt @@ -23,6 +23,7 @@ android\.net\.IConnectivityDiagnosticsCallback(\$.+)? # of these classes must be protected by a check for >= S SDK. # It's unlikely anybody else declares a hidden class with this name ? android\.net\.RoutingCoordinatorManager(\$.+)? +android\.net\.LocalNetworkInfo(\$.+)? # KeepaliveUtils is used by ConnectivityManager CTS # TODO: move into service-connectivity so framework-connectivity stops using diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index eb8f8c3db9..66b284036e 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3963,16 +3963,21 @@ public class ConnectivityManager { * @param network The {@link Network} of the satisfying network. * @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network. * @param linkProperties The {@link LinkProperties} of the satisfying network. + * @param localInfo The {@link LocalNetworkInfo} of the satisfying network, or null + * if this network is not a local network. * @param blocked Whether access to the {@link Network} is blocked due to system policy. * @hide */ public final void onAvailable(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities, - @NonNull LinkProperties linkProperties, @BlockedReason int blocked) { + @NonNull LinkProperties linkProperties, + @Nullable LocalNetworkInfo localInfo, + @BlockedReason int blocked) { // Internally only this method is called when a new network is available, and // it calls the callback in the same way and order that older versions used // to call so as not to change the behavior. onAvailable(network, networkCapabilities, linkProperties, blocked != 0); + if (null != localInfo) onLocalNetworkInfoChanged(network, localInfo); onBlockedStatusChanged(network, blocked); } @@ -3989,7 +3994,8 @@ public class ConnectivityManager { */ public void onAvailable(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities, - @NonNull LinkProperties linkProperties, boolean blocked) { + @NonNull LinkProperties linkProperties, + boolean blocked) { onAvailable(network); if (!networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)) { @@ -4115,6 +4121,19 @@ public class ConnectivityManager { public void onLinkPropertiesChanged(@NonNull Network network, @NonNull LinkProperties linkProperties) {} + /** + * Called when there is a change in the {@link LocalNetworkInfo} for this network. + * + * This is only called for local networks, that is those with the + * NET_CAPABILITY_LOCAL_NETWORK network capability. + * + * @param network the {@link Network} whose local network info has changed. + * @param localNetworkInfo the new {@link LocalNetworkInfo} for this network. + * @hide + */ + public void onLocalNetworkInfoChanged(@NonNull Network network, + @NonNull LocalNetworkInfo localNetworkInfo) {} + /** * Called when the network the framework connected to for this request suspends data * transmission temporarily. @@ -4209,27 +4228,29 @@ public class ConnectivityManager { } /** @hide */ - public static final int CALLBACK_PRECHECK = 1; + public static final int CALLBACK_PRECHECK = 1; /** @hide */ - public static final int CALLBACK_AVAILABLE = 2; + public static final int CALLBACK_AVAILABLE = 2; /** @hide arg1 = TTL */ - public static final int CALLBACK_LOSING = 3; + public static final int CALLBACK_LOSING = 3; /** @hide */ - public static final int CALLBACK_LOST = 4; + public static final int CALLBACK_LOST = 4; /** @hide */ - public static final int CALLBACK_UNAVAIL = 5; + public static final int CALLBACK_UNAVAIL = 5; /** @hide */ - public static final int CALLBACK_CAP_CHANGED = 6; + public static final int CALLBACK_CAP_CHANGED = 6; /** @hide */ - public static final int CALLBACK_IP_CHANGED = 7; + public static final int CALLBACK_IP_CHANGED = 7; /** @hide obj = NetworkCapabilities, arg1 = seq number */ - private static final int EXPIRE_LEGACY_REQUEST = 8; + private static final int EXPIRE_LEGACY_REQUEST = 8; /** @hide */ - public static final int CALLBACK_SUSPENDED = 9; + public static final int CALLBACK_SUSPENDED = 9; /** @hide */ - public static final int CALLBACK_RESUMED = 10; + public static final int CALLBACK_RESUMED = 10; /** @hide */ - public static final int CALLBACK_BLK_CHANGED = 11; + public static final int CALLBACK_BLK_CHANGED = 11; + /** @hide */ + public static final int CALLBACK_LOCAL_NETWORK_INFO_CHANGED = 12; /** @hide */ public static String getCallbackName(int whichCallback) { @@ -4245,6 +4266,7 @@ public class ConnectivityManager { case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED"; case CALLBACK_RESUMED: return "CALLBACK_RESUMED"; case CALLBACK_BLK_CHANGED: return "CALLBACK_BLK_CHANGED"; + case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: return "CALLBACK_LOCAL_NETWORK_INFO_CHANGED"; default: return Integer.toString(whichCallback); } @@ -4299,7 +4321,8 @@ public class ConnectivityManager { case CALLBACK_AVAILABLE: { NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); LinkProperties lp = getObject(message, LinkProperties.class); - callback.onAvailable(network, cap, lp, message.arg1); + LocalNetworkInfo lni = getObject(message, LocalNetworkInfo.class); + callback.onAvailable(network, cap, lp, lni, message.arg1); break; } case CALLBACK_LOSING: { @@ -4324,6 +4347,11 @@ public class ConnectivityManager { callback.onLinkPropertiesChanged(network, lp); break; } + case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: { + final LocalNetworkInfo info = getObject(message, LocalNetworkInfo.class); + callback.onLocalNetworkInfoChanged(network, info); + break; + } case CALLBACK_SUSPENDED: { callback.onNetworkSuspended(network); break; diff --git a/framework/src/android/net/LocalNetworkInfo.java b/framework/src/android/net/LocalNetworkInfo.java new file mode 100644 index 0000000000..f94513319a --- /dev/null +++ b/framework/src/android/net/LocalNetworkInfo.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 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.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + + +/** + * Information about a local network. + * + * This is sent to ConnectivityManager.NetworkCallback. + * @hide + */ +// TODO : make public +public final class LocalNetworkInfo implements Parcelable { + @Nullable private final Network mUpstreamNetwork; + + public LocalNetworkInfo(@Nullable final Network upstreamNetwork) { + this.mUpstreamNetwork = upstreamNetwork; + } + + /** + * Return the upstream network, or null if none. + */ + @Nullable + public Network getUpstreamNetwork() { + return mUpstreamNetwork; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeParcelable(mUpstreamNetwork, flags); + } + + @Override + public String toString() { + return "LocalNetworkInfo { upstream=" + mUpstreamNetwork + " }"; + } + + public static final @NonNull Creator CREATOR = new Creator<>() { + public LocalNetworkInfo createFromParcel(Parcel in) { + final Network upstreamNetwork = in.readParcelable(null); + return new LocalNetworkInfo(upstreamNetwork); + } + + @Override + public LocalNetworkInfo[] newArray(final int size) { + return new LocalNetworkInfo[size]; + } + }; + + /** + * Builder for LocalNetworkInfo + */ + public static final class Builder { + @Nullable private Network mUpstreamNetwork; + + /** + * Set the upstream network, or null if none. + * @return the builder + */ + @NonNull public Builder setUpstreamNetwork(@Nullable final Network network) { + mUpstreamNetwork = network; + return this; + } + + /** + * Build the LocalNetworkInfo + */ + @NonNull public LocalNetworkInfo build() { + return new LocalNetworkInfo(mUpstreamNetwork); + } + } +} diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 50b41348af..d8417c1541 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -168,6 +168,7 @@ import android.net.IpMemoryStore; import android.net.IpPrefix; import android.net.LinkProperties; import android.net.LocalNetworkConfig; +import android.net.LocalNetworkInfo; import android.net.MatchAllNetworkSpecifier; import android.net.NativeNetworkConfig; import android.net.NativeNetworkType; @@ -4183,7 +4184,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } case NetworkAgent.EVENT_LOCAL_NETWORK_CONFIG_CHANGED: { final LocalNetworkConfig config = (LocalNetworkConfig) arg.second; - updateLocalNetworkConfig(nai, nai.localNetworkConfig, config); + handleUpdateLocalNetworkConfig(nai, nai.localNetworkConfig, config); break; } case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: { @@ -4946,7 +4947,7 @@ public class ConnectivityService extends IConnectivityManager.Stub notifyIfacesChangedForNetworkStats(); // If this was a local network forwarded to some upstream, or if some local network was // forwarded to this nai, then disable forwarding rules now. - maybeDisableForwardRulesForDisconnectingNai(nai); + maybeDisableForwardRulesForDisconnectingNai(nai, true /* sendCallbacks */); // If this is a local network with an upstream selector, remove the associated network // request. if (nai.isLocalNetwork()) { @@ -5069,7 +5070,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void maybeDisableForwardRulesForDisconnectingNai( - @NonNull final NetworkAgentInfo disconnecting) { + @NonNull final NetworkAgentInfo disconnecting, final boolean sendCallbacks) { // Step 1 : maybe this network was the upstream for one or more local networks. for (final NetworkAgentInfo local : mNetworkAgentInfos) { if (!local.isLocalNetwork()) continue; @@ -5082,6 +5083,13 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkAgentInfo satisfier = nri.getSatisfier(); if (disconnecting != satisfier) continue; removeLocalNetworkUpstream(local, disconnecting); + // Set the satisfier to null immediately so that the LOCAL_NETWORK_CHANGED callback + // correctly contains null as an upstream. + if (sendCallbacks) { + nri.setSatisfier(null, null); + notifyNetworkCallbacks(local, + ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED); + } } // Step 2 : maybe this is a local network that had an upstream. @@ -5148,8 +5156,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mDscpPolicyTracker.removeAllDscpPolicies(nai, false); } // Remove any forwarding rules to and from the interface for this network, since - // the interface is going to go away. - maybeDisableForwardRulesForDisconnectingNai(nai); + // the interface is going to go away. Don't send the callbacks however ; if the network + // was is being disconnected the callbacks have already been sent, and if it is being + // destroyed pending replacement they will be sent when it is disconnected. + maybeDisableForwardRulesForDisconnectingNai(nai, false /* sendCallbacks */); try { mNetd.networkDestroy(nai.network.getNetId()); } catch (RemoteException | ServiceSpecificException e) { @@ -8320,7 +8330,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (nai.isLocalNetwork()) { - updateLocalNetworkConfig(nai, null /* oldConfig */, nai.localNetworkConfig); + handleUpdateLocalNetworkConfig(nai, null /* oldConfig */, nai.localNetworkConfig); } nai.notifyRegistered(); NetworkInfo networkInfo = nai.networkInfo; @@ -8988,7 +8998,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } // oldConfig is null iff this is the original registration of the local network config - private void updateLocalNetworkConfig(@NonNull final NetworkAgentInfo nai, + private void handleUpdateLocalNetworkConfig(@NonNull final NetworkAgentInfo nai, @Nullable final LocalNetworkConfig oldConfig, @NonNull final LocalNetworkConfig newConfig) { if (!nai.isLocalNetwork()) { @@ -9021,6 +9031,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // If there is an old satisfier, but no new request, then remove the old upstream. removeLocalNetworkUpstream(nai, oldSatisfier); nai.localNetworkConfig = configBuilder.build(); + // When there is a new request, the rematch sees the new request and sends the + // LOCAL_NETWORK_INFO_CHANGED callbacks accordingly. + // But here there is no new request, so the rematch won't see anything. Send + // callbacks to apps now to tell them about the loss of upstream. + notifyNetworkCallbacks(nai, + ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED); return; } } @@ -9042,12 +9058,14 @@ public class ConnectivityService extends IConnectivityManager.Stub nri.setSatisfier(oldSatisfier, nr); } nai.localNetworkConfig = configBuilder.build(); + // handleRegisterNetworkRequest causes a rematch. The rematch must happen after + // nai.localNetworkConfig is set, since it will base its callbacks on the old + // satisfier and the new request. handleRegisterNetworkRequest(nri); } else { configBuilder.setUpstreamSelector(oldRequest); nai.localNetworkConfig = configBuilder.build(); } - } /** @@ -9378,6 +9396,21 @@ public class ConnectivityService extends IConnectivityManager.Stub releasePendingNetworkRequestWithDelay(pendingIntent); } + @Nullable + private LocalNetworkInfo localNetworkInfoForNai(@NonNull final NetworkAgentInfo nai) { + if (!nai.isLocalNetwork()) return null; + final Network upstream; + final NetworkRequest selector = nai.localNetworkConfig.getUpstreamSelector(); + if (null == selector) { + upstream = null; + } else { + final NetworkRequestInfo upstreamNri = mNetworkRequests.get(selector); + final NetworkAgentInfo satisfier = upstreamNri.getSatisfier(); + upstream = (null == satisfier) ? null : satisfier.network; + } + return new LocalNetworkInfo.Builder().setUpstreamNetwork(upstream).build(); + } + // networkAgent is only allowed to be null if notificationType is // CALLBACK_UNAVAIL. This is because UNAVAIL is about no network being // available, while all other cases are about some particular network. @@ -9413,6 +9446,10 @@ public class ConnectivityService extends IConnectivityManager.Stub putParcelable(bundle, nc); putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions( networkAgent.linkProperties, nri.mPid, nri.mUid)); + // The local network info is often null, so can't use the static putParcelable + // method here. + bundle.putParcelable(LocalNetworkInfo.class.getSimpleName(), + localNetworkInfoForNai(networkAgent)); // For this notification, arg1 contains the blocked status. msg.arg1 = arg1; break; @@ -9444,6 +9481,14 @@ public class ConnectivityService extends IConnectivityManager.Stub msg.arg1 = arg1; break; } + case ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED: { + if (!networkAgent.isLocalNetwork()) { + Log.wtf(TAG, "Callback for local info for a non-local network"); + return; + } + putParcelable(bundle, localNetworkInfoForNai(networkAgent)); + break; + } } msg.what = notificationType; msg.setData(bundle); @@ -10077,6 +10122,7 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Can't update forwarding rules", e); } } + notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED); } updateLegacyTypeTrackerAndVpnLockdownForRematch(changes, nais); diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt index df9c61ae2d..05c044400e 100644 --- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt +++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt @@ -18,6 +18,7 @@ package com.android.testutils import android.net.ConnectivityManager.NetworkCallback import android.net.LinkProperties +import android.net.LocalNetworkInfo import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED @@ -28,6 +29,7 @@ import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatusInt import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged +import com.android.testutils.RecorderCallback.CallbackEntry.LocalInfoChanged import com.android.testutils.RecorderCallback.CallbackEntry.Losing import com.android.testutils.RecorderCallback.CallbackEntry.Lost import com.android.testutils.RecorderCallback.CallbackEntry.Resumed @@ -68,6 +70,10 @@ open class RecorderCallback private constructor( override val network: Network, val lp: LinkProperties ) : CallbackEntry() + data class LocalInfoChanged( + override val network: Network, + val info: LocalNetworkInfo + ) : CallbackEntry() data class Suspended(override val network: Network) : CallbackEntry() data class Resumed(override val network: Network) : CallbackEntry() data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackEntry() @@ -94,6 +100,8 @@ open class RecorderCallback private constructor( @JvmField val LINK_PROPERTIES_CHANGED = LinkPropertiesChanged::class @JvmField + val LOCAL_INFO_CHANGED = LocalInfoChanged::class + @JvmField val SUSPENDED = Suspended::class @JvmField val RESUMED = Resumed::class @@ -131,6 +139,11 @@ open class RecorderCallback private constructor( history.add(LinkPropertiesChanged(network, lp)) } + override fun onLocalNetworkInfoChanged(network: Network, info: LocalNetworkInfo) { + Log.d(TAG, "onLocalNetworkInfoChanged $network $info") + history.add(LocalInfoChanged(network, info)) + } + override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { Log.d(TAG, "onBlockedStatusChanged $network $blocked") history.add(BlockedStatus(network, blocked)) @@ -430,37 +443,63 @@ open class TestableNetworkCallback private constructor( suspended: Boolean = false, validated: Boolean? = true, blocked: Boolean = false, + upstream: Network? = null, tmt: Long = defaultTimeoutMs ) { - expectAvailableCallbacksCommon(net, suspended, validated, tmt) + expectAvailableCallbacksCommon(net, suspended, validated, upstream, tmt) expect(net, tmt) { it.blocked == blocked } } + // For backward compatibility, add a method that allows callers to specify a timeout but + // no upstream. + fun expectAvailableCallbacks( + net: Network, + suspended: Boolean = false, + validated: Boolean? = true, + blocked: Boolean = false, + tmt: Long = defaultTimeoutMs + ) = expectAvailableCallbacks(net, suspended, validated, blocked, upstream = null, tmt = tmt) + fun expectAvailableCallbacks( net: Network, suspended: Boolean, validated: Boolean, blockedReason: Int, + upstream: Network? = null, tmt: Long ) { - expectAvailableCallbacksCommon(net, suspended, validated, tmt) + expectAvailableCallbacksCommon(net, suspended, validated, upstream, tmt) expect(net) { it.reason == blockedReason } } + // For backward compatibility, add a method that allows callers to specify a timeout but + // no upstream. + fun expectAvailableCallbacks( + net: Network, + suspended: Boolean = false, + validated: Boolean = true, + blockedReason: Int, + tmt: Long = defaultTimeoutMs + ) = expectAvailableCallbacks(net, suspended, validated, blockedReason, upstream = null, tmt) + private fun expectAvailableCallbacksCommon( net: Network, suspended: Boolean, validated: Boolean?, + upstream: Network?, tmt: Long ) { expect(net, tmt) if (suspended) { expect(net, tmt) } - expect(net, tmt) { + val caps = expect(net, tmt) { validated == null || validated == it.caps.hasCapability(NET_CAPABILITY_VALIDATED) - } + }.caps expect(net, tmt) + if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)) { + expect(net, tmt) { it.info.upstreamNetwork == upstream } + } } // Backward compatibility for existing Java code. Use named arguments instead and remove all @@ -507,13 +546,15 @@ open class TestableNetworkCallback private constructor( val network: Network } + @JvmOverloads fun expectAvailableCallbacks( n: HasNetwork, suspended: Boolean, validated: Boolean, blocked: Boolean, + upstream: Network? = null, timeoutMs: Long - ) = expectAvailableCallbacks(n.network, suspended, validated, blocked, timeoutMs) + ) = expectAvailableCallbacks(n.network, suspended, validated, blocked, upstream, timeoutMs) fun expectAvailableAndSuspendedCallbacks(n: HasNetwork, expectValidated: Boolean) { expectAvailableAndSuspendedCallbacks(n.network, expectValidated) diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt index 3a76ad0060..235f7deda3 100644 --- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt +++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt @@ -38,10 +38,7 @@ import android.net.RouteInfo import android.os.Build import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRunner -import com.android.testutils.RecorderCallback.CallbackEntry.Available -import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus -import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged -import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged +import com.android.testutils.RecorderCallback.CallbackEntry.LocalInfoChanged import com.android.testutils.RecorderCallback.CallbackEntry.Lost import com.android.testutils.TestableNetworkCallback import org.junit.Test @@ -114,10 +111,7 @@ class CSLocalAgentTests : CSTest() { .build(), lnc = LocalNetworkConfig.Builder().build()) agent.connect() - cb.expect(agent.network) - cb.expect(agent.network) - cb.expect(agent.network) - cb.expect(agent.network) + cb.expectAvailableCallbacks(agent.network, validated = false) agent.sendNetworkCapabilities(NetworkCapabilities.Builder().build()) cb.expect(agent.network) @@ -125,10 +119,7 @@ class CSLocalAgentTests : CSTest() { .build(), lnc = null) agent2.connect() - cb.expect(agent2.network) - cb.expect(agent2.network) - cb.expect(agent2.network) - cb.expect(agent2.network) + cb.expectAvailableCallbacks(agent2.network, validated = false) agent2.sendNetworkCapabilities(NetworkCapabilities.Builder() .addCapability(NET_CAPABILITY_LOCAL_NETWORK) .build()) @@ -153,10 +144,11 @@ class CSLocalAgentTests : CSTest() { ) localAgent.connect() - cb.expect(localAgent.network) - cb.expect(localAgent.network) - cb.expect(localAgent.network) - cb.expect(localAgent.network) + cb.expectAvailableCallbacks(localAgent.network, validated = false) + + val wifiAgent = Agent(score = keepScore(), lp = lp("wifi0"), + nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)) + wifiAgent.connect() val newLnc = LocalNetworkConfig.Builder() .setUpstreamSelector(NetworkRequest.Builder() @@ -165,6 +157,21 @@ class CSLocalAgentTests : CSTest() { .build() localAgent.sendLocalNetworkConfig(newLnc) + cb.expect(localAgent.network) { + it.info.upstreamNetwork == wifiAgent.network + } + + localAgent.sendLocalNetworkConfig(LocalNetworkConfig.Builder().build()) + cb.expect(localAgent.network) { it.info.upstreamNetwork == null } + + localAgent.sendLocalNetworkConfig(newLnc) + cb.expect(localAgent.network) { + it.info.upstreamNetwork == wifiAgent.network + } + + wifiAgent.disconnect() + cb.expect(localAgent.network) { it.info.upstreamNetwork == null } + localAgent.disconnect() } @@ -204,6 +211,9 @@ class CSLocalAgentTests : CSTest() { wifiAgent.connect() cb.expectAvailableCallbacks(wifiAgent.network, validated = false) + cb.expect(localAgent.network) { + it.info.upstreamNetwork == wifiAgent.network + } clearInvocations(netd) val inOrder = inOrder(netd) @@ -218,6 +228,7 @@ class CSLocalAgentTests : CSTest() { wifiAgent2.connect() cb.expectAvailableCallbacks(wifiAgent2.network, validated = false) + cb.expect { it.info.upstreamNetwork == wifiAgent2.network } cb.expect { it.network == wifiAgent.network } inOrder.verify(netd).ipfwdAddInterfaceForward("local0", wifiIface2) @@ -252,7 +263,10 @@ class CSLocalAgentTests : CSTest() { nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)) wifiAgent.connect() - cb.expectAvailableCallbacksUnvalidated(wifiAgent) + cb.expectAvailableCallbacks(wifiAgent.network, validated = false) + cb.expect(localAgent.network) { + it.info.upstreamNetwork == wifiAgent.network + } clearInvocations(netd) wifiAgent.unregisterAfterReplacement(TIMEOUT_MS.toInt()) @@ -260,6 +274,7 @@ class CSLocalAgentTests : CSTest() { verify(netd).networkDestroy(wifiAgent.network.netId) verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi0") + cb.expect(localAgent.network) { it.info.upstreamNetwork == null } cb.expect { it.network == wifiAgent.network } } @@ -294,7 +309,12 @@ class CSLocalAgentTests : CSTest() { val wifiAgent = Agent(lp = lp("wifi0"), nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)) wifiAgent.connect() - cb.expectAvailableCallbacksUnvalidated(wifiAgent) + cb.expectAvailableCallbacks(wifiAgent.network, validated = false) + listOf(cb, localCb).forEach { + it.expect(localAgent.network) { + it.info.upstreamNetwork == wifiAgent.network + } + } verify(netd).ipfwdAddInterfaceForward("local0", "wifi0") @@ -303,8 +323,10 @@ class CSLocalAgentTests : CSTest() { val localAgent2 = Agent(nc = localNc, lp = lp("local0"), lnc = lnc, score = localScore) localAgent2.connect() - localCb.expectAvailableCallbacks(localAgent2.network, validated = false) - cb.expectAvailableCallbacks(localAgent2.network, validated = false) + localCb.expectAvailableCallbacks(localAgent2.network, + validated = false, upstream = wifiAgent.network) + cb.expectAvailableCallbacks(localAgent2.network, + validated = false, upstream = wifiAgent.network) cb.expect { it.network == localAgent.network } } @@ -316,9 +338,11 @@ class CSLocalAgentTests : CSTest() { val wifiAgent = Agent(lp = lp("wifi0"), nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)) wifiAgent.connect() - cb.expectAvailableCallbacksUnvalidated(wifiAgent) + cb.expectAvailableCallbacks(wifiAgent.network, validated = false) - // Set up a local agent that should forward its traffic to the best wifi upstream. + // Unregister wifi pending replacement, then set up a local agent that would have + // this network as its upstream. + wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS) val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK), lp = lp("local0"), lnc = LocalNetworkConfig.Builder() @@ -331,14 +355,18 @@ class CSLocalAgentTests : CSTest() { .build()) ) - // ...but destroy the wifi agent before connecting it - wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS) - + // Connect the local agent. The zombie wifi is its upstream, but the stack doesn't + // tell netd to add the forward since the wifi0 interface has gone. localAgent.connect() - cb.expectAvailableCallbacks(localAgent.network, validated = false) + cb.expectAvailableCallbacks(localAgent.network, + validated = false, upstream = wifiAgent.network) - verify(netd).ipfwdAddInterfaceForward("local0", "wifi0") - verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi0") + verify(netd, never()).ipfwdAddInterfaceForward("local0", "wifi0") + + // Disconnect wifi without a replacement. Expect an update with upstream null. + wifiAgent.disconnect() + verify(netd, never()).ipfwdAddInterfaceForward("local0", "wifi0") + cb.expect { it.info.upstreamNetwork == null } } @Test @@ -366,19 +394,34 @@ class CSLocalAgentTests : CSTest() { val wifiAgentDun = Agent(score = keepScore(), lp = lp("wifi1"), nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)) + val cb = TestableNetworkCallback() + cm.registerNetworkCallback(NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_LOCAL_NETWORK) + .build(), + cb) + cb.expectAvailableCallbacks(localAgent.network, validated = false) + val inOrder = inOrder(netd) inOrder.verify(netd, never()).ipfwdAddInterfaceForward(any(), any()) + cb.assertNoCallback() wifiAgent.connect() inOrder.verify(netd, never()).ipfwdAddInterfaceForward(any(), any()) + cb.assertNoCallback() cellAgentDun.connect() inOrder.verify(netd).ipfwdEnableForwarding(any()) inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "cell0") + cb.expect(localAgent.network) { + it.info.upstreamNetwork == cellAgentDun.network + } wifiAgentDun.connect() inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "cell0") inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi1") + cb.expect(localAgent.network) { + it.info.upstreamNetwork == wifiAgentDun.network + } // Make sure sending the same config again doesn't do anything repeat(5) { @@ -387,6 +430,10 @@ class CSLocalAgentTests : CSTest() { inOrder.verifyNoMoreInteractions() wifiAgentDun.disconnect() + cb.expect(localAgent.network) { it.info.upstreamNetwork == null } + cb.expect(localAgent.network) { + it.info.upstreamNetwork == cellAgentDun.network + } inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi1") // This can take a little bit of time because it needs to wait for the rematch inOrder.verify(netd, timeout(MEDIUM_TIMEOUT_MS)).ipfwdAddInterfaceForward("local0", "cell0") @@ -394,15 +441,35 @@ class CSLocalAgentTests : CSTest() { cellAgentDun.disconnect() inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "cell0") inOrder.verify(netd).ipfwdDisableForwarding(any()) + cb.expect(localAgent.network) { it.info.upstreamNetwork == null } val wifiAgentDun2 = Agent(score = keepScore(), lp = lp("wifi2"), nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)) wifiAgentDun2.connect() inOrder.verify(netd).ipfwdEnableForwarding(any()) inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi2") + cb.expect(localAgent.network) { + it.info.upstreamNetwork == wifiAgentDun2.network + } - localAgent.disconnect() + wifiAgentDun2.disconnect() inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi2") inOrder.verify(netd).ipfwdDisableForwarding(any()) + cb.expect(localAgent.network) { it.info.upstreamNetwork == null } + + val wifiAgentDun3 = Agent(score = keepScore(), lp = lp("wifi3"), + nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN)) + wifiAgentDun3.connect() + inOrder.verify(netd).ipfwdEnableForwarding(any()) + inOrder.verify(netd).ipfwdAddInterfaceForward("local0", "wifi3") + cb.expect(localAgent.network) { + it.info.upstreamNetwork == wifiAgentDun3.network + } + + localAgent.disconnect() + inOrder.verify(netd).ipfwdRemoveInterfaceForward("local0", "wifi3") + inOrder.verify(netd).ipfwdDisableForwarding(any()) + cb.expect(localAgent.network) + cb.assertNoCallback() } }