diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 3a7d070670..f8a1293a34 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -6856,68 +6856,4 @@ public class ConnectivityManager { throw e.rethrowFromSystemServer(); } } - - /** - * Register QUIC UDP socket and UDP payload that can close QUIC connection. - * - * When the app loses network access (e.g. due to freezer or firewall chain), - * ConnectivityService 1)destroys the registered UDP socket by sending a SOCK_DESTROY netlink - * message and 2)sends the registered UDP payload to the server. - * This prevents unnecessary modem wakeups caused by packets from the server after the apps - * lose network access. - * - * Additionally, to close QUIC connection for apps that crash or are killed, ConnectivityService - * sends the registered UDP payload when it receives a socket destroy diag message for the - * registered socket. - * - * If apps call this method, they must call {@link #unregisterQuicConnectionClosePayload} before - * they close the QUIC connection and the socket. - * Otherwise, the registered UDP payload will be sent to the server, even if the QUIC connection - * was already properly closed. This is because ConnectivityService cannot distinguish whether - * the socket was closed due to the app being killed or because the app forgets to call - * {@link #unregisterQuicConnectionClosePayload}. - * - * The registered payload will be sent using the source and destination addresses and ports of - * the connected UDP socket provided. So, the app must not reuse the same socket to reconnect - * to a different server, even if first calls unregisterQuicConnectionClosePayload. This is - * because this method operates asynchronously. - * - * If the number of registered socket and payload hits the limit, ConnectivityService silently - * ignores new registration. ConnectivityService cannot know about this synchronously, but the - * caller couldn't really do anything with this information anyway. - * TODO: Consider adding a result listener if this API becomes public. - * - * @param pfd The {@link ParcelFileDescriptor} for the connected UDP socket. - * The registered payload will be sent using the source and destination addresses and - * ports of this connected UDP socket. - * @param payload The UDP payload that can close QUIC connection. - * - * @hide - */ - public void registerQuicConnectionClosePayload(final ParcelFileDescriptor pfd, - final byte[] payload) { - try { - mService.registerQuicConnectionClosePayload(pfd, payload); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Unregister the QUIC socket previously registered by - * {@link #registerQuicConnectionClosePayload} - * - * This method is a no-op if the provided UDP socket is not registered. - * - * @param pfd The {@link ParcelFileDescriptor} for the UDP socket. - * - * @hide - */ - public void unregisterQuicConnectionClosePayload(final ParcelFileDescriptor pfd) { - try { - mService.unregisterQuicConnectionClosePayload(pfd); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } } diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 0146e729a7..a270684c40 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -266,7 +266,4 @@ interface IConnectivityManager long getEnabledConnectivityManagerFeatures(); boolean isConnectivityServiceFeatureEnabledForTesting(String featureFlag); - - oneway void registerQuicConnectionClosePayload(in ParcelFileDescriptor pfd, in byte[] payload); - oneway void unregisterQuicConnectionClosePayload(in ParcelFileDescriptor pfd); } diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 7f317930a4..45b431f1e7 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -156,7 +156,6 @@ import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPer import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf; import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE; import static com.android.server.connectivity.ConnectivityFlags.CELLULAR_DATA_INACTIVITY_TIMEOUT; -import static com.android.server.connectivity.ConnectivityFlags.CLOSE_QUIC_CONNECTION; import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS; import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING; import static com.android.server.connectivity.ConnectivityFlags.NAMESPACE_TETHERING_BOOT; @@ -390,7 +389,6 @@ import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProfileNetworkPreferenceInfo; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; -import com.android.server.connectivity.QuicConnectionCloser; import com.android.server.connectivity.SatelliteAccessController; import com.android.server.connectivity.UidRangeUtils; import com.android.server.connectivity.VpnNetworkPreferenceInfo; @@ -1106,13 +1104,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Flag to drop packets to VPN addresses ingressing via non-VPN interfaces. private final boolean mIngressToVpnAddressFiltering; - // Flag to close QUIC connection for registered sockets when apps lose network access. - private final boolean mCloseQuicConnection; - - // This is null if mCloseQuicConnection is false - @Nullable - private final QuicConnectionCloser mQuicConnectionCloser; - /** * Implements support for the legacy "one network per network type" model. * @@ -1834,14 +1825,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { handler.sendMessageDelayed( handler.obtainMessage(EVENT_INITIAL_EVALUATION_TIMEOUT, network), delayMs); } - - /** - * Create {@link QuicConnectionCloser}. - */ - public QuicConnectionCloser makeQuicConnectionCloser( - final SparseArray networkForNetId, final Handler handler) { - return new QuicConnectionCloser(networkForNetId, handler); - } } public ConnectivityService(Context context) { @@ -2150,13 +2133,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { && mDeps.isFeatureNotChickenedOut(mContext, INGRESS_TO_VPN_ADDRESS_FILTERING); mL2capNetworkProvider = mDeps.makeL2capNetworkProvider(mContext); - - mCloseQuicConnection = mDeps.isFeatureEnabled(context, CLOSE_QUIC_CONNECTION); - if (mCloseQuicConnection) { - mQuicConnectionCloser = mDeps.makeQuicConnectionCloser(mNetworkForNetId, mHandler); - } else { - mQuicConnectionCloser = null; - } } /** @@ -3711,11 +3687,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } catch (SocketException | InterruptedIOException | ErrnoException e) { loge("Failed to destroy sockets: " + e); } - - if (mCloseQuicConnection) { - mQuicConnectionCloser.closeQuicConnectionByUids(uids); - } - mDestroySocketPendingUids.clear(); } @@ -4545,12 +4516,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetworkActivityTracker.dump(pw); pw.decreaseIndent(); - pw.println(); - pw.println("Close QUIC connection: " + mCloseQuicConnection); - if (mCloseQuicConnection) { - mQuicConnectionCloser.dump(pw); - } - pw.println(); pw.println("Multicast routing supported: " + (mMulticastRoutingCoordinatorService != null)); @@ -15160,33 +15125,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { return mIngressToVpnAddressFiltering; case QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER: return mQueueNetworkAgentEventsInSystemServer; - case CLOSE_QUIC_CONNECTION: - return mCloseQuicConnection; default: throw new IllegalArgumentException("Unknown flag: " + featureFlag); } } - - @Override - public void registerQuicConnectionClosePayload(final ParcelFileDescriptor pfd, - final byte[] payload) { - if (!mCloseQuicConnection) { - IoUtils.closeQuietly(pfd); - return; - } - // pfd is closed by registerQuicConnectionClosePayload - mQuicConnectionCloser.registerQuicConnectionClosePayload(mDeps.getCallingUid(), - pfd, payload); - } - - @Override - public void unregisterQuicConnectionClosePayload(final ParcelFileDescriptor pfd) { - if (!mCloseQuicConnection) { - IoUtils.closeQuietly(pfd); - return; - } - // pfd is closed by unregisterQuicConnectionClosePayload - mQuicConnectionCloser.unregisterQuicConnectionClosePayload(pfd); - } - } diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java index fd24951049..74bd2351ac 100644 --- a/service/src/com/android/server/connectivity/ConnectivityFlags.java +++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java @@ -65,8 +65,6 @@ public final class ConnectivityFlags { public static final String QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER = "queue_network_agent_events_in_system_server"; - public static final String CLOSE_QUIC_CONNECTION = "close_quic_connection"; - private boolean mNoRematchAllRequestsOnRegister; /** diff --git a/service/src/com/android/server/connectivity/QuicConnectionCloser.java b/service/src/com/android/server/connectivity/QuicConnectionCloser.java deleted file mode 100644 index ce5d61c92b..0000000000 --- a/service/src/com/android/server/connectivity/QuicConnectionCloser.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * 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 com.android.server.connectivity; - -import static android.system.OsConstants.ENOENT; -import static android.system.OsConstants.SOL_SOCKET; -import static android.system.OsConstants.SO_SNDTIMEO; - -import android.annotation.NonNull; -import android.net.Network; -import android.net.NetworkUtils; -import android.os.Handler; -import android.os.ParcelFileDescriptor; -import android.system.ErrnoException; -import android.system.Os; -import android.system.StructTimeval; -import android.util.ArrayMap; -import android.util.IndentingPrintWriter; -import android.util.Log; -import android.util.SparseArray; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.net.module.util.HandlerUtils; -import com.android.net.module.util.SharedLog; -import com.android.net.module.util.SkDestroyListener; -import com.android.net.module.util.netlink.InetDiagMessage; - -import libcore.io.IoUtils; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.DatagramSocket; -import java.net.InetSocketAddress; -import java.net.SocketException; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - -public class QuicConnectionCloser { - private static final String TAG = QuicConnectionCloser.class.getSimpleName(); - - private static final int SOCKET_WRITE_TIMEOUT_MS = 100; - - // Map from socket cookie to QUIC connection close information - private final Map - mRegisteredQuicConnectionCloseInfos = new ArrayMap<>(); - - // The maximum number of QUIC connection close information entries that can be registered - // concurrently. - private static final int MAX_REGISTERED_QUIC_CONNECTION_CLOSE_INFO = 1000; - - private final Handler mHandler; - - // Reference to ConnectivityService#mNetworkForNetId, must be synchronized on itself. - // mHandler in this class is associated with the ConnectivityService handler thread. - // The only code that can update mNetworkForNetId runs on that thread, so no entries can be - // updated while the code in this class is running on mHandler thread. - @GuardedBy("mNetworkForNetId") - private final SparseArray mNetworkForNetId; - - @NonNull - private final Dependencies mDeps; - - /** - * Class to store the necessary information for closing a QUIC connection. - */ - private static class QuicConnectionCloseInfo { - public final int uid; - public final int netId; - public final long cookie; - public final InetSocketAddress src; - public final InetSocketAddress dst; - public final byte[] payload; - - QuicConnectionCloseInfo(int uid, int netId, long cookie, InetSocketAddress src, - InetSocketAddress dst, byte[] payload) { - this.uid = uid; - this.netId = netId; - this.cookie = cookie; - this.src = src; - this.dst = dst; - this.payload = payload; - } - - @Override - public String toString() { - return "QuicConnectionCloseInfo{" - + "uid: " + uid - + ", netId: " + netId - + ", cookie: " + cookie - + ", src: " + src - + ", dst: " + dst - + ", payload length: " + payload.length - + "}"; - } - } - - public static class Dependencies { - /** - * Send a UDP packet with the specified source and destination address and port over the - * specified network. - * - * @param network The {@link Network} over which the UDP packet will be sent. - * @param src The source {@link InetSocketAddress} of the UDP packet to be sent. - * @param dst The destination {@link InetSocketAddress} of the UDP packet to be sent. - * @param payload The UDP payload to be sent. - */ - public void sendQuicConnectionClosePayload(final Network network, - final InetSocketAddress src, final InetSocketAddress dst, final byte[] payload) - throws IOException, ErrnoException { - final DatagramSocket socket = new DatagramSocket(src); - network.bindSocket(socket); - socket.connect(dst); - Os.setsockoptTimeval(socket.getFileDescriptor$(), SOL_SOCKET, SO_SNDTIMEO, - StructTimeval.fromMillis(SOCKET_WRITE_TIMEOUT_MS)); - Os.write(socket.getFileDescriptor$(), payload, 0 /* byteOffset */, payload.length); - } - - /** - * Call {@link InetDiagMessage#destroyUdpSocket} - */ - public void destroyUdpSocket(final InetSocketAddress src, final InetSocketAddress dst, - final long cookie) - throws SocketException, InterruptedIOException, ErrnoException { - InetDiagMessage.destroyUdpSocket(src, dst, 0 /* ifindex */, cookie); - } - - /** - * Call {@link SkDestroyListener#makeSkDestroyListener} - */ - public SkDestroyListener makeSkDestroyListener(final Consumer consumer, - final Handler handler) { - return SkDestroyListener.makeSkDestroyListener(consumer, false /* monitorTcpSocket */, - true /* monitorUdpSocket */, handler, new SharedLog(TAG)); - } - - /** - * Call {@link NetworkUtils#getSocketCookie} - */ - public long getSocketCookie(final FileDescriptor fd) throws ErrnoException { - return NetworkUtils.getSocketCookie(fd); - } - - /** - * Call {@link Os#getsockoptInt} - */ - public int getsockoptInt(final FileDescriptor fd, final int level, final int option) - throws ErrnoException { - return Os.getsockoptInt(fd, level, option); - } - - /** - * Call {@link Os#getsockname}} - */ - public InetSocketAddress getsockname(final FileDescriptor fd) throws ErrnoException { - return (InetSocketAddress) Os.getsockname(fd); - } - - /** - * Call {@link Os#getpeername} - */ - public InetSocketAddress getpeername(final FileDescriptor fd) throws ErrnoException { - return (InetSocketAddress) Os.getpeername(fd); - } - } - - public QuicConnectionCloser(final SparseArray networkForNetId, - final Handler handler) { - this(networkForNetId, handler, new Dependencies()); - } - - @VisibleForTesting - public QuicConnectionCloser(final SparseArray networkForNetId, - final Handler handler, final Dependencies deps) { - mNetworkForNetId = networkForNetId; - mHandler = handler; - mDeps = deps; - - // handleUdpSocketDestroy must be posted to the thread to avoid racing with - // handleUnregisterQuicConnectionCloseInfo, even though they both run on the same thread. - // Specifically, the following can happen: - // unregisterQuicConnectionClosePayload posts handleUnregisterQuicConnectionCloseInfo to - // the handler and then closes the fd. - // The close() will cause the kernel to enqueue a netlink message to the SkDestroyHandler's - // fd. - // It's possible that the MessageQueue of the handler sees both the SkDestroyHandler's fd - // and the MessageQueue fd go active at the same time, and chooses to run the netlink - // event first. - // As a result, handleUdpSocketDestroy runs before handleUnregisterQuicConnectionCloseInfo, - // and the code incorrectly sends a close packet for a socket that the app has already - // unregistered. - // Posting handleUdpSocketDestroy to the handler ensures that it always runs after - // handleUnregisterQuicConnectionCloseInfo. It doesn't matter if the - // handleUdpSocketDestroy is delayed, because it will only send a packet if - // mRegisteredQuicConnectionCloseInfos contains the socket cookie, and socket cookies are - // never reused. - final SkDestroyListener udpSkDestroyListener = mDeps.makeSkDestroyListener( - (inetDiagMessage) -> handler.post(() -> handleUdpSocketDestroy(inetDiagMessage)), - handler); - handler.post(udpSkDestroyListener::start); - } - - private void ensureRunningOnHandlerThread() { - HandlerUtils.ensureRunningOnHandlerThread(mHandler); - } - - /** - * Close registered QUIC connection by uids - * - * @param uids target uids to close QUIC connections - */ - public void closeQuicConnectionByUids(final Set uids) { - ensureRunningOnHandlerThread(); - - for (Iterator> it = - mRegisteredQuicConnectionCloseInfos.entrySet().iterator(); it.hasNext();) { - final QuicConnectionCloseInfo info = it.next().getValue(); - if (uids.contains(info.uid)) { - closeQuicConnection(info, true /* destroySocket */); - it.remove(); - } - } - } - - private NetworkAgentInfo getNetworkAgentInfoForNetId(int netId) { - synchronized (mNetworkForNetId) { - return mNetworkForNetId.get(netId); - } - } - - private void closeQuicConnection(final QuicConnectionCloseInfo info, - final boolean destroySocket) { - ensureRunningOnHandlerThread(); - - Log.d(TAG, "Close QUIC socket for " + info + ", destroySocket=" + destroySocket); - final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(info.netId); - if (nai == null || !nai.linkProperties.getAddresses().contains(info.src.getAddress())) { - // The device should not send a packet with an unused source address. - return; - } - - if (destroySocket) { - try { - mDeps.destroyUdpSocket(info.src, info.dst, info.cookie); - } catch (ErrnoException | SocketException | InterruptedIOException e) { - if (e instanceof ErrnoException && ((ErrnoException) e).errno == ENOENT) { - // This can happen if the socket is already closed, but unregister message is - // not processed yet. - return; - } - Log.e(TAG, "Failed to destroy QUIC socket for " + info + ": " + e); - return; - } - } - - try { - mDeps.sendQuicConnectionClosePayload(nai.network(), info.src, info.dst, info.payload); - } catch (ErrnoException | IOException e) { - Log.e(TAG, "Failed to send registered QUIC connection close payload for " - + info + ": " + e); - } - } - - /** - * Close QUIC connection if the registered socket was destroyed - * - * @param inetDiagMessage {@link InetDiagMessage} received from kernel - */ - private void handleUdpSocketDestroy(final InetDiagMessage inetDiagMessage) { - ensureRunningOnHandlerThread(); - - final long cookie = inetDiagMessage.inetDiagMsg.id.cookie; - final QuicConnectionCloseInfo info = mRegisteredQuicConnectionCloseInfos.remove(cookie); - if (info == null) { - // Destroyed socket is not registered or already unregistered. - return; - } - - // App registered a QUIC connection close payload and this socket, but the socket was - // closed before the socket was unregistered. - // This can happen if the app crashes or is killed. - closeQuicConnection(info, false /* destroySocket */); - } - - /** - * Register the QUIC connection close information - * - * @param info {@link QuicConnectionCloseInfo} to register - */ - private void handleRegisterQuicConnectionCloseInfo(final QuicConnectionCloseInfo info) { - ensureRunningOnHandlerThread(); - - if (mRegisteredQuicConnectionCloseInfos.size() - >= MAX_REGISTERED_QUIC_CONNECTION_CLOSE_INFO) { - Log.e(TAG, "Failed to register QUIC connection close information." - + " number of registered information exceeded " - + MAX_REGISTERED_QUIC_CONNECTION_CLOSE_INFO); - return; - } - mRegisteredQuicConnectionCloseInfos.put(info.cookie, info); - } - - // TODO: Use OsConstants.SO_MARK once this API is available - private static final int SO_MARK = 36; - - /** - * Register QUIC socket and connection close payload - * - * @param uid The uid of the socket owner - * @param pfd The {@link ParcelFileDescriptor} for the connected UDP socket. - * @param payload The UDP payload that can close QUIC connection. - */ - public void registerQuicConnectionClosePayload(final int uid, final ParcelFileDescriptor pfd, - final byte[] payload) { - try { - final FileDescriptor fd = pfd.getFileDescriptor(); - // See FWMARK_NET_ID_MASK in Fwmark.h - final int netId = mDeps.getsockoptInt(fd, SOL_SOCKET, SO_MARK) & 0xffff; - // The peer name could change if the socket is reconnected to a different server. - // But, it is the caller's responsibility to avoid this, as explicitly stated in the - // Javadoc of ConnectivityManager#registerQuicConnectionClosePayload. - final QuicConnectionCloseInfo info = new QuicConnectionCloseInfo( - uid, - netId, - mDeps.getSocketCookie(fd), - mDeps.getsockname(fd), - mDeps.getpeername(fd), - payload); - mHandler.post(() -> handleRegisterQuicConnectionCloseInfo(info)); - } catch (ErrnoException e) { - Log.e(TAG, "Failed to register QUIC connection close information", e); - } finally { - // |pfd| must remain open until the message is posted to the handler thread, because - // doing so ensures that the socket is not closed even if the app calls close(). - // Keeping it open avoids the following threading issue with SkDestroyListener : - // If the PFD was closed before posting the message, then SkDestroyListener might - // get a "socket destroyed" message from Netlink immediately and post to the handler - // thread before this method posts its own message, resulting in a leak of the - // mRegisteredQuicConnectionCloseInfo entry. - // While the PFD is held, Netlink will not send the socket destroy message to - // SkDestroyListener, and thus the message posted by SkDestroyListener as - // a reaction to it will be processed after the message posted by this method. - IoUtils.closeQuietly(pfd); - } - } - - /** - * Unregister the QUIC connection close information - * - * @param cookie cookie of the socket whose connection close information should be unregistered - */ - private void handleUnregisterQuicConnectionCloseInfo(final long cookie) { - ensureRunningOnHandlerThread(); - - mRegisteredQuicConnectionCloseInfos.remove(cookie); - } - - /** - * Unregister the QUIC socket - * - * @param pfd The {@link ParcelFileDescriptor} for the UDP socket. - */ - public void unregisterQuicConnectionClosePayload(final ParcelFileDescriptor pfd) { - try { - final long cookie = mDeps.getSocketCookie(pfd.getFileDescriptor()); - mHandler.post(() -> handleUnregisterQuicConnectionCloseInfo(cookie)); - } catch (ErrnoException e) { - Log.e(TAG, "Failed to unregister QUIC connection close information: " + e); - } finally { - // |pfd| must be closed after posting the message to the handler thread to avoid the - // threading issue with SkDestroyListener. - // See the comment in registerQuicConnectionClosePayload. - IoUtils.closeQuietly(pfd); - } - } - - /** - * Dump QUIC connection closer information - */ - public void dump(final IndentingPrintWriter pw) { - pw.println("Registered QUIC connection close information: " - + mRegisteredQuicConnectionCloseInfos.size()); - pw.increaseIndent(); - for (QuicConnectionCloseInfo info: mRegisteredQuicConnectionCloseInfos.values()) { - pw.println(info); - } - pw.decreaseIndent(); - } -} diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java index e20e457b01..87c2b9e5d1 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java @@ -21,7 +21,6 @@ import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.ACCESS_NETWORK_STATE; import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; -import static android.Manifest.permission.MANAGE_TEST_NETWORKS; import static android.Manifest.permission.NETWORK_FACTORY; import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.NETWORK_SETUP_WIZARD; @@ -101,11 +100,6 @@ import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERE import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import static android.system.OsConstants.AF_UNSPEC; -import static android.system.OsConstants.ECONNABORTED; -import static android.system.OsConstants.EDESTADDRREQ; -import static android.system.OsConstants.IPPROTO_UDP; -import static android.system.OsConstants.POLLIN; -import static android.system.OsConstants.SOCK_DGRAM; import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity; import static com.android.compatibility.common.util.SystemUtil.runShellCommand; @@ -188,9 +182,7 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; -import android.os.ParcelFileDescriptor; import android.os.Process; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; @@ -198,9 +190,6 @@ import android.os.VintfRuntimeInfo; import android.platform.test.annotations.AppModeFull; import android.provider.DeviceConfig; import android.provider.Settings; -import android.system.ErrnoException; -import android.system.Os; -import android.system.StructPollfd; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -216,10 +205,6 @@ import com.android.internal.util.ArrayUtils; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.DnsPacket; -import com.android.net.module.util.Struct; -import com.android.net.module.util.structs.Ipv4Header; -import com.android.net.module.util.structs.Ipv6Header; -import com.android.net.module.util.structs.UdpHeader; import com.android.networkstack.apishim.ConnectivityManagerShimImpl; import com.android.networkstack.apishim.ConstantsShim; import com.android.networkstack.apishim.NetworkInformationShimImpl; @@ -243,7 +228,6 @@ import com.android.testutils.TestableNetworkCallback; import junit.framework.AssertionFailedError; -import libcore.io.IoUtils; import libcore.io.Streams; import org.junit.After; @@ -269,7 +253,6 @@ import java.net.MalformedURLException; import java.net.Socket; import java.net.SocketException; import java.net.URL; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -369,15 +352,6 @@ public class ConnectivityManagerTest { // Re-connecting to the AP, obtaining an IP address, revalidating can take a long time private static final long WIFI_CONNECT_TIMEOUT_MS = 60_000L; - // Timeout for waiting the QUIC connection close information registration/unregistration - private static final long QUIC_CONNECTION_CLOSE_INFO_REGISTRATION_TIMEOUT_MS = 500L; - - // Timeout for waiting the QUIC connection close packet to be sent - private static final long QUIC_CONNECTION_CLOSE_PACKET_TIMEOUT_MS = 200L; - - // Name of the feature flag for closing quic connection - private static final String CLOSE_QUIC_CONNECTION = "close_quic_connection"; - private Context mContext; private Instrumentation mInstrumentation; private ConnectivityManager mCm; @@ -2415,8 +2389,8 @@ public class ConnectivityManagerTest { waitForAvailable(cb); } - private CallbackEntry.Available waitForAvailable(@NonNull final TestableNetworkCallback cb) { - return cb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS, + private void waitForAvailable(@NonNull final TestableNetworkCallback cb) { + cb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS, c -> c instanceof CallbackEntry.Available); } @@ -4157,302 +4131,4 @@ public class ConnectivityManagerTest { assertThrows(UnsupportedOperationException.class, () -> mCm.tether("iface")); assertThrows(UnsupportedOperationException.class, () -> mCm.untether("iface")); } - - private ParcelFileDescriptor setupTestNetworkAndGetFd() { - return runWithShellPermissionIdentity(() -> { - final TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class); - final List linkAddresses = List.of(new LinkAddress("192.0.2.2/24"), - new LinkAddress("2001:db8:1:2::2/64")); - final TestNetworkInterface iface = tnm.createTunInterface(linkAddresses); - tnm.setupTestNetwork(iface.getInterfaceName(), new Binder()); - return iface.getFileDescriptor(); - }, MANAGE_TEST_NETWORKS); - } - - private Network getTestNetwork() { - final TestableNetworkCallback callback = networkCallbackRule.requestNetwork( - new NetworkRequest.Builder() - .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) - .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) - .addTransportType(TRANSPORT_TEST) - .build() - ); - return waitForAvailable(callback).getNetwork(); - } - - private void waitForQuicConnectionCloseInfoRegistration(final int myUid, - final boolean expectRegistered) - throws ErrnoException, RemoteException, InterruptedException { - final long timeout = SystemClock.elapsedRealtime() - + QUIC_CONNECTION_CLOSE_INFO_REGISTRATION_TIMEOUT_MS; - while (timeout > SystemClock.elapsedRealtime()) { - if (DumpTestUtils.dumpServiceWithShellPermission( - Context.CONNECTIVITY_SERVICE, "--short") - .contains("QuicConnectionCloseInfo{uid: " + myUid) == expectRegistered) { - return; - } - Thread.sleep(50); - } - fail("Failed to register/unregister QUIC connection close information in " - + QUIC_CONNECTION_CLOSE_INFO_REGISTRATION_TIMEOUT_MS - + "ms, expectRegistered=" + expectRegistered); - } - - private void doTestRegisterQuicConnectionClosePayload(final boolean isV6, - final boolean unregister, final boolean closeSocket, final boolean blockNetwork, - final boolean expectPacketSent) throws Exception { - final InetAddress dstAddress = isV6 ? InetAddresses.parseNumericAddress("2001:db8:1:2::3") - : InetAddresses.parseNumericAddress("192.0.2.3"); - final InetSocketAddress dstSockAddress = new InetSocketAddress(dstAddress, 443); - final int myUid = Process.myUid(); - - final ParcelFileDescriptor tunFd = setupTestNetworkAndGetFd(); - final Network testNetwork = getTestNetwork(); - - // Firewall chain status will be restored after the test. - final boolean wasChainEnabled = runWithShellPermissionIdentity(() -> - mCm.getFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND), NETWORK_SETTINGS); - final int previousUidFirewallRule = runWithShellPermissionIdentity(() -> - mCm.getUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, myUid), NETWORK_SETTINGS); - runWithShellPermissionIdentity(() -> { - mCm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true /* enable */); - mCm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, myUid, FIREWALL_RULE_ALLOW); - }, NETWORK_SETTINGS); - - final FileDescriptor sock = Os.socket(isV6 ? AF_INET6 : AF_INET, SOCK_DGRAM, IPPROTO_UDP); - final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(sock.getInt$()); - testNetwork.bindSocket(sock); - Os.connect(sock, dstSockAddress); - final InetSocketAddress srcSockAddress = (InetSocketAddress) Os.getsockname(sock); - - testAndCleanup(() -> { - final Random random = new Random(); - final byte[] payload = new byte[100]; - random.nextBytes(payload); - - // register/unregisterQuicConnectionClosePayload are oneway binder calls, - // while setUidFirewallRule is a two-way binder call. So if - // setUidFirewallRule is called immediately after them, it's possible that - // setUidFirewallRule could be processed before them. - // To ensure that uid networking is blocked after the connection close information - // is registered/unregistered, wait for the registration/unregistration. - mCm.registerQuicConnectionClosePayload(pfd, payload); - waitForQuicConnectionCloseInfoRegistration(myUid, true /* expectRegistered */); - if (unregister) { - mCm.unregisterQuicConnectionClosePayload(pfd); - waitForQuicConnectionCloseInfoRegistration(myUid, false /* expectRegistered */); - } - - if (closeSocket) { - Os.close(sock); - pfd.close(); - } - - if (blockNetwork) { - runWithShellPermissionIdentity(() -> - mCm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, myUid, - FIREWALL_RULE_DENY), NETWORK_SETTINGS); - } - - final long timeout = SystemClock.elapsedRealtime() - + QUIC_CONNECTION_CLOSE_PACKET_TIMEOUT_MS; - long remainingTimeMs; - while ((remainingTimeMs = timeout - SystemClock.elapsedRealtime()) > 0) { - final StructPollfd pollfd = new StructPollfd(); - pollfd.events = (short) POLLIN; - pollfd.fd = tunFd.getFileDescriptor(); - final int ret = Os.poll(new StructPollfd[] { pollfd }, (int) remainingTimeMs); - if (ret == 0) { - continue; - } - - final byte[] recvData = new byte[200]; - final int readSize = Os.read(tunFd.getFileDescriptor(), recvData, - 0 /* byteOffset */, recvData.length); - - if (readSize < payload.length - || !Arrays.equals(payload, 0, payload.length, - recvData, readSize - payload.length, readSize)) { - continue; - } - // If the control comes here then the payload was received, otherwise poll would - // have returned 0 or the test above would have matched it and gone to continue. - if (!expectPacketSent) { - fail("Unexpectedly received the QUIC connection close packet."); - } - - final ByteBuffer buf = ByteBuffer.wrap(recvData, 0 /* offset */, readSize); - if (isV6) { - final Ipv6Header header = Struct.parse(Ipv6Header.class, buf); - assertEquals(srcSockAddress.getAddress(), header.srcIp); - assertEquals(dstSockAddress.getAddress(), header.dstIp); - } else { - final Ipv4Header header = Struct.parse(Ipv4Header.class, buf); - assertEquals(srcSockAddress.getAddress(), header.srcIp); - assertEquals(dstSockAddress.getAddress(), header.dstIp); - } - final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf); - assertEquals(srcSockAddress.getPort(), udpHeader.srcPort); - assertEquals(dstSockAddress.getPort(), udpHeader.dstPort); - assertEquals(payload.length + Struct.getSize(UdpHeader.class), udpHeader.length); - - if (!closeSocket) { - // After the socket is destroyed and QUIC connection is closed by - // ConnectivityService, writing to the UDP socket should throw. - try { - Os.write(sock, payload, 0 /* byteOffset */, payload.length); - fail("Write to the destroyed socket must throw."); - } catch (ErrnoException e) { - assertEquals(EDESTADDRREQ, e.errno); - } - try { - Os.sendto(sock, payload, 0 /* byteOffset */, - payload.length, 0 /* flags */, dstSockAddress); - fail("Sendto with the destroyed socket must throw."); - } catch (ErrnoException e) { - assertEquals(ECONNABORTED, e.errno); - } - } - return; - } - if (expectPacketSent) { - fail("Did not receive the QUIC connection close packet."); - } - }, /* cleanup */ () -> { - runWithShellPermissionIdentity(() -> { - mContext.getSystemService(TestNetworkManager.class).teardownTestNetwork( - testNetwork); - }, MANAGE_TEST_NETWORKS); - IoUtils.closeQuietly(tunFd); - IoUtils.closeQuietly(sock); - IoUtils.closeQuietly(pfd); - }, /* cleanup */ () -> { - // Restore firewall chain global status - runWithShellPermissionIdentity(() -> { - mCm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, wasChainEnabled); - }, NETWORK_SETTINGS); - }, /* cleanup */ () -> { - // Restore firewall chain status for myUid - runWithShellPermissionIdentity(() -> { - try { - mCm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, myUid, - previousUidFirewallRule); - } catch (IllegalStateException ignored) { - // Removing match causes an exception when the rule entry for the uid does - // not exist. But this is fine and can be ignored. - } - }, NETWORK_SETTINGS); - }); - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.R) - @ConnectivityModuleTest - @AppModeFull(reason = "Cannot create test network in instant app mode") - public void testRegisterQuicConnectionClosePayload_blockNetwork() throws Exception { - assumeTrue(mCm.isConnectivityServiceFeatureEnabledForTesting(CLOSE_QUIC_CONNECTION)); - - // Network is blocked while the connection close payload is registered. - // Packet should be sent. - doTestRegisterQuicConnectionClosePayload( - false /* isV6 */, false /* unregister */, false /* closeSocket */, - true /* blockNetwork */, true /* expectPacketSent */); - doTestRegisterQuicConnectionClosePayload( - true /* isV6 */, false /* unregister */, false /* closeSocket */, - true /* blockNetwork */, true /* expectPacketSent */); - - // Network is blocked after the connection close payload is unregistered. - // Packet should not be sent. - doTestRegisterQuicConnectionClosePayload( - false /* isV6 */, true /* unregister */, false /* closeSocket */, - true /* blockNetwork */, false /* expectPacketSent */); - doTestRegisterQuicConnectionClosePayload( - true /* isV6 */, true /* unregister */, false /* closeSocket */, - true /* blockNetwork */, false /* expectPacketSent */); - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.R) - @ConnectivityModuleTest - @AppModeFull(reason = "Cannot create test network in instant app mode") - public void testRegisterQuicConnectionClosePayload_closeSocket() throws Exception { - assumeTrue(mCm.isConnectivityServiceFeatureEnabledForTesting(CLOSE_QUIC_CONNECTION)); - - // Registered socket is closed while the connection close payload is registered. - // Packet should be sent. This simulates that apps crash or are killed. - doTestRegisterQuicConnectionClosePayload( - false /* isV6 */, false /* unregister */, true /* closeSocket */, - false /* blockNetwork */, true /* expectPacketSent */); - doTestRegisterQuicConnectionClosePayload( - true /* isV6 */, false /* unregister */, true /* closeSocket */, - false /* blockNetwork */, true /* expectPacketSent */); - } - - /** - * Repeat register a connection close payload, unregister it, and then close the socket. - * The registered payload must not be sent because it is unregistered before the socket - * is closed. - * This test tries to detect threading issues between - * register/unregisterQuicConnectionClosePayload and socket destroy message handling. - */ - @Test - @IgnoreUpTo(Build.VERSION_CODES.R) - @ConnectivityModuleTest - @AppModeFull(reason = "Cannot create test network in instant app mode") - public void testRegisterQuicConnectionClosePayload_closeSocketAfterUnregister() - throws Exception { - assumeTrue(mCm.isConnectivityServiceFeatureEnabledForTesting(CLOSE_QUIC_CONNECTION)); - - final ParcelFileDescriptor tunFd = setupTestNetworkAndGetFd(); - final Network testNetwork = getTestNetwork(); - - final Random random = new Random(); - final byte[] payload = new byte[100]; - random.nextBytes(payload); - - final InetSocketAddress dstSockAddress = - new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8:1:2::3"), 443); - for (int i = 0; i < 100; i++) { - final FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); - testNetwork.bindSocket(sock); - Os.connect(sock, dstSockAddress); - - final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(sock.getInt$()); - mCm.registerQuicConnectionClosePayload(pfd, payload); - mCm.unregisterQuicConnectionClosePayload(pfd); - Os.close(sock); - pfd.close(); - } - - testAndCleanup(() -> { - final long timeout = SystemClock.elapsedRealtime() - + QUIC_CONNECTION_CLOSE_PACKET_TIMEOUT_MS; - long remainingTimeMs; - while ((remainingTimeMs = timeout - SystemClock.elapsedRealtime()) > 0) { - StructPollfd pollfd = new StructPollfd(); - pollfd.events = (short) POLLIN; - pollfd.fd = tunFd.getFileDescriptor(); - final int ret = Os.poll(new StructPollfd[]{pollfd}, (int) remainingTimeMs); - if (ret == 0) { - continue; - } - - final byte[] recvData = new byte[200]; - final int readSize = Os.read(tunFd.getFileDescriptor(), recvData, - 0 /* byteOffset */, recvData.length); - if (readSize < payload.length - || !Arrays.equals(payload, 0, payload.length, - recvData, readSize - payload.length, readSize)) { - continue; - } - fail("Unexpectedly received the QUIC connection close packet."); - } - }, /* cleanup */ () -> { - runWithShellPermissionIdentity(() -> { - mContext.getSystemService(TestNetworkManager.class).teardownTestNetwork( - testNetwork); - }, MANAGE_TEST_NETWORKS); - IoUtils.closeQuietly(tunFd); - }); - } } diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index bd26b95764..8c90e01086 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -2188,7 +2188,6 @@ public class ConnectivityServiceTest { case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS: case ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS: case ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN: - case ConnectivityFlags.CLOSE_QUIC_CONNECTION: case ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION: return true; default: diff --git a/tests/unit/java/com/android/server/connectivity/QuicConnectionCloserTest.kt b/tests/unit/java/com/android/server/connectivity/QuicConnectionCloserTest.kt deleted file mode 100644 index 9e1c090f2c..0000000000 --- a/tests/unit/java/com/android/server/connectivity/QuicConnectionCloserTest.kt +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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 com.android.server.connectivity - -import android.net.InetAddresses -import android.net.LinkAddress -import android.net.LinkProperties -import android.net.Network -import android.net.TEST_IFACE -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.os.ParcelFileDescriptor -import android.system.OsConstants.SOL_SOCKET -import android.util.SparseArray -import com.android.net.module.util.SkDestroyListener -import com.android.net.module.util.netlink.InetDiagMessage -import com.android.net.module.util.netlink.StructInetDiagSockId -import com.android.net.module.util.netlink.StructNlMsgHdr -import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo -import com.android.testutils.DevSdkIgnoreRunner -import com.android.testutils.visibleOnHandlerThread -import java.net.InetSocketAddress -import java.util.function.Consumer -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.InOrder -import org.mockito.Mockito.any -import org.mockito.Mockito.anyLong -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.eq -import org.mockito.Mockito.inOrder -import org.mockito.Mockito.mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -private const val TEST_UID = 1234 -private const val TEST_NETID = 789 -private const val TEST_SOCKET_COOKIE = 12321L - -// TODO: Use OsConstants.SO_MARK once this API is available -private const val SO_MARK = 36 - -private val TEST_SRC_ADDRESS = InetAddresses.parseNumericAddress("2001:db8:1:2::2") -private val TEST_SRC_SOCKET_ADDRESS = InetSocketAddress( - TEST_SRC_ADDRESS, - 1234 -) -private val TEST_DST_SOCKET_ADDRESS = InetSocketAddress( - InetAddresses.parseNumericAddress("2001:db8:1:2::3"), - 443 -) -private val TEST_LP = LinkProperties().apply { - interfaceName = TEST_IFACE - addLinkAddress(LinkAddress(TEST_SRC_ADDRESS, 64)) -} -private val TEST_NETWORK = Network(TEST_NETID) -private val TEST_PAYLOAD = byteArrayOf(0, 1, 2, 3, 4, 5) - -@RunWith(DevSdkIgnoreRunner::class) -@IgnoreUpTo(Build.VERSION_CODES.R) -class QuicConnectionCloserTest { - private val pfd = mock(ParcelFileDescriptor::class.java) - private val skDestroyListener = mock(SkDestroyListener::class.java) - private val handler by lazy { Handler(Looper.getMainLooper()) } - - private val mDeps = mock(QuicConnectionCloser.Dependencies::class.java).also { - doReturn(TEST_NETID).`when`(it).getsockoptInt(any(), eq(SOL_SOCKET), eq(SO_MARK)) - doReturn(TEST_SOCKET_COOKIE).`when`(it).getSocketCookie(any()) - doReturn(TEST_SRC_SOCKET_ADDRESS).`when`(it).getsockname(any()) - doReturn(TEST_DST_SOCKET_ADDRESS).`when`(it).getpeername(any()) - doReturn(skDestroyListener).`when`(it).makeSkDestroyListener(any(), any()) - } - - private val nai = mock(NetworkAgentInfo::class.java).also { - it.linkProperties = TEST_LP - doReturn(TEST_NETWORK).`when`(it).network() - } - - private val networkForNetId = SparseArray().apply { - put(TEST_NETID, nai) - } - - private val mQuicConnectionCloser = QuicConnectionCloser(networkForNetId, handler, mDeps) - - private fun InOrder.expectDestroyUdpSocket() = verify(mDeps).destroyUdpSocket( - TEST_SRC_SOCKET_ADDRESS, - TEST_DST_SOCKET_ADDRESS, - TEST_SOCKET_COOKIE - ) - - private fun InOrder.assertNoDestroyUdpSocket() = verify(mDeps, never()).destroyUdpSocket( - any(), - any(), - anyLong() - ) - - private fun InOrder.expectSendQuicConnectionClosePayload() = - verify(mDeps).sendQuicConnectionClosePayload( - TEST_NETWORK, - TEST_SRC_SOCKET_ADDRESS, - TEST_DST_SOCKET_ADDRESS, - TEST_PAYLOAD - ) - - private fun InOrder.assertNoSendQuicConnectionClosePayload() = - verify(mDeps, never()).sendQuicConnectionClosePayload( - any(), - any(), - any(), - any() - ) - - @Test - fun testCloseQuicConnectionByUids() { - mQuicConnectionCloser.registerQuicConnectionClosePayload(TEST_UID, pfd, TEST_PAYLOAD) - visibleOnHandlerThread(handler) { - mQuicConnectionCloser.closeQuicConnectionByUids(setOf(TEST_UID)) - } - - val inOrder = inOrder(mDeps) - inOrder.expectDestroyUdpSocket() - inOrder.expectSendQuicConnectionClosePayload() - } - - @Test - fun testCloseQuicConnectionByUids_unregisterQuicConnectionCloseInfo() { - mQuicConnectionCloser.registerQuicConnectionClosePayload(TEST_UID, pfd, TEST_PAYLOAD) - mQuicConnectionCloser.unregisterQuicConnectionClosePayload(pfd) - visibleOnHandlerThread(handler) { - mQuicConnectionCloser.closeQuicConnectionByUids(setOf(TEST_UID)) - } - - val inOrder = inOrder(mDeps) - inOrder.assertNoDestroyUdpSocket() - inOrder.assertNoSendQuicConnectionClosePayload() - } - - @Test - fun testCloseQuicConnectionByUids_networkDisconnected() { - mQuicConnectionCloser.registerQuicConnectionClosePayload(TEST_UID, pfd, TEST_PAYLOAD) - // closeQuicConnectionByUids determines that the network is disconnected by - // checking if it's absent from the networkForNetId set. - synchronized (networkForNetId) { - networkForNetId.clear() - } - visibleOnHandlerThread(handler) { - mQuicConnectionCloser.closeQuicConnectionByUids(setOf(TEST_UID)) - } - - val inOrder = inOrder(mDeps) - inOrder.assertNoDestroyUdpSocket() - inOrder.assertNoSendQuicConnectionClosePayload() - } - - @Test - fun testCloseQuicConnectionByUids_networkAddressChange() { - mQuicConnectionCloser.registerQuicConnectionClosePayload(TEST_UID, pfd, TEST_PAYLOAD) - // Update address to different address from TEST_SRC_SOCKET_ADDRESS - nai.linkProperties = LinkProperties().apply { - interfaceName = TEST_IFACE - addLinkAddress(LinkAddress(InetAddresses.parseNumericAddress("2001:db8:1:3::2"), 64)) - } - visibleOnHandlerThread(handler) { - mQuicConnectionCloser.closeQuicConnectionByUids(setOf(TEST_UID)) - } - - val inOrder = inOrder(mDeps) - inOrder.assertNoDestroyUdpSocket() - inOrder.assertNoSendQuicConnectionClosePayload() - } - - private fun getSkDestroyListenerCallback(): Consumer { - val captor = ArgumentCaptor.forClass(Consumer::class.java) - as ArgumentCaptor> - verify(mDeps).makeSkDestroyListener(captor.capture(), any()) - return captor.value - } - - @Test - fun testHandleUdpSocketDestroy() { - mQuicConnectionCloser.registerQuicConnectionClosePayload(TEST_UID, pfd, TEST_PAYLOAD) - - val inetDiagMessage = InetDiagMessage(StructNlMsgHdr()) - inetDiagMessage.inetDiagMsg.id = StructInetDiagSockId( - TEST_SRC_SOCKET_ADDRESS, - TEST_DST_SOCKET_ADDRESS, - 0 /* ifindex */, - TEST_SOCKET_COOKIE - ) - getSkDestroyListenerCallback().accept(inetDiagMessage) - - val inOrder = inOrder(mDeps) - // DestroyUdpSocket is not called since the socket is already closed - inOrder.assertNoDestroyUdpSocket() - inOrder.expectSendQuicConnectionClosePayload() - } - - @Test - fun testHandleUdpSocketDestroy_unregisterBeforeSocketClose() { - mQuicConnectionCloser.registerQuicConnectionClosePayload(TEST_UID, pfd, TEST_PAYLOAD) - mQuicConnectionCloser.unregisterQuicConnectionClosePayload(pfd) - - val inetDiagMessage = InetDiagMessage(StructNlMsgHdr()) - inetDiagMessage.inetDiagMsg.id = StructInetDiagSockId( - TEST_SRC_SOCKET_ADDRESS, - TEST_DST_SOCKET_ADDRESS, - 0 /* ifindex */, - TEST_SOCKET_COOKIE - ) - getSkDestroyListenerCallback().accept(inetDiagMessage) - - val inOrder = inOrder(mDeps) - inOrder.assertNoDestroyUdpSocket() - inOrder.assertNoSendQuicConnectionClosePayload() - } -} diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt index 56d934bf51..bc5be7865c 100644 --- a/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt @@ -155,6 +155,7 @@ class CSDestroySocketTest : CSTest() { @Test fun testReplaceFirewallChain() { val netdEventListener = getRegisteredNetdUnsolicitedEventListener() + val inOrder = inOrder(destroySocketsWrapper) val cellAgent = Agent(nc = cellNc(), lp = cellLp()) cellAgent.connect() @@ -192,8 +193,8 @@ class CSDestroySocketTest : CSTest() { .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID2) cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf(TEST_UID2)) waitForIdle() - verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) - verify(quicConnectionCloser, never()).closeQuicConnectionByUids(setOf(TEST_UID)) + inOrder.verify(destroySocketsWrapper, never()) + .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) netdEventListener.onInterfaceClassActivityChanged( true, // isActive @@ -202,8 +203,8 @@ class CSDestroySocketTest : CSTest() { TEST_UID ) waitForIdle() - verify(destroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) - verify(quicConnectionCloser).closeQuicConnectionByUids(setOf(TEST_UID)) + inOrder.verify(destroySocketsWrapper) + .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) cellAgent.disconnect() } @@ -276,10 +277,8 @@ class CSDestroySocketTest : CSTest() { if (expectDestroySockets) { verify(destroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) - verify(quicConnectionCloser).closeQuicConnectionByUids(setOf(TEST_UID)) } else { verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID)) - verify(quicConnectionCloser, never()).closeQuicConnectionByUids(setOf(TEST_UID)) } } diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt index d7ecfd6159..5c55483ee9 100644 --- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt @@ -58,7 +58,6 @@ import android.permission.PermissionManager.PermissionResult import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.testing.TestableContext -import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.app.IBatteryStats import com.android.internal.util.test.BroadcastInterceptingContext @@ -73,11 +72,9 @@ import com.android.server.connectivity.InterfaceTracker import com.android.server.connectivity.MulticastRoutingCoordinatorService import com.android.server.connectivity.MultinetworkPolicyTracker import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies -import com.android.server.connectivity.NetworkAgentInfo import com.android.server.connectivity.NetworkRequestStateStatsMetrics import com.android.server.connectivity.PermissionMonitor import com.android.server.connectivity.ProxyTracker -import com.android.server.connectivity.QuicConnectionCloser import com.android.server.connectivity.SatelliteAccessController import com.android.testutils.visibleOnHandlerThread import com.android.testutils.waitForIdle @@ -174,7 +171,6 @@ open class CSTest { it[ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS] = true it[ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS] = true it[ConnectivityFlags.QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER] = true - it[ConnectivityFlags.CLOSE_QUIC_CONNECTION] = true } fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled) @@ -223,7 +219,6 @@ open class CSTest { val multicastRoutingCoordinatorService = mock() val satelliteAccessController = mock() - val quicConnectionCloser = mock() val destroySocketsWrapper = mock() val deps = CSDeps() @@ -417,11 +412,6 @@ open class CSTest { } override fun makeL2capNetworkProvider(context: Context) = null - - override fun makeQuicConnectionCloser( - networkForNetId: SparseArray, - handler: Handler - ): QuicConnectionCloser = quicConnectionCloser } inner class PermDeps : PermissionMonitor.Dependencies() {