Merge changes from topic "revert-3538339-SNUWWZJNJZ" into main

* changes:
  Revert "Add hidden API to register/unregister QUIC connection cl..."
  Revert "Remove unnecessary ifindex arg from destroyUdpSocket"
This commit is contained in:
Chaitanya Cheemala (xWF) 2025-03-26 03:44:15 -07:00 • committed by Gerrit Code Review
commit 86961c4b73
11 changed files with 9 additions and 1112 deletions

View file

@ -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();
}
}
}

View file

@ -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);
}

View file

@ -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<NetworkAgentInfo> 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);
}
}

View file

@ -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;
/**

View file

@ -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<Long, QuicConnectionCloseInfo>
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<NetworkAgentInfo> 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, cookie);
}
/**
* Call {@link SkDestroyListener#makeSkDestroyListener}
*/
public SkDestroyListener makeSkDestroyListener(final Consumer<InetDiagMessage> 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<NetworkAgentInfo> networkForNetId,
final Handler handler) {
this(networkForNetId, handler, new Dependencies());
}
@VisibleForTesting
public QuicConnectionCloser(final SparseArray<NetworkAgentInfo> 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<Integer> uids) {
ensureRunningOnHandlerThread();
for (Iterator<Map.Entry<Long, QuicConnectionCloseInfo>> 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();
}
}

View file

@ -491,7 +491,7 @@ public class InetDiagMessage extends NetlinkMessage {
* Close the udp socket which can be uniquely identified with the cookie and other information.
*/
public static void destroyUdpSocket(final InetSocketAddress src, final InetSocketAddress dst,
final long cookie)
final int ifIndex, final long cookie)
throws ErrnoException, SocketException, InterruptedIOException {
FileDescriptor fd = null;
@ -502,7 +502,7 @@ public class InetDiagMessage extends NetlinkMessage {
final StructInetDiagSockId id = new StructInetDiagSockId(
src,
dst,
0 /* ifIndex */,
ifIndex,
cookie
);
sendNetlinkDestroyRequest(fd, IPPROTO_UDP, id, (short) family, 0 /* state */);

View file

@ -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<LinkAddress> 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);
});
}
}

View file

@ -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:

View file

@ -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<NetworkAgentInfo>().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<InetDiagMessage> {
val captor = ArgumentCaptor.forClass(Consumer::class.java)
as ArgumentCaptor<Consumer<InetDiagMessage>>
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()
}
}

View file

@ -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))
}
}

View file

@ -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<MulticastRoutingCoordinatorService>()
val satelliteAccessController = mock<SatelliteAccessController>()
val quicConnectionCloser = mock<QuicConnectionCloser>()
val destroySocketsWrapper = mock<DestroySocketsWrapper>()
val deps = CSDeps()
@ -417,11 +412,6 @@ open class CSTest {
}
override fun makeL2capNetworkProvider(context: Context) = null
override fun makeQuicConnectionCloser(
networkForNetId: SparseArray<NetworkAgentInfo>,
handler: Handler
): QuicConnectionCloser = quicConnectionCloser
}
inner class PermDeps : PermissionMonitor.Dependencies() {