diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 3d7ea6944e..4f18fa207e 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1252,9 +1252,13 @@ public class ConnectivityManager { * {@link ConnectivityManager#registerNetworkAgent} * @hide */ - public static Network registerNetworkAgentResult( + public static NetworkAndAgentRegistryParcelable registerNetworkAgentResult( @Nullable final Network network, @Nullable final INetworkAgentRegistry registry) { - return network; + final NetworkAndAgentRegistryParcelable result = + new NetworkAndAgentRegistryParcelable(); + result.network = network; + result.registry = registry; + return result; } } @@ -3968,7 +3972,8 @@ public class ConnectivityManager { @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) - public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni, + public NetworkAndAgentRegistryParcelable registerNetworkAgent( + @NonNull INetworkAgent na, @NonNull NetworkInfo ni, @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId) { return registerNetworkAgent(na, ni, lp, nc, null /* localNetworkConfig */, score, config, @@ -3983,7 +3988,8 @@ public class ConnectivityManager { @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) - public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni, + public NetworkAndAgentRegistryParcelable registerNetworkAgent( + @NonNull INetworkAgent na, @NonNull NetworkInfo ni, @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId) { diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 47b33162d2..a270684c40 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -30,6 +30,7 @@ import android.net.LinkProperties; import android.net.LocalNetworkConfig; import android.net.Network; import android.net.NetworkAgentConfig; +import android.net.NetworkAndAgentRegistryParcelable; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; @@ -146,7 +147,8 @@ interface IConnectivityManager void declareNetworkRequestUnfulfillable(in NetworkRequest request); - Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp, + NetworkAndAgentRegistryParcelable registerNetworkAgent( + in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp, in NetworkCapabilities nc, in NetworkScore score, in LocalNetworkConfig localNetworkConfig, in NetworkAgentConfig config, in int factorySerialNumber); diff --git a/framework/src/android/net/INetworkAgent.aidl b/framework/src/android/net/INetworkAgent.aidl index fa5175c49f..c6beeca869 100644 --- a/framework/src/android/net/INetworkAgent.aidl +++ b/framework/src/android/net/INetworkAgent.aidl @@ -26,7 +26,7 @@ import android.net.INetworkAgentRegistry; * @hide */ oneway interface INetworkAgent { - void onRegistered(in INetworkAgentRegistry registry); + void onRegistered(); void onDisconnected(); void onBandwidthUpdateRequested(); void onValidationStatusChanged(int validationStatus, diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl index 61b27b5f0e..afdd1eec0b 100644 --- a/framework/src/android/net/INetworkAgentRegistry.aidl +++ b/framework/src/android/net/INetworkAgentRegistry.aidl @@ -30,7 +30,7 @@ import android.telephony.data.NrQosSessionAttributes; * Interface for NetworkAgents to send network properties. * @hide */ -oneway interface INetworkAgentRegistry { +interface INetworkAgentRegistry { void sendNetworkCapabilities(in NetworkCapabilities nc); void sendLinkProperties(in LinkProperties lp); // TODO: consider replacing this by "markConnected()" and removing diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index cefa1ea072..08f5ecdb7c 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -98,6 +98,7 @@ public abstract class NetworkAgent { @Nullable private volatile Network mNetwork; + // Null before the agent is registered @Nullable private volatile INetworkAgentRegistry mRegistry; @@ -121,6 +122,8 @@ public abstract class NetworkAgent { private NetworkInfo mNetworkInfo; @NonNull private final Object mRegisterLock = new Object(); + // TODO : move the preconnected queue to the system server and remove this + private boolean mConnected = false; /** * The ID of the {@link NetworkProvider} that created this object, or @@ -606,16 +609,16 @@ public abstract class NetworkAgent { public void handleMessage(Message msg) { switch (msg.what) { case EVENT_AGENT_CONNECTED: { - if (mRegistry != null) { - log("Received new connection while already connected!"); - } else { - if (VDBG) log("NetworkAgent fully connected"); - synchronized (mPreConnectedQueue) { - final INetworkAgentRegistry registry = (INetworkAgentRegistry) msg.obj; - mRegistry = registry; + // TODO : move the pre-connected queue to the system server, and remove + // handling this EVENT_AGENT_CONNECTED message. + synchronized (mPreConnectedQueue) { + if (mConnected) { + log("Received new connection while already connected!"); + } else { + if (VDBG) log("NetworkAgent fully connected"); for (RegistryAction a : mPreConnectedQueue) { try { - a.execute(registry); + a.execute(mRegistry); } catch (RemoteException e) { Log.wtf(LOG_TAG, "Communication error with registry", e); // Fall through @@ -623,6 +626,7 @@ public abstract class NetworkAgent { } mPreConnectedQueue.clear(); } + mConnected = true; } break; } @@ -631,7 +635,7 @@ public abstract class NetworkAgent { // let the client know CS is done with us. onNetworkUnwanted(); synchronized (mPreConnectedQueue) { - mRegistry = null; + mConnected = false; } break; } @@ -758,20 +762,32 @@ public abstract class NetworkAgent { } final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context .getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkAndAgentRegistryParcelable result; if (mInitialConfiguration.localNetworkConfig == null) { // Call registerNetworkAgent without localNetworkConfig argument to pass // android.net.cts.NetworkAgentTest#testAgentStartsInConnecting in old cts - mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), + result = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), new NetworkInfo(mInitialConfiguration.info), mInitialConfiguration.properties, mInitialConfiguration.capabilities, mInitialConfiguration.score, mInitialConfiguration.config, providerId); } else { - mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), + result = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), new NetworkInfo(mInitialConfiguration.info), mInitialConfiguration.properties, mInitialConfiguration.capabilities, mInitialConfiguration.localNetworkConfig, mInitialConfiguration.score, mInitialConfiguration.config, providerId); } + if (null == result && Process.isApplicationUid(Process.myUid())) { + // Let it slide in tests to allow mocking, since NetworkAndAgentRegistryParcelable + // is not public and can't be instantiated by CTS. The danger here is that if + // this happens in production for some reason the code will crash later instead + // of here. If this is a system app, it will still crash as expected. + Log.e(LOG_TAG, "registerNetworkAgent returned null. This agent will not work. " + + "Is ConnectivityManager a mockĀ ?"); + } else { + mNetwork = result.network; + mRegistry = result.registry; + } mInitialConfiguration = null; // All this memory can now be GC'd } return mNetwork; @@ -787,8 +803,8 @@ public abstract class NetworkAgent { } @Override - public void onRegistered(@NonNull INetworkAgentRegistry registry) { - mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_CONNECTED, registry)); + public void onRegistered() { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_CONNECTED)); } @Override @@ -913,11 +929,13 @@ public abstract class NetworkAgent { * * @hide */ - public INetworkAgent registerForTest(final Network network) { + public INetworkAgent registerForTest(final Network network, + final INetworkAgentRegistry registry) { log("Registering NetworkAgent for test"); synchronized (mRegisterLock) { mNetwork = network; mInitialConfiguration = null; + mRegistry = registry; } return new NetworkAgentBinder(mHandler); } @@ -958,7 +976,7 @@ public abstract class NetworkAgent { FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_MESSAGE_QUEUED_BEFORE_CONNECT ); } - if (mRegistry != null) { + if (mConnected) { try { action.execute(mRegistry); } catch (RemoteException e) { diff --git a/framework/src/android/net/NetworkAndAgentRegistryParcelable.aidl b/framework/src/android/net/NetworkAndAgentRegistryParcelable.aidl new file mode 100644 index 0000000000..8c01bbcda1 --- /dev/null +++ b/framework/src/android/net/NetworkAndAgentRegistryParcelable.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2025 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.net.INetworkAgentRegistry; +import android.net.Network; + +/** + * A pair of Network and NetworkAgentRegistry. + * + * {@hide} + */ +@JavaDerive(toString=true) +parcelable NetworkAndAgentRegistryParcelable { + Network network; + INetworkAgentRegistry registry; +} diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index b2e49e7f50..6453d765b6 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -228,6 +228,7 @@ import android.net.NattSocketKeepalive; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkAgentConfig; +import android.net.NetworkAndAgentRegistryParcelable; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; @@ -9316,7 +9317,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { * @param providerId the ID of the provider owning this NetworkAgent. * @return the network created for this agent. */ - public Network registerNetworkAgent(INetworkAgent na, + public NetworkAndAgentRegistryParcelable registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, @@ -9359,7 +9360,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private Network registerNetworkAgentInternal(INetworkAgent na, NetworkInfo networkInfo, + private NetworkAndAgentRegistryParcelable registerNetworkAgentInternal( + INetworkAgent na, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, @Nullable LocalNetworkConfig localNetworkConfig, int providerId, @@ -9391,8 +9393,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { // NetworkAgentInfo registration will finish when the NetworkMonitor is created. // If the network disconnects or sends any other event before that, messages are deferred by // NetworkAgent until nai.connect(), which will be called when finalizing the - // registration. - return nai.network; + // registration. TODO : have NetworkAgentInfo defer them instead. + final NetworkAndAgentRegistryParcelable result = new NetworkAndAgentRegistryParcelable(); + result.network = nai.network; + result.registry = nai.getRegistry(); + return result; } private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) { diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java index e762a8e866..d6bd659946 100644 --- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java +++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java @@ -639,6 +639,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { private final ConnectivityService.Dependencies mConnServiceDeps; private final Context mContext; private final Handler mHandler; + private final NetworkAgentMessageHandler mRegistry; private final QosCallbackTracker mQosCallbackTracker; private final INetd mNetd; @@ -673,6 +674,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { mNetd = netd; mContext = context; mHandler = handler; + mRegistry = new NetworkAgentMessageHandler(mHandler); this.factorySerialNumber = factorySerialNumber; this.creatorUid = creatorUid; mLingerDurationMs = lingerDurationMs; @@ -701,7 +703,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { public void notifyRegistered() { try { networkAgent.asBinder().linkToDeath(mDeathMonitor, 0); - networkAgent.onRegistered(new NetworkAgentMessageHandler(mHandler)); + networkAgent.onRegistered(); } catch (RemoteException e) { Log.e(TAG, "Error registering NetworkAgent", e); maybeUnlinkDeathMonitor(); @@ -1117,6 +1119,13 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { return mNetworkMonitor; } + /** + * Get the registry in this NetworkAgentInfo. + */ + public INetworkAgentRegistry getRegistry() { + return mRegistry; + } + // Functions for manipulating the requests satisfied by this network. // // These functions must only called on ConnectivityService's main thread. diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt index 5e035a2723..678e1cab55 100644 --- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt @@ -154,10 +154,10 @@ import java.nio.ByteBuffer import java.security.MessageDigest import java.time.Duration import java.util.Arrays -import java.util.Random import java.util.UUID import java.util.concurrent.Executors import kotlin.collections.ArrayList +import kotlin.random.Random import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse @@ -267,7 +267,7 @@ class NetworkAgentTest { private class FakeConnectivityService { val mockRegistry = mock(INetworkAgentRegistry::class.java) private var agentField: INetworkAgent? = null - private val registry = object : INetworkAgentRegistry.Stub(), + val registry: INetworkAgentRegistry = object : INetworkAgentRegistry.Stub(), INetworkAgentRegistry by mockRegistry { // asBinder has implementations in both INetworkAgentRegistry.Stub and mockRegistry, so // it needs to be disambiguated. Just fail the test as it should be unused here. @@ -284,7 +284,7 @@ class NetworkAgentTest { fun connect(agent: INetworkAgent) { this.agentField = agent - agent.onRegistered(registry) + agent.onRegistered() } fun disconnect() = agent.onDisconnected() @@ -413,7 +413,8 @@ class NetworkAgentTest { } private fun createNetworkAgentWithFakeCS() = createNetworkAgent().also { - mFakeConnectivityService.connect(it.registerForTest(Network(FAKE_NET_ID))) + val binder = it.registerForTest(Network(FAKE_NET_ID), mFakeConnectivityService.registry) + mFakeConnectivityService.connect(binder) } private fun TestableNetworkAgent.expectPostConnectionCallbacks( @@ -1628,7 +1629,7 @@ class NetworkAgentTest { val s = Os.socket(AF_INET6, SOCK_DGRAM, 0) net.bindSocket(s) val content = ByteArray(16) - Random().nextBytes(content) + Random.nextBytes(content) Os.sendto(s, ByteBuffer.wrap(content), 0, REMOTE_ADDRESS, 7 /* port */) val match = reader.poll(DEFAULT_TIMEOUT_MS) { val udpStart = IPV6_HEADER_LEN + UDP_HEADER_LEN