Merge changes from topic "destroy_socket_at_cs" into main
* changes: Destroy sockets lacking permission at CS. Destroy sockets with nlmsg RTM_DELADDR Destroy sockets with NetworkAgent update event Add a Map to NetworkAgent list for IP address Add socket destroying methods to InetDiagMessage.
This commit is contained in:
commit
13e6742742
11 changed files with 1661 additions and 50 deletions
|
|
@ -156,6 +156,8 @@ import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPer
|
|||
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
|
||||
import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
|
||||
import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
|
||||
import static com.android.server.NetIdManager.MAX_NET_ID;
|
||||
import static com.android.server.NetIdManager.MIN_NET_ID;
|
||||
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.CONSTRAINED_DATA_SATELLITE_METRICS;
|
||||
|
|
@ -276,6 +278,7 @@ import android.net.netd.aidl.NativeUidRangeConfig;
|
|||
import android.net.networkstack.ModuleNetworkStackClient;
|
||||
import android.net.networkstack.NetworkStackClientBase;
|
||||
import android.net.networkstack.aidl.NetworkMonitorParameters;
|
||||
import android.net.platform.flags.Flags;
|
||||
import android.net.resolv.aidl.DnsHealthEventParcel;
|
||||
import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
|
||||
import android.net.resolv.aidl.Nat64PrefixEventParcel;
|
||||
|
|
@ -311,6 +314,7 @@ import android.stats.connectivity.RequestType;
|
|||
import android.stats.connectivity.ValidatedState;
|
||||
import android.sysprop.NetworkProperties;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
|
|
@ -350,14 +354,21 @@ import com.android.net.module.util.CollectionUtils;
|
|||
import com.android.net.module.util.DeviceConfigUtils;
|
||||
import com.android.net.module.util.HandlerUtils;
|
||||
import com.android.net.module.util.InterfaceParams;
|
||||
import com.android.net.module.util.LinkPropertiesUtils;
|
||||
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
|
||||
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
|
||||
import com.android.net.module.util.LocationPermissionChecker;
|
||||
import com.android.net.module.util.PerUidCounter;
|
||||
import com.android.net.module.util.PermissionUtils;
|
||||
import com.android.net.module.util.RoutingCoordinatorService;
|
||||
import com.android.net.module.util.SharedLog;
|
||||
import com.android.net.module.util.TcUtils;
|
||||
import com.android.net.module.util.ip.NetlinkMonitor;
|
||||
import com.android.net.module.util.netlink.InetDiagMessage;
|
||||
import com.android.net.module.util.netlink.NetlinkConstants;
|
||||
import com.android.net.module.util.netlink.NetlinkMessage;
|
||||
import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
|
||||
import com.android.net.module.util.netlink.StructIfaddrMsg;
|
||||
import com.android.networkstack.apishim.BroadcastOptionsShimImpl;
|
||||
import com.android.networkstack.apishim.ConstantsShim;
|
||||
import com.android.networkstack.apishim.common.BroadcastOptionsShim;
|
||||
|
|
@ -375,6 +386,7 @@ import com.android.server.connectivity.DnsManager;
|
|||
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
|
||||
import com.android.server.connectivity.DscpPolicyTracker;
|
||||
import com.android.server.connectivity.FullScore;
|
||||
import com.android.server.connectivity.IntegerRangeUtils;
|
||||
import com.android.server.connectivity.InterfaceTracker;
|
||||
import com.android.server.connectivity.InvalidTagException;
|
||||
import com.android.server.connectivity.KeepaliveResourceUtil;
|
||||
|
|
@ -411,6 +423,7 @@ import java.io.InterruptedIOException;
|
|||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
|
@ -1133,6 +1146,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
@Nullable
|
||||
private final QuicConnectionCloser mQuicConnectionCloser;
|
||||
|
||||
// A map from IP address to networks. Only the handler thread is allowed to access this field.
|
||||
@Nullable @VisibleForTesting final Map<InetAddress, Set<NetworkAgentInfo>> mIpToNetworksMap;
|
||||
// NetlinkMonitor for ConnectivityService
|
||||
@Nullable private final AddressUpdateMonitor mAddressUpdateMonitor;
|
||||
|
||||
/**
|
||||
* Implements support for the legacy "one network per network type" model.
|
||||
*
|
||||
|
|
@ -1478,6 +1496,28 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple NetlinkMonitor. Listen for address changed events from kernel.
|
||||
* All methods except the constructor must be called on the handler thread.
|
||||
*/
|
||||
public static class AddressUpdateMonitor extends NetlinkMonitor {
|
||||
private final Consumer<NetlinkMessage> mNetlinkMessageConsumer;
|
||||
|
||||
AddressUpdateMonitor(Handler h, SharedLog log, String tag,
|
||||
Consumer<NetlinkMessage> netlinkMessageConsumer) {
|
||||
super(h, log, tag, OsConstants.NETLINK_ROUTE,
|
||||
(NetlinkConstants.RTMGRP_IPV4_IFADDR
|
||||
| NetlinkConstants.RTMGRP_IPV6_IFADDR));
|
||||
mNetlinkMessageConsumer = netlinkMessageConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processNetlinkMessage(
|
||||
@NonNull NetlinkMessage nlMsg, long whenMs) {
|
||||
mNetlinkMessageConsumer.accept(nlMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependencies of ConnectivityService, for injection in tests.
|
||||
*/
|
||||
|
|
@ -1838,6 +1878,45 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
InetDiagMessage.destroyLiveTcpSocketsByOwnerUids(ownerUids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy live tcp sockets for IP addresses with conditions.
|
||||
*
|
||||
* @param address a local address to destroy sockets
|
||||
* @param netIdRange range of net IDs to destroy sockets
|
||||
* @param uidRanges ranges of UIDs to destroy sockets
|
||||
*/
|
||||
public void destroyLiveTcpSocketsByLocalAddress(
|
||||
@NonNull InetAddress address,
|
||||
@Nullable Set<Range<Integer>> netIdRange,
|
||||
@Nullable Set<Range<Integer>> uidRanges)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
InetDiagMessage.destroyLiveTcpSocketsByLocalAddress(address, netIdRange, uidRanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy live tcp sockets for IP addresses with conditions.
|
||||
*
|
||||
* @param address a local address to destroy sockets
|
||||
* @param interfaceId interface ID
|
||||
*/
|
||||
public void destroyLiveTcpSocketsByLocalAddress(
|
||||
@NonNull InetAddress address, int interfaceId)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
InetDiagMessage.destroyLiveTcpSocketsByLocalAddress(address, interfaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link InetDiagMessage#destroyLiveTcpSocketsLackingPermission(int, int)}
|
||||
*
|
||||
* @param netId network ID
|
||||
* @param permission network permission
|
||||
*/
|
||||
public void destroyLiveTcpSocketsLackingPermission(
|
||||
int netId, int permission)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
InetDiagMessage.destroyLiveTcpSocketsLackingPermission(netId, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the evaluation timeout.
|
||||
*
|
||||
|
|
@ -1862,6 +1941,20 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
final SparseArray<NetworkAgentInfo> networkForNetId, final Handler handler) {
|
||||
return new QuicConnectionCloser(networkForNetId, handler);
|
||||
}
|
||||
|
||||
/** Whether the flag for connectivity service socket destroy is enabled or not. */
|
||||
public boolean flagConnectivityServiceDestroySocket() {
|
||||
return Flags.connectivityServiceDestroySocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a AddressUpdateMonitor instance.
|
||||
*/
|
||||
public AddressUpdateMonitor makeAddressUpdateMonitor(
|
||||
@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
|
||||
@NonNull Consumer<NetlinkMessage> consumer) {
|
||||
return new AddressUpdateMonitor(h, log, tag, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectivityService(Context context) {
|
||||
|
|
@ -1998,6 +2091,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
mConstrainedDataSatelliteMetrics = (mSatelliteAccessController != null)
|
||||
&& mDeps.isFeatureNotChickenedOut(mContext, CONSTRAINED_DATA_SATELLITE_METRICS);
|
||||
|
||||
if (mDeps.flagConnectivityServiceDestroySocket()) {
|
||||
mIpToNetworksMap = new HashMap<>();
|
||||
mAddressUpdateMonitor = mDeps.makeAddressUpdateMonitor(
|
||||
mHandler, new SharedLog(20, TAG), TAG,
|
||||
this::processNetlinkAddressUpdateMessage);
|
||||
} else {
|
||||
mIpToNetworksMap = null;
|
||||
mAddressUpdateMonitor = null;
|
||||
}
|
||||
// To ensure uid state is synchronized with Network Policy, register for
|
||||
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
|
||||
// reading existing policy from disk.
|
||||
|
|
@ -4213,6 +4315,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
mBroadcastReceiveHelper.callOnUserAddedForExistingUsers();
|
||||
permissionMonitorInitializeDone.open();
|
||||
});
|
||||
if (mAddressUpdateMonitor != null) {
|
||||
mHandler.post(() -> mAddressUpdateMonitor.start());
|
||||
}
|
||||
mProxyTracker.loadGlobalProxy();
|
||||
registerDnsResolverUnsolicitedEventListener();
|
||||
|
||||
|
|
@ -4602,6 +4707,21 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
(mMulticastRoutingCoordinatorService != null));
|
||||
pw.println("Background firewall chain enabled: " + mBackgroundFirewallChainEnabled);
|
||||
pw.println("IngressToVpnAddressFiltering: " + mIngressToVpnAddressFiltering);
|
||||
|
||||
if (mIpToNetworksMap != null) {
|
||||
pw.println();
|
||||
pw.println("mIpToNetworksMap:");
|
||||
pw.increaseIndent();
|
||||
for (Map.Entry<InetAddress, Set<NetworkAgentInfo>> info : mIpToNetworksMap.entrySet()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(info.getKey()).append(": ");
|
||||
for (NetworkAgentInfo nai : info.getValue()) {
|
||||
sb.append("{").append(nai.network).append("}");
|
||||
}
|
||||
pw.println(sb);
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpNetworks(IndentingPrintWriter pw) {
|
||||
|
|
@ -5728,8 +5848,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
|
||||
// Delayed teardown.
|
||||
if (nai.isCreated() && !nai.isDestroyed()) {
|
||||
destroyLiveTcpSocketsLackingPermission(
|
||||
nai, getNetworkPermission(nai.networkCapabilities), INetd.PERMISSION_SYSTEM);
|
||||
try {
|
||||
mNetd.networkSetPermissionForNetwork(nai.network.netId, INetd.PERMISSION_SYSTEM);
|
||||
// Call again in case sockets were opened after the first time : see
|
||||
// updateNetworkPermissions
|
||||
destroyLiveTcpSocketsLackingPermission(nai,
|
||||
getNetworkPermission(nai.networkCapabilities), INetd.PERMISSION_SYSTEM);
|
||||
} catch (RemoteException e) {
|
||||
Log.d(TAG, "Error marking network restricted during teardown: ", e);
|
||||
}
|
||||
|
|
@ -5858,6 +5984,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
loge("Exception destroying network(networkDestroy): " + e);
|
||||
}
|
||||
updateIpAddressesAndDestroySockets(nai, nai.linkProperties, null);
|
||||
try {
|
||||
mDnsResolver.destroyNetworkCache(nai.network.getNetId());
|
||||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
|
|
@ -9866,12 +9993,104 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
}
|
||||
networkAgent.networkMonitor().notifyLinkPropertiesChanged(
|
||||
new LinkProperties(newLp, true /* parcelSensitiveFields */));
|
||||
updateIpAddressesAndDestroySockets(networkAgent, oldLp, newLp);
|
||||
notifyNetworkCallbacks(networkAgent, CALLBACK_IP_CHANGED);
|
||||
}
|
||||
|
||||
mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
|
||||
}
|
||||
|
||||
private void updateIpAddressesAndDestroySockets(
|
||||
NetworkAgentInfo nai, LinkProperties oldLp, LinkProperties newLp) {
|
||||
ensureRunningOnConnectivityServiceThread();
|
||||
if (mIpToNetworksMap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final CompareResult<LinkAddress> result =
|
||||
LinkPropertiesUtils.compareAllAddresses(oldLp, newLp);
|
||||
|
||||
if (!result.added.isEmpty()) {
|
||||
for (LinkAddress la : result.added) {
|
||||
if (la.getAddress() instanceof Inet6Address
|
||||
&& la.getAddress().isLinkLocalAddress()) {
|
||||
// Some NetworkAgents may not report IPv6 link local address to CS, thus socket
|
||||
// destruction on the address will be done at handling RTM_DELADDR and the
|
||||
// address doesn't need to be added to mIpToNetworksMap.
|
||||
continue;
|
||||
}
|
||||
mIpToNetworksMap.computeIfAbsent(la.getAddress(), k -> new ArraySet<>()).add(nai);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.removed.isEmpty()) {
|
||||
for (LinkAddress la : result.removed) {
|
||||
if (la.getAddress() instanceof Inet6Address
|
||||
&& la.getAddress().isLinkLocalAddress()) {
|
||||
continue;
|
||||
}
|
||||
final Set<NetworkAgentInfo> networks = mIpToNetworksMap.get(la.getAddress());
|
||||
destroySocketsForRemovedAddress(la.getAddress(), nai, networks);
|
||||
if (networks != null) {
|
||||
networks.remove(nai);
|
||||
if (networks.isEmpty()) {
|
||||
mIpToNetworksMap.remove(la.getAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void destroySocketsForRemovedAddress(
|
||||
InetAddress address,
|
||||
@NonNull NetworkAgentInfo targetNai,
|
||||
@NonNull Set<NetworkAgentInfo> networks) {
|
||||
// Logic to destroy sockets(MIN_NET_ID ~ MAX_NET_ID) on networks managed by this service
|
||||
// when IP addresses are removed. Sockets(0 ~ MIN_NET_ID - 1) are for OEM network or legacy
|
||||
// local network. Those are not tracked by this service, thus will be destroyed at the
|
||||
// RTM_DELADDR event handling.
|
||||
final Set<Range<Integer>> netIdRangeSet;
|
||||
final Set<Range<Integer>> uidRangeSet;
|
||||
if (networks.size() == 1) {
|
||||
// Removed IP address was in single network.
|
||||
netIdRangeSet = Set.of(Range.create(MIN_NET_ID, MAX_NET_ID));
|
||||
uidRangeSet = null;
|
||||
} else if (CollectionUtils.all(networks, nai -> !nai.isVPN())) {
|
||||
// If all networks are not VPNs, other networks with the same IP address can be exempted
|
||||
// from this socket destruction. Since there may be remaining socket's which has Fwmark
|
||||
// matched to previously disconnected VPN's netId, It's intended destroying sockets not
|
||||
// matched to exempt networks rather than sockets matched to the target network.
|
||||
final List<Integer> exemptNetIds = new ArrayList<>();
|
||||
for (NetworkAgentInfo nai : networks) {
|
||||
if (nai.network.netId != targetNai.network.netId) {
|
||||
exemptNetIds.add(nai.network.netId);
|
||||
}
|
||||
}
|
||||
netIdRangeSet = IntegerRangeUtils.rangeWithoutValues(
|
||||
Range.create(MIN_NET_ID, MAX_NET_ID), exemptNetIds);
|
||||
uidRangeSet = null;
|
||||
} else if (CollectionUtils.all(networks, NetworkAgentInfo::isVPN)) {
|
||||
// Since the UID ranges of VPN networks cannot overlap with each other, if all networks
|
||||
// with the corresponding address were VPNs, sockets included in the UID range of the
|
||||
// target network are selectively removed.
|
||||
netIdRangeSet = Set.of(Range.create(MIN_NET_ID, MAX_NET_ID));
|
||||
uidRangeSet = targetNai.networkCapabilities.getUids();
|
||||
} else {
|
||||
// If there is at least one VPN network among multiple networks with the same address,
|
||||
// there may be sockets with Fwmark value which does not match the netId (e.g. Socket
|
||||
// connections on non-bypassable VPN or socket connections fallen out of split tunnel
|
||||
// for the bypassable VPN), and it is currently too complicated to distinguish them.
|
||||
// Thus sockets in whole netId range will be destroyed based on local IP address.
|
||||
netIdRangeSet = Set.of(Range.create(MIN_NET_ID, MAX_NET_ID));
|
||||
uidRangeSet = null;
|
||||
}
|
||||
try {
|
||||
mDeps.destroyLiveTcpSocketsByLocalAddress(address, netIdRangeSet, uidRangeSet);
|
||||
} catch (SocketException | InterruptedIOException | ErrnoException e) {
|
||||
loge("Exception destroy TCP sockets on local address: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyInitialLinkProperties(@NonNull NetworkAgentInfo nai) {
|
||||
updateLinkProperties(nai, new LinkProperties(nai.linkProperties), null);
|
||||
}
|
||||
|
|
@ -10430,8 +10649,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
final int oldPermission = getNetworkPermission(nai.networkCapabilities);
|
||||
final int newPermission = getNetworkPermission(newNc);
|
||||
if (oldPermission != newPermission && nai.isCreated() && !nai.isVPN()) {
|
||||
destroyLiveTcpSocketsLackingPermission(nai, oldPermission, newPermission);
|
||||
try {
|
||||
mNetd.networkSetPermissionForNetwork(nai.network.getNetId(), newPermission);
|
||||
// Destroy sockets again in case any were opened after
|
||||
// destroySocketsLackingPermission is called above and before the permissions is
|
||||
// changed. These sockets won't be able to send any RST packets because they are now
|
||||
// no longer routed, but at least the apps will get errors.
|
||||
destroyLiveTcpSocketsLackingPermission(nai, oldPermission, newPermission);
|
||||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
loge("Exception in networkSetPermissionForNetwork: " + e);
|
||||
}
|
||||
|
|
@ -15352,4 +15577,62 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
mQuicConnectionCloser.unregisterQuicConnectionClosePayload(pfd);
|
||||
}
|
||||
|
||||
// Currently this only handles message type RTM_DELADDR to destroy sockets for the deleted
|
||||
// address, we could make this a dispatcher method if we have more use cases in the future.
|
||||
private void processNetlinkAddressUpdateMessage(NetlinkMessage nlMsg) {
|
||||
if (!(nlMsg instanceof RtNetlinkAddressMessage msg)) {
|
||||
return;
|
||||
}
|
||||
if (msg.getHeader().nlmsg_type != NetlinkConstants.RTM_DELADDR) {
|
||||
return;
|
||||
}
|
||||
|
||||
final StructIfaddrMsg ifaddrMsg = msg.getIfaddrHeader();
|
||||
final InetAddress removedAddress = msg.getIpAddress();
|
||||
if (removedAddress instanceof Inet6Address && removedAddress.isLinkLocalAddress()) {
|
||||
try {
|
||||
mDeps.destroyLiveTcpSocketsByLocalAddress(removedAddress, ifaddrMsg.index);
|
||||
} catch (SocketException | InterruptedIOException | ErrnoException e) {
|
||||
loge("Exception destroying TCP sockets on local address with interfaceId: ", e);
|
||||
}
|
||||
} else {
|
||||
// Range of netId for networks that ConnectivityService isn't aware of. OEM & legacy
|
||||
// local networks are included in this scope.
|
||||
try {
|
||||
mDeps.destroyLiveTcpSocketsByLocalAddress(
|
||||
removedAddress,
|
||||
Set.of(Range.create(0, MIN_NET_ID - 1)), null /* uidRanges */
|
||||
);
|
||||
} catch (SocketException | InterruptedIOException | ErrnoException e) {
|
||||
loge("Exception destroy TCP sockets on local address: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyLiveTcpSocketsLackingPermission(
|
||||
NetworkAgentInfo nai, int oldPermission, int newPermission) {
|
||||
if (!mDeps.flagConnectivityServiceDestroySocket()) {
|
||||
return;
|
||||
}
|
||||
// Virtual network doesn't have permission set.
|
||||
if (!isPermissionMoreRestrictive(oldPermission, newPermission) || nai.isVPN()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mDeps.destroyLiveTcpSocketsLackingPermission(
|
||||
nai.network.netId, newPermission);
|
||||
} catch (SocketException | InterruptedIOException | ErrnoException e) {
|
||||
loge("Exception destroy TCP sockets lacking permission: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPermissionMoreRestrictive(int oldPermission, int newPermission) {
|
||||
return switch (newPermission) {
|
||||
case INetd.PERMISSION_NONE -> false;
|
||||
case INetd.PERMISSION_NETWORK -> oldPermission == INetd.PERMISSION_NONE;
|
||||
case INetd.PERMISSION_SYSTEM -> oldPermission == INetd.PERMISSION_NONE
|
||||
|| oldPermission == INetd.PERMISSION_NETWORK;
|
||||
default -> throw new IllegalArgumentException("Invalid permission");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.util.ArraySet;
|
||||
import android.util.Range;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class IntegerRangeUtils {
|
||||
/**
|
||||
* Remove given Integer list from the source Integer range and return a set of ranges.
|
||||
* @param source Integer range from which Integer list will be removed
|
||||
* @param exempt Integer list to be removed from the source.
|
||||
* @return a set of Integer ranges which doesn't have exempted Integer list.
|
||||
*/
|
||||
public static Set<Range<Integer>> rangeWithoutValues(
|
||||
@NonNull Range<Integer> source, @Nullable List<Integer> exempt) {
|
||||
final Set<Range<Integer>> rangeSet = new ArraySet<>();
|
||||
final List<Integer> exemptInRange = exempt != null
|
||||
? CollectionUtils.filter(exempt, source::contains) : null;
|
||||
if (exemptInRange == null || exemptInRange.isEmpty()) {
|
||||
rangeSet.add(source);
|
||||
return rangeSet;
|
||||
}
|
||||
Collections.sort(exemptInRange);
|
||||
int start = source.getLower();
|
||||
for (int i = 0; i < exemptInRange.size(); i++) {
|
||||
int end = exemptInRange.get(i);
|
||||
if (end > start) {
|
||||
rangeSet.add(Range.create(start, end - 1));
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
if (start <= source.getUpper()) {
|
||||
rangeSet.add(Range.create(start, source.getUpper()));
|
||||
}
|
||||
return rangeSet;
|
||||
}
|
||||
}
|
||||
|
|
@ -253,6 +253,9 @@ java_library {
|
|||
"net-utils-device-common-struct",
|
||||
"net-utils-device-common-struct-base",
|
||||
],
|
||||
static_libs: [
|
||||
"netd_aidl_interface-lateststable-java",
|
||||
],
|
||||
apex_available: [
|
||||
"com.android.tethering",
|
||||
"//apex_available:platform",
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import static android.system.OsConstants.IPPROTO_TCP;
|
|||
import static android.system.OsConstants.IPPROTO_UDP;
|
||||
import static android.system.OsConstants.NETLINK_INET_DIAG;
|
||||
|
||||
import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
|
||||
import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DESTROY;
|
||||
import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
|
||||
import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
|
||||
import static com.android.net.module.util.netlink.NetlinkConstants.stringForAddressFamily;
|
||||
import static com.android.net.module.util.netlink.NetlinkConstants.stringForProtocol;
|
||||
import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
|
||||
|
|
@ -37,6 +37,7 @@ import static com.android.net.module.util.netlink.NetlinkUtils.connectToKernel;
|
|||
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
|
||||
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
|
||||
|
||||
import android.net.INetd;
|
||||
import android.net.util.SocketUtils;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
|
|
@ -48,6 +49,9 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
import com.android.net.module.util.InetAddressUtils;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
|
|
@ -61,6 +65,7 @@ import java.nio.ByteBuffer;
|
|||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
|
@ -76,6 +81,13 @@ import java.util.function.Predicate;
|
|||
public class InetDiagMessage extends NetlinkMessage {
|
||||
public static final String TAG = "InetDiagMessage";
|
||||
private static final int TIMEOUT_MS = 500;
|
||||
/** Bitmask position for 'explicitlySelected' in Fwmark.h */
|
||||
public static final int EXPLICITLY_SELECTED_BIT_SHIFT = 16;
|
||||
/** Bitmask position for 'Permission' in Fwmark.h */
|
||||
public static final int PERMISSION_SHIFT = 18;
|
||||
|
||||
/** FWMARK_NET_ID_MASK in Fwmark.h */
|
||||
public static final int FWMARK_NET_ID_MASK = 0xFFFF;
|
||||
|
||||
/**
|
||||
* Construct an inet_diag_req_v2 message. This method will throw
|
||||
|
|
@ -342,20 +354,27 @@ public class InetDiagMessage extends NetlinkMessage {
|
|||
FileDescriptor destroyFd, int proto, Predicate<InetDiagMessage> filter)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
AtomicInteger destroyedSockets = new AtomicInteger(0);
|
||||
Consumer<InetDiagMessage> handleNlDumpMsg = (diagMsg) -> {
|
||||
if (filter.test(diagMsg)) {
|
||||
try {
|
||||
sendNetlinkDestroyRequest(destroyFd, proto, diagMsg.inetDiagMsg.id,
|
||||
diagMsg.inetDiagMsg.idiag_family, 1 << diagMsg.inetDiagMsg.idiag_state);
|
||||
destroyedSockets.getAndIncrement();
|
||||
} catch (InterruptedIOException | ErrnoException e) {
|
||||
if (!(e instanceof ErrnoException
|
||||
&& ((ErrnoException) e).errno == ENOENT)) {
|
||||
Log.e(TAG, "Failed to destroy socket: diagMsg=" + diagMsg + ", " + e);
|
||||
Consumer<InetDiagMessage> handleNlDumpMsg =
|
||||
diagMsg -> {
|
||||
if (filter.test(diagMsg)) {
|
||||
try {
|
||||
sendNetlinkDestroyRequest(
|
||||
destroyFd,
|
||||
proto,
|
||||
diagMsg.inetDiagMsg.id,
|
||||
diagMsg.inetDiagMsg.idiag_family,
|
||||
1 << diagMsg.inetDiagMsg.idiag_state);
|
||||
destroyedSockets.getAndIncrement();
|
||||
} catch (InterruptedIOException | ErrnoException e) {
|
||||
if (!(e instanceof ErrnoException
|
||||
&& ((ErrnoException) e).errno == ENOENT)) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"Failed to destroy socket: diagMsg=" + diagMsg + ", " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
NetlinkUtils.<InetDiagMessage>getAndProcessNetlinkDumpMessages(dumpReq,
|
||||
NETLINK_INET_DIAG, InetDiagMessage.class, handleNlDumpMsg);
|
||||
|
|
@ -388,6 +407,26 @@ public class InetDiagMessage extends NetlinkMessage {
|
|||
return false;
|
||||
}
|
||||
|
||||
private static boolean containsMarkMatchedToNetId(
|
||||
InetDiagMessage msg, @NonNull Set<Range<Integer>> netIdRanges) {
|
||||
final StructNlAttr attr = findInetDiagMarkAttr(msg);
|
||||
if (attr == null) return false;
|
||||
|
||||
final Integer fwMark = attr.getValueAsInteger();
|
||||
if (fwMark == null) return false;
|
||||
return CollectionUtils.any(
|
||||
netIdRanges, range -> range.contains(fwMark & FWMARK_NET_ID_MASK));
|
||||
}
|
||||
|
||||
private static StructNlAttr findInetDiagMarkAttr(InetDiagMessage msg) {
|
||||
for (StructNlAttr attr : msg.nlAttrs) {
|
||||
if (attr.nla_type == NetlinkUtils.INET_DIAG_MARK) {
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isLoopbackAddress(InetAddress addr) {
|
||||
if (addr.isLoopbackAddress()) return true;
|
||||
if (!(addr instanceof Inet6Address)) return false;
|
||||
|
|
@ -415,6 +454,17 @@ public class InetDiagMessage extends NetlinkMessage {
|
|||
|| srcAddr.equals(dstAddr);
|
||||
}
|
||||
|
||||
/** This returns the time taken to perform socket destruction including time spent in sleep. */
|
||||
private static long destroyLiveTcpSockets(Predicate<InetDiagMessage> filter)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
final long startTimeMs = SystemClock.elapsedRealtime();
|
||||
destroySockets(
|
||||
IPPROTO_TCP,
|
||||
TCP_ALIVE_STATE_FILTER,
|
||||
diagMsg -> filter.test(diagMsg) && !isLoopback(diagMsg) && !isAdbSocket(diagMsg));
|
||||
return SystemClock.elapsedRealtime() - startTimeMs;
|
||||
}
|
||||
|
||||
private static void destroySockets(int proto, int states, Predicate<InetDiagMessage> filter)
|
||||
throws ErrnoException, SocketException, InterruptedIOException {
|
||||
FileDescriptor destroyFd = null;
|
||||
|
|
@ -425,7 +475,6 @@ public class InetDiagMessage extends NetlinkMessage {
|
|||
|
||||
for (int family : List.of(AF_INET, AF_INET6)) {
|
||||
byte[] req = makeNetlinkDumpRequest(proto, states, family);
|
||||
|
||||
try {
|
||||
final int destroyedSockets = processNetlinkDumpAndDestroySockets(
|
||||
req, destroyFd, proto, filter);
|
||||
|
|
@ -456,34 +505,27 @@ public class InetDiagMessage extends NetlinkMessage {
|
|||
*/
|
||||
public static void destroyLiveTcpSockets(Set<Range<Integer>> ranges, Set<Integer> exemptUids)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
final long startTimeMs = SystemClock.elapsedRealtime();
|
||||
destroySockets(IPPROTO_TCP, TCP_ALIVE_STATE_FILTER,
|
||||
(diagMsg) -> !exemptUids.contains(diagMsg.inetDiagMsg.idiag_uid)
|
||||
&& containsUid(diagMsg, ranges)
|
||||
&& !isLoopback(diagMsg)
|
||||
&& !isAdbSocket(diagMsg));
|
||||
final long durationMs = SystemClock.elapsedRealtime() - startTimeMs;
|
||||
final long durationMs =
|
||||
destroyLiveTcpSockets(
|
||||
diagMsg ->
|
||||
!exemptUids.contains(diagMsg.inetDiagMsg.idiag_uid)
|
||||
&& containsUid(diagMsg, ranges));
|
||||
Log.d(TAG, "Destroyed live tcp sockets for uids=" + ranges + " exemptUids=" + exemptUids
|
||||
+ " in " + durationMs + "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Close tcp sockets that match the following condition
|
||||
* 1. TCP status is one of TCP_ESTABLISHED, TCP_SYN_SENT, and TCP_SYN_RECV
|
||||
* 2. Owner uid of socket is in the targetUids
|
||||
* 3. Socket is not loopback
|
||||
* 4. Socket is not adb socket
|
||||
* Close tcp sockets that match the following condition 1. TCP status is one of TCP_ESTABLISHED,
|
||||
* TCP_SYN_SENT, and TCP_SYN_RECV 2. Owner uid of socket is in the targetUids 3. Socket is not
|
||||
* loopback 4. Socket is not adb socket
|
||||
*
|
||||
* @param ownerUids target uids to close sockets
|
||||
*/
|
||||
public static void destroyLiveTcpSocketsByOwnerUids(Set<Integer> ownerUids)
|
||||
public static void destroyLiveTcpSocketsByOwnerUids(@NonNull Set<Integer> ownerUids)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
final long startTimeMs = SystemClock.elapsedRealtime();
|
||||
destroySockets(IPPROTO_TCP, TCP_ALIVE_STATE_FILTER,
|
||||
(diagMsg) -> ownerUids.contains(diagMsg.inetDiagMsg.idiag_uid)
|
||||
&& !isLoopback(diagMsg)
|
||||
&& !isAdbSocket(diagMsg));
|
||||
final long durationMs = SystemClock.elapsedRealtime() - startTimeMs;
|
||||
Objects.requireNonNull(ownerUids);
|
||||
final long durationMs =
|
||||
destroyLiveTcpSockets(diagMsg -> ownerUids.contains(diagMsg.inetDiagMsg.idiag_uid));
|
||||
Log.d(TAG, "Destroyed live tcp sockets for uids=" + ownerUids + " in " + durationMs + "ms");
|
||||
}
|
||||
|
||||
|
|
@ -511,6 +553,185 @@ public class InetDiagMessage extends NetlinkMessage {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close tcp sockets that match the following condition 1. TCP status is one of TCP_ESTABLISHED,
|
||||
* TCP_SYN_SENT, and TCP_SYN_RECV 2. Local address of socket is equal to the given address 3.
|
||||
* Socket is not loopback 4. Socket is not adb socket 5. Sockets satisfying given
|
||||
* conditions(netId ranges, UID ranges).
|
||||
*
|
||||
* @param address a local address to destroy sockets
|
||||
* @param netIdRanges ranges of net IDs need to be matched
|
||||
* @param uidRanges ranges of UIDs to destroy sockets
|
||||
*/
|
||||
public static void destroyLiveTcpSocketsByLocalAddress(
|
||||
@NonNull InetAddress address,
|
||||
@Nullable Set<Range<Integer>> netIdRanges,
|
||||
@Nullable Set<Range<Integer>> uidRanges)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
final long durationMs =
|
||||
destroyLiveTcpSockets(
|
||||
diagMsg -> matchesLocalAddressWithNetworkAndUser(
|
||||
address, netIdRanges, uidRanges, diagMsg));
|
||||
Log.d(
|
||||
TAG,
|
||||
"Destroyed live tcp sockets for local address "
|
||||
+ address
|
||||
+ ", netIdRanges"
|
||||
+ netIdRanges
|
||||
+ ", uidRanges:"
|
||||
+ uidRanges
|
||||
+ " in "
|
||||
+ durationMs
|
||||
+ "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Close tcp sockets that match the following condition 1. TCP status is one of TCP_ESTABLISHED,
|
||||
* TCP_SYN_SENT, and TCP_SYN_RECV 2. Local address of socket is equal to the given address 3.
|
||||
* Socket is not loopback 4. Socket is not adb socket 5. Sockets satisfying given
|
||||
* conditions(interface ID).
|
||||
*
|
||||
* @param address a local address to destroy sockets
|
||||
* @param interfaceId interface id needs to be referred for destroying sockets.
|
||||
*/
|
||||
public static void destroyLiveTcpSocketsByLocalAddress(
|
||||
@NonNull InetAddress address, int interfaceId)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
final long durationMs =
|
||||
destroyLiveTcpSockets(
|
||||
diagMsg ->
|
||||
matchesLocalAddressWithInterfaceId(address, interfaceId, diagMsg));
|
||||
Log.d(
|
||||
TAG,
|
||||
"Destroyed live tcp sockets for local address "
|
||||
+ address
|
||||
+ ", interfaceId:"
|
||||
+ interfaceId
|
||||
+ " in "
|
||||
+ durationMs
|
||||
+ "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the |diagMsg| matches the following conditions : - is for a socket with the
|
||||
* passed address - is on one of the passed netIds - belongs to an app with one of the passed
|
||||
* UIDs
|
||||
*
|
||||
* @param address local address to compare
|
||||
* @param netIdRanges a range of net IDs that needs to be matched. If null, netId is not
|
||||
* considered in this match result.
|
||||
* @param uidRanges ranges of UIDs to destroy sockets, If null, netId is not considered in this
|
||||
* match result.
|
||||
* @param diagMsg {@link InetDiagMessage} retrieved from kernel.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static boolean matchesLocalAddressWithNetworkAndUser(
|
||||
@NonNull InetAddress address,
|
||||
@Nullable Set<Range<Integer>> netIdRanges,
|
||||
@Nullable Set<Range<Integer>> uidRanges,
|
||||
@NonNull InetDiagMessage diagMsg) {
|
||||
if (!matchesLocalAddress(address, diagMsg)) {
|
||||
// Socket's local address should be matched to the given address.
|
||||
return false;
|
||||
}
|
||||
if (netIdRanges != null && !containsMarkMatchedToNetId(diagMsg, netIdRanges)) {
|
||||
return false;
|
||||
}
|
||||
return uidRanges == null || containsUid(diagMsg, uidRanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the address of InetDiagMessage matches the given address.
|
||||
*
|
||||
* @param address local address to compare
|
||||
* @param interfaceId interface id to be referred. If it's 0, interface id is not considered in
|
||||
* this match result.
|
||||
* @param diagMsg {@link InetDiagMessage} retrieved from kernel.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static boolean matchesLocalAddressWithInterfaceId(
|
||||
@NonNull InetAddress address, int interfaceId, @NonNull InetDiagMessage diagMsg) {
|
||||
return (interfaceId == 0 || interfaceId == diagMsg.inetDiagMsg.id.ifIndex)
|
||||
&& matchesLocalAddress(address, diagMsg);
|
||||
}
|
||||
|
||||
private static boolean matchesLocalAddress(
|
||||
@NonNull InetAddress address, @NonNull InetDiagMessage diagMsg) {
|
||||
final InetAddress addressDiag = diagMsg.inetDiagMsg.id.locSocketAddress.getAddress();
|
||||
return address.equals(addressDiag)
|
||||
|| (address instanceof Inet4Address
|
||||
&& addressDiag instanceof Inet6Address
|
||||
&& addressDiag.equals(
|
||||
// TODO: Improve performance for computing this. b/418877261
|
||||
InetAddressUtils.v4MappedV6Address((Inet4Address) address)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Close tcp sockets that match the following condition 1. TCP status is one of TCP_ESTABLISHED,
|
||||
* TCP_SYN_SENT, and TCP_SYN_RECV 2. Socket is not loopback 3. Socket is not adb socket 4.
|
||||
* Sockets on the specified netId where: - The opening app no longer has permission to use this
|
||||
* network, or: - The opening app does have permission, but did not explicitly select this
|
||||
* network.
|
||||
*
|
||||
* @param netId a network identifier needs to be referred for destroying sockets.
|
||||
* @param permission network permission, available values are INetd.PERMISSION_NETWORK or
|
||||
* INetd.PERMISSION_SYSTEM.
|
||||
*/
|
||||
public static void destroyLiveTcpSocketsLackingPermission(int netId, int permission)
|
||||
throws SocketException, InterruptedIOException, ErrnoException {
|
||||
if (permission != INetd.PERMISSION_NETWORK && permission != INetd.PERMISSION_SYSTEM) {
|
||||
throw new IllegalArgumentException("Invalid permission");
|
||||
}
|
||||
final long durationMs =
|
||||
destroyLiveTcpSockets(diagMsg -> isLackingPermission(netId, permission, diagMsg));
|
||||
Log.d(
|
||||
TAG,
|
||||
"Destroyed live tcp sockets for lacking permission for netId:"
|
||||
+ netId
|
||||
+ " and permission:"
|
||||
+ permission
|
||||
+ " in "
|
||||
+ durationMs
|
||||
+ "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the opening app lacking permission or not. "isLacking" because an app is not
|
||||
* lacking a permission on a mismatched netId, while it's not true that it would
|
||||
* "holdPermission"
|
||||
*
|
||||
* @param netId network ID to compare
|
||||
* @param permission network permission defined in INetd.aidl
|
||||
* @param diagMsg {@link InetDiagMessage} retrieved from kernel.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static boolean isLackingPermission(
|
||||
int netId, int permission, @NonNull InetDiagMessage diagMsg) {
|
||||
final int netdPermission = convertToNetdPermission(permission);
|
||||
StructNlAttr attr = findInetDiagMarkAttr(diagMsg);
|
||||
if (attr == null) return false;
|
||||
final Integer fwMark = attr.getValueAsInteger();
|
||||
if (fwMark == null) return false;
|
||||
if ((fwMark & FWMARK_NET_ID_MASK) != netId) return false;
|
||||
// Makes a mask to find matching sockets where:
|
||||
// 1. The opening app no longer has permission to use this network, or:
|
||||
// 2. The opening app does have permission, but did not explicitly select this network.
|
||||
final int mask =
|
||||
netdPermission << PERMISSION_SHIFT | 1 << EXPLICITLY_SELECTED_BIT_SHIFT;
|
||||
return (fwMark & mask) != mask;
|
||||
}
|
||||
|
||||
private static int convertToNetdPermission(int permission) {
|
||||
// CS will destroy sockets lacking permission instead of Netd, and it follows permission
|
||||
// value defined in INetd. However socket's Fwmark value follows Permission.h, thus we need
|
||||
// conversion.
|
||||
return switch (permission) {
|
||||
case INetd.PERMISSION_NETWORK -> INetd.PERMISSION_NETWORK;
|
||||
case INetd.PERMISSION_SYSTEM -> INetd.PERMISSION_SYSTEM | INetd.PERMISSION_NETWORK;
|
||||
default -> INetd.PERMISSION_NONE;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InetDiagMessage{ "
|
||||
|
|
|
|||
|
|
@ -146,6 +146,23 @@ public final class LinkPropertiesUtils {
|
|||
right != null ? right.getLinkAddresses() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the all addresses including stacked addresses in {@code left} LinkProperties with
|
||||
* {@code right} LinkProperties, examining only addresses on the base link.
|
||||
*
|
||||
* @param left A LinkProperties with the old list of all addresses.
|
||||
* @param right A LinkProperties with the new list of all addresses.
|
||||
* @return the differences between the addresses.
|
||||
*/
|
||||
public static @NonNull CompareResult<LinkAddress> compareAllAddresses(
|
||||
@Nullable LinkProperties left, @Nullable LinkProperties right) {
|
||||
/*
|
||||
* Same logic as compareAddresses()
|
||||
*/
|
||||
return new CompareResult<>(left != null ? left.getAllLinkAddresses() : null,
|
||||
right != null ? right.getAllLinkAddresses() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares {@code left} {@code LinkProperties} allLinkAddresses against the {@code right}.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -36,10 +36,13 @@ import static org.junit.Assert.assertNull;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.net.INetd;
|
||||
import android.net.InetAddresses;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Range;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
|
|
@ -56,6 +59,7 @@ import java.nio.ByteBuffer;
|
|||
import java.nio.ByteOrder;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
|
|
@ -800,4 +804,201 @@ public class InetDiagSocketTest {
|
|||
doTestIsAdbSocket(ROOT_UID, false /* expected */);
|
||||
doTestIsAdbSocket(appUid, false /* expected */);
|
||||
}
|
||||
|
||||
private static final String INET_DIAG_MSG_MAPPED6 =
|
||||
// struct nlmsghdr
|
||||
"6C000000" // length = 108
|
||||
+ "1400" // type = SOCK_DIAG_BY_FAMILY
|
||||
+ "0000" // flags
|
||||
+ "00000000" // seqno
|
||||
+ "f5220000" // pid
|
||||
// struct inet_diag_msg
|
||||
+ "0a" // family = AF_INET6
|
||||
+ "02" // idiag_state = 2
|
||||
+ "10" // idiag_timer = 16
|
||||
+ "20" // idiag_retrans = 32
|
||||
// inet_diag_sockid
|
||||
+ "a845" // idiag_sport = 43077
|
||||
+ "01bb" // idiag_dport = 443
|
||||
+ "00000000000000000000ffffc0000201" // idiag_src = ::FFFF:192.0.2.1
|
||||
+ "00000000000000000000ffffc0000202" // idiag_dst = ::FFFF:192.0.2.2
|
||||
+ "08000000" // idiag_if = 8
|
||||
+ "6300000000000000" // idiag_cookie = 99
|
||||
+ "30000000" // idiag_expires = 48
|
||||
+ "40000000" // idiag_rqueue = 64
|
||||
+ "50000000" // idiag_wqueue = 80
|
||||
+ "39300000" // idiag_uid = 12345
|
||||
+ "851a0000" // idiag_inode = 6789
|
||||
+ "0500" // len = 5
|
||||
+ "0800" // type = 8
|
||||
+ "00000000" // data
|
||||
+ "0800" // len = 8
|
||||
+ "0F00" // type = 15(INET_DIAG_MARK)
|
||||
+ "65000500" // Fwmark netId:101, explicitlySelected: true, permission: network
|
||||
+ "0400" // len = 4
|
||||
+ "0200"; // type = 2
|
||||
|
||||
private static final String INET_DIAG_MSG_MAPPED6_OEM_NET_ID =
|
||||
// struct nlmsghdr
|
||||
"6C000000" // length = 108
|
||||
+ "1400" // type = SOCK_DIAG_BY_FAMILY
|
||||
+ "0000" // flags
|
||||
+ "00000000" // seqno
|
||||
+ "f5220000" // pid
|
||||
// struct inet_diag_msg
|
||||
+ "0a" // family = AF_INET6
|
||||
+ "02" // idiag_state = 2
|
||||
+ "10" // idiag_timer = 16
|
||||
+ "20" // idiag_retrans = 32
|
||||
// inet_diag_sockid
|
||||
+ "a845" // idiag_sport = 43077
|
||||
+ "01bb" // idiag_dport = 443
|
||||
+ "00000000000000000000ffffc0000201" // idiag_src = ::FFFF:192.0.2.1
|
||||
+ "00000000000000000000ffffc0000202" // idiag_dst = ::FFFF:192.0.2.2
|
||||
+ "08000000" // idiag_if = 8
|
||||
+ "6300000000000000" // idiag_cookie = 99
|
||||
+ "30000000" // idiag_expires = 48
|
||||
+ "40000000" // idiag_rqueue = 64
|
||||
+ "50000000" // idiag_wqueue = 80
|
||||
+ "39300000" // idiag_uid = 12345
|
||||
+ "851a0000" // idiag_inode = 6789
|
||||
+ "0500" // len = 5
|
||||
+ "0800" // type = 8
|
||||
+ "00000000" // data
|
||||
+ "0800" // len = 8
|
||||
+ "0F00" // type = 15(INET_DIAG_MARK)
|
||||
+ "23000500" // Fwmark netId:35, explicitlySelected: true, permission: network
|
||||
+ "0400" // len = 4
|
||||
+ "0200"; // type = 2
|
||||
|
||||
private boolean matchesDestroyCondition(
|
||||
@NonNull String nlmsg, @NonNull Predicate<InetDiagMessage> condition) {
|
||||
final byte[] nlMsgByte = HexEncoding.decode(nlmsg.toCharArray(), false);
|
||||
final ByteBuffer byteBuffer = ByteBuffer.wrap(nlMsgByte);
|
||||
byteBuffer.order(ByteOrder.nativeOrder());
|
||||
final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG);
|
||||
final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
|
||||
return condition.test(inetDiagMsg);
|
||||
}
|
||||
|
||||
private void doTestMatchingLocalAddress(
|
||||
boolean expected,
|
||||
@NonNull InetAddress address,
|
||||
@Nullable Set<Range<Integer>> netIdRanges,
|
||||
@Nullable Set<Range<Integer>> uidRanges,
|
||||
@NonNull String nlmsg) {
|
||||
assertEquals(
|
||||
expected,
|
||||
matchesDestroyCondition(
|
||||
nlmsg,
|
||||
diagMsg ->
|
||||
InetDiagMessage.matchesLocalAddressWithNetworkAndUser(
|
||||
address, netIdRanges, uidRanges, diagMsg)));
|
||||
}
|
||||
|
||||
private void doTestMatchingLocalAddress(
|
||||
boolean expected,
|
||||
@NonNull InetAddress address,
|
||||
int interfaceId,
|
||||
@NonNull String nlmsg) {
|
||||
assertEquals(
|
||||
expected,
|
||||
matchesDestroyCondition(
|
||||
nlmsg,
|
||||
diagMsg ->
|
||||
InetDiagMessage.matchesLocalAddressWithInterfaceId(
|
||||
address, interfaceId, diagMsg)));
|
||||
}
|
||||
|
||||
private void doTestIsLackingPermission(
|
||||
boolean expected, int netId, int permission, @NonNull String nlmsg) {
|
||||
assertEquals(
|
||||
expected,
|
||||
matchesDestroyCondition(
|
||||
nlmsg,
|
||||
diagMsg ->
|
||||
InetDiagMessage.isLackingPermission(netId, permission, diagMsg)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchingDestroyConditionAddressOnly() {
|
||||
doTestMatchingLocalAddress(
|
||||
true, /* expected */
|
||||
InetAddresses.parseNumericAddress("2001:db8::3"),
|
||||
null, /* netIdRange */
|
||||
null, /* uidRanges */
|
||||
INET_DIAG_MSG_HEX2);
|
||||
doTestMatchingLocalAddress(
|
||||
true, /* expected */
|
||||
InetAddresses.parseNumericAddress("192.0.2.1"),
|
||||
Set.of(new Range<>(100, 65535)),
|
||||
null, /* uidRanges */
|
||||
INET_DIAG_MSG_MAPPED6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchingDestroyConditionForInterfaceId() {
|
||||
doTestMatchingLocalAddress(
|
||||
false /* expected */,
|
||||
InetAddresses.parseNumericAddress("192.0.2.1"),
|
||||
7 /* interfaceId */,
|
||||
INET_DIAG_MSG_MAPPED6);
|
||||
doTestMatchingLocalAddress(
|
||||
true /* expected */,
|
||||
InetAddresses.parseNumericAddress("192.0.2.1"),
|
||||
8 /* interfaceId */,
|
||||
INET_DIAG_MSG_MAPPED6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchingDestroyConditionForNetIdRange() {
|
||||
doTestMatchingLocalAddress(
|
||||
false /* expected */,
|
||||
InetAddresses.parseNumericAddress("192.0.2.1"),
|
||||
Set.of(new Range<>(0, 99)),
|
||||
null /* uidRanges */,
|
||||
INET_DIAG_MSG_MAPPED6);
|
||||
doTestMatchingLocalAddress(
|
||||
true /* expected */,
|
||||
InetAddresses.parseNumericAddress("192.0.2.1"),
|
||||
Set.of(new Range<>(0, 99)),
|
||||
null /* uidRanges */,
|
||||
INET_DIAG_MSG_MAPPED6_OEM_NET_ID);
|
||||
doTestMatchingLocalAddress(
|
||||
false /* expected */,
|
||||
InetAddresses.parseNumericAddress("192.0.2.1"),
|
||||
Set.of(new Range<>(100, 100), new Range<>(102, 65535)),
|
||||
null /* uidRanges */,
|
||||
INET_DIAG_MSG_MAPPED6);
|
||||
doTestMatchingLocalAddress(
|
||||
true /* expected */,
|
||||
InetAddresses.parseNumericAddress("192.0.2.1"),
|
||||
Set.of(new Range<>(0, 20), new Range<>(100, 65535)),
|
||||
null /* uidRanges */,
|
||||
INET_DIAG_MSG_MAPPED6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchingDestroyConditionForUidRanges() {
|
||||
doTestMatchingLocalAddress(
|
||||
true /* expected */,
|
||||
InetAddresses.parseNumericAddress("2001:db8::3"),
|
||||
Set.of(new Range<>(100, 65535)),
|
||||
Set.of(new Range<>(12000, 12346)),
|
||||
INET_DIAG_MSG_HEX2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsLackingPermission() {
|
||||
int systemPermission = INetd.PERMISSION_SYSTEM;
|
||||
int netPermission = INetd.PERMISSION_NETWORK;
|
||||
doTestIsLackingPermission(
|
||||
true /* expected */, 101 /* netId */, systemPermission, INET_DIAG_MSG_MAPPED6);
|
||||
doTestIsLackingPermission(
|
||||
false /* expected */, 100 /* netId */, systemPermission, INET_DIAG_MSG_MAPPED6);
|
||||
doTestIsLackingPermission(
|
||||
false /* expected */, 101 /* netId */, netPermission, INET_DIAG_MSG_MAPPED6);
|
||||
doTestIsLackingPermission(
|
||||
true /* expected */, 2693 /* netId */, systemPermission, INET_DIAG_MSG_HEX2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,12 +214,12 @@ import static org.junit.Assume.assumeFalse;
|
|||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.mockito.AdditionalMatchers.aryEq;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
|
|
@ -405,7 +405,6 @@ import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInf
|
|||
import com.android.server.ConnectivityService.NetworkRequestInfo;
|
||||
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.DestroySocketsWrapper;
|
||||
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
|
||||
import com.android.server.L2capNetworkProvider;
|
||||
import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
|
||||
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
|
||||
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 org.junit.Assert.assertEquals;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Range;
|
||||
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Tests for IntegerRangeUtils.
|
||||
*/
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
public class IntegerRangeUtilsTest {
|
||||
@Test
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
public void testRangeWithoutValues() {
|
||||
final Range<Integer> source1 = Range.create(100, 200);
|
||||
// Basic separation after excluding exempt Integer list.
|
||||
final List<Integer> exempt1 = List.of(110, 170);
|
||||
final Set<Range<Integer>> expected1 = Set.of(
|
||||
Range.create(100, 109),
|
||||
Range.create(111, 169),
|
||||
Range.create(171, 200));
|
||||
// Check continuous exempt list.
|
||||
final List<Integer> exempt2 = List.of(110, 111, 112, 114);
|
||||
final Set<Range<Integer>> expected2 = Set.of(
|
||||
Range.create(100, 109),
|
||||
Range.create(113, 113),
|
||||
Range.create(115, 200));
|
||||
// Check handling exempted Integer on the edge of range.
|
||||
final List<Integer> exempt3 = List.of(100, 199, 200);
|
||||
final Set<Range<Integer>> expected3 = Set.of(
|
||||
Range.create(101, 198)
|
||||
);
|
||||
final List<Integer> exempt4 = List.of(100, 101, 102, 200);
|
||||
final Set<Range<Integer>> expected4 = Set.of(
|
||||
Range.create(103, 199)
|
||||
);
|
||||
// Check handling exempted Integer list out of the source range.
|
||||
final List<Integer> exempt5 = List.of(99, 201);
|
||||
final Set<Range<Integer>> expected5 = Set.of(
|
||||
Range.create(100, 200)
|
||||
);
|
||||
final List<Integer> exempt6 = List.of(99, 150, 200, 201);
|
||||
final Set<Range<Integer>> expected6 = Set.of(
|
||||
Range.create(100, 149),
|
||||
Range.create(151, 199)
|
||||
);
|
||||
// Check handling unordered exempted Integer list.
|
||||
final List<Integer> exempt7 = List.of(200, 99, 201, 150, 117);
|
||||
final Set<Range<Integer>> expected7 = Set.of(
|
||||
Range.create(100, 116),
|
||||
Range.create(118, 149),
|
||||
Range.create(151, 199)
|
||||
);
|
||||
assertEquals(expected1, IntegerRangeUtils.rangeWithoutValues(source1, exempt1));
|
||||
assertEquals(expected2, IntegerRangeUtils.rangeWithoutValues(source1, exempt2));
|
||||
assertEquals(expected3, IntegerRangeUtils.rangeWithoutValues(source1, exempt3));
|
||||
assertEquals(expected4, IntegerRangeUtils.rangeWithoutValues(source1, exempt4));
|
||||
assertEquals(expected5, IntegerRangeUtils.rangeWithoutValues(source1, exempt5));
|
||||
assertEquals(expected6, IntegerRangeUtils.rangeWithoutValues(source1, exempt6));
|
||||
assertEquals(expected7, IntegerRangeUtils.rangeWithoutValues(source1, exempt7));
|
||||
// Check the case that source range is included in exempted list.
|
||||
final Range<Integer> source2 = Range.create(100, 101);
|
||||
final List<Integer> exempt8 = List.of(99, 100, 101);
|
||||
final Set<Range<Integer>> expected8 = Set.of();
|
||||
assertEquals(expected8, IntegerRangeUtils.rangeWithoutValues(source2, exempt8));
|
||||
}
|
||||
}
|
||||
|
|
@ -24,38 +24,105 @@ import android.net.ConnectivityManager.BLOCKED_REASON_NONE
|
|||
import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
|
||||
import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
|
||||
import android.net.ConnectivityManager.FIREWALL_RULE_DENY
|
||||
import android.net.INetd
|
||||
import android.net.LinkAddress
|
||||
import android.net.LinkProperties
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
|
||||
import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
|
||||
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
|
||||
import android.net.NetworkCapabilities.TRANSPORT_TEST
|
||||
import android.net.NetworkCapabilities.TRANSPORT_VPN
|
||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
||||
import android.net.NetworkRequest
|
||||
import android.net.VpnManager
|
||||
import android.net.VpnTransportInfo
|
||||
import android.os.Build
|
||||
import android.util.Range
|
||||
import com.android.net.module.util.BaseNetdUnsolicitedEventListener
|
||||
import com.android.net.module.util.netlink.NetlinkConstants
|
||||
import com.android.net.module.util.netlink.RtNetlinkAddressMessage
|
||||
import com.android.net.module.util.netlink.StructIfaddrMsg
|
||||
import com.android.net.module.util.netlink.StructNlMsgHdr
|
||||
import com.android.server.NetIdManager.MAX_NET_ID
|
||||
import com.android.server.NetIdManager.MIN_NET_ID
|
||||
import com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS
|
||||
import com.android.testutils.DevSdkIgnoreRule
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnNetworkDestroyed
|
||||
import com.android.testutils.TestableNetworkCallback
|
||||
import com.android.testutils.TestableNetworkCallback.Event.CapabilitiesChanged
|
||||
import com.android.testutils.TestableNetworkCallback.Event.LinkPropertiesChanged
|
||||
import com.android.testutils.TestableNetworkCallback.Event.Lost
|
||||
import java.net.Inet6Address
|
||||
import java.net.InetAddress
|
||||
import junit.framework.Assert.assertFalse
|
||||
import junit.framework.Assert.assertTrue
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.doNothing
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.timeout
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
|
||||
private const val TIMESTAMP = 1234L
|
||||
private const val TEST_UID = 1234
|
||||
private const val TEST_UID2 = 5678
|
||||
private val TEST_UID_RANGE1 = Range(10000, 20000)
|
||||
private val TEST_UID_RANGE2 = Range(21000, 30000)
|
||||
private val TEST_UID_RANGE3 = Range(31000, 40000)
|
||||
private const val TEST_CELL_IFACE = "test_rmnet"
|
||||
|
||||
private fun cellNc() = NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
|
||||
.build()
|
||||
private fun cellNc() = makeNc(TRANSPORT_CELLULAR)
|
||||
|
||||
private fun cellLp() = LinkProperties().also{
|
||||
it.interfaceName = TEST_CELL_IFACE
|
||||
private fun makeNc(transportType: Int) = nc(transportType, NET_CAPABILITY_INTERNET)
|
||||
|
||||
private fun cellLp() = makeLp(TEST_CELL_IFACE)
|
||||
|
||||
private fun vpnNc(uidRanges: Set<Range<Int>>) = NetworkCapabilities.Builder()
|
||||
.addTransportType(TRANSPORT_VPN)
|
||||
.setTransportInfo(
|
||||
VpnTransportInfo(
|
||||
VpnManager.TYPE_VPN_PLATFORM,
|
||||
null /* sessionId */,
|
||||
false /* bypassable */,
|
||||
false /* longLivedTcpConnectionsExpensive */
|
||||
)
|
||||
)
|
||||
.removeCapability(NET_CAPABILITY_NOT_VPN)
|
||||
.addCapability(NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
|
||||
.setUids(uidRanges)
|
||||
.build()
|
||||
|
||||
private fun makeLp(interfaceName: String) = LinkProperties().also{
|
||||
it.interfaceName = interfaceName
|
||||
}
|
||||
|
||||
private fun makeRequest(nc: NetworkCapabilities) = NetworkRequest.Builder()
|
||||
.clearCapabilities()
|
||||
.setCapabilities(nc)
|
||||
.build()
|
||||
|
||||
private fun makeVpnRequest(uidRanges: Set<Range<Int>>) = NetworkRequest.Builder()
|
||||
.clearCapabilities()
|
||||
.addTransportType(TRANSPORT_VPN)
|
||||
.setUids(uidRanges)
|
||||
.build()
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
class CSDestroySocketTest : CSTest() {
|
||||
|
|
@ -336,4 +403,469 @@ class CSDestroySocketTest : CSTest() {
|
|||
expectDestroySockets = false
|
||||
)
|
||||
}
|
||||
|
||||
private fun InetAddress.toLinkAddress() =
|
||||
LinkAddress(this, if (this is Inet6Address) 64 else 24)
|
||||
|
||||
private fun prepareNetworkAgent(
|
||||
interfaceName: String,
|
||||
addresses: List<LinkAddress>,
|
||||
nc: NetworkCapabilities
|
||||
): Pair<CSAgentWrapper, TestableNetworkCallback> {
|
||||
val callback = TestableNetworkCallback()
|
||||
cm.registerNetworkCallback(makeRequest(nc), callback)
|
||||
val linkProperties = makeLp(interfaceName)
|
||||
for (linkAddress in addresses) {
|
||||
linkProperties.addLinkAddress(linkAddress)
|
||||
}
|
||||
val agent = Agent(nc = nc, lp = linkProperties)
|
||||
agent.connect()
|
||||
return agent to callback
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIpToNetworksMap() {
|
||||
val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111")
|
||||
val addressV6_2 = InetAddress.getByName("2001:DB8:0200::2222")
|
||||
val addressV6_3 = InetAddress.getByName("2001:DB8:0333::3333")
|
||||
val addressV6LinkLocal = InetAddress.getByName("FE80::1234")
|
||||
val addressV4_1 = InetAddress.getByName("192.0.2.10")
|
||||
val addressV4_2 = InetAddress.getByName("192.0.2.11")
|
||||
// Creates 3 NetworkAgent with various IP addresses(some are used for only one network
|
||||
// agent some are used for multiple network agents.)
|
||||
|
||||
val (cellAgent, cellCallback) = prepareNetworkAgent(
|
||||
"rmnet1",
|
||||
arrayListOf(addressV6_1.toLinkAddress(), addressV4_1.toLinkAddress()),
|
||||
makeNc(transportType = TRANSPORT_CELLULAR)
|
||||
)
|
||||
|
||||
val (wlanAgent, wlanCallback) = prepareNetworkAgent(
|
||||
"wlan1",
|
||||
arrayListOf(addressV6_2.toLinkAddress(), addressV4_1.toLinkAddress()),
|
||||
makeNc(transportType = TRANSPORT_WIFI)
|
||||
)
|
||||
val (ethAgent, ethCallback) = prepareNetworkAgent(
|
||||
"eth2",
|
||||
arrayListOf(addressV6_2.toLinkAddress(), addressV4_2.toLinkAddress()),
|
||||
makeNc(transportType = TRANSPORT_ETHERNET)
|
||||
)
|
||||
val ipToNetworksMap = assertNotNull(service.mIpToNetworksMap)
|
||||
fun InetAddress.getNetworks() = ipToNetworksMap[this]!!.map{ it.network }.toSet()
|
||||
|
||||
// Verify mIpToNetworksMap properly stores network agents based on IP addresses.
|
||||
assertEquals(setOf(cellAgent.network), addressV6_1.getNetworks())
|
||||
assertEquals(
|
||||
setOf(wlanAgent.network, ethAgent.network),
|
||||
addressV6_2.getNetworks()
|
||||
)
|
||||
assertEquals(
|
||||
setOf(wlanAgent.network, cellAgent.network),
|
||||
addressV4_1.getNetworks()
|
||||
)
|
||||
assertEquals(setOf(ethAgent.network), addressV4_2.getNetworks())
|
||||
|
||||
// Disconnect network agent#2 & #3.
|
||||
wlanAgent.disconnect()
|
||||
ethAgent.disconnect()
|
||||
// Update only interface name without IP address change for network agent #1.
|
||||
val linkProperties = makeLp("ipsec1")
|
||||
linkProperties.addLinkAddress(addressV4_1.toLinkAddress())
|
||||
linkProperties.addLinkAddress(addressV6_1.toLinkAddress())
|
||||
cellAgent.sendLinkProperties(linkProperties)
|
||||
cellCallback.eventuallyExpect<LinkPropertiesChanged> {
|
||||
it.network == cellAgent.network && it.lp.interfaceName == "ipsec1"
|
||||
}
|
||||
|
||||
// Verify mIpToNetworksMap for the LinkPropertiesChanged
|
||||
assertFalse(ipToNetworksMap.containsKey(addressV6_2))
|
||||
assertFalse(ipToNetworksMap.containsKey(addressV4_2))
|
||||
assertEquals(setOf(cellAgent.network), addressV6_1.getNetworks())
|
||||
assertEquals(setOf(cellAgent.network), addressV4_1.getNetworks())
|
||||
|
||||
// Change IP addresses for network agent #1.
|
||||
val linkProperties2 = makeLp("rmnet1")
|
||||
linkProperties2.addLinkAddress(addressV4_2.toLinkAddress())
|
||||
linkProperties2.addLinkAddress(addressV6_1.toLinkAddress())
|
||||
linkProperties2.addLinkAddress(addressV6_3.toLinkAddress())
|
||||
cellAgent.sendLinkProperties(linkProperties2)
|
||||
// Add network agent#4
|
||||
val (agent4, callback4) = prepareNetworkAgent(
|
||||
"bt1",
|
||||
arrayListOf(addressV6LinkLocal.toLinkAddress(), addressV4_2.toLinkAddress()),
|
||||
makeNc(transportType = TRANSPORT_BLUETOOTH)
|
||||
)
|
||||
assertEquals(3, ipToNetworksMap.size)
|
||||
assertEquals(setOf(cellAgent.network), addressV6_1.getNetworks())
|
||||
assertEquals(
|
||||
setOf(cellAgent.network, agent4.network),
|
||||
addressV4_2.getNetworks()
|
||||
)
|
||||
assertEquals(setOf(cellAgent.network), addressV6_3.getNetworks())
|
||||
assertFalse(ipToNetworksMap.containsKey(addressV4_1))
|
||||
// This map doesn't handle IPv6 link local addresses.
|
||||
assertFalse(ipToNetworksMap.containsKey(addressV6LinkLocal))
|
||||
|
||||
// Disconnect all remaining network agents.
|
||||
cellAgent.disconnect()
|
||||
agent4.disconnect()
|
||||
agent4.eventuallyExpect<OnNetworkDestroyed>()
|
||||
// Verify mIpToNetworksMap is empty.
|
||||
assertTrue(ipToNetworksMap.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDestroySocketForRemovedIpAddressFromSingleNetwork() {
|
||||
val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111")
|
||||
val addressV6_2 = InetAddress.getByName("2001:DB8:0200::2222")
|
||||
// add a test network.
|
||||
val (agent1, callback1) = prepareNetworkAgent(
|
||||
"wlan2",
|
||||
arrayListOf(addressV6_1.toLinkAddress()),
|
||||
makeNc(transportType = TRANSPORT_WIFI)
|
||||
)
|
||||
|
||||
// IP address changed
|
||||
val linkProperties2 = makeLp("wlan2")
|
||||
linkProperties2.addLinkAddress(addressV6_2.toLinkAddress())
|
||||
agent1.sendLinkProperties(linkProperties2)
|
||||
callback1.eventuallyExpect<LinkPropertiesChanged> {
|
||||
it.network == agent1.network && !it.lp.addresses.contains(addressV6_1)
|
||||
}
|
||||
// verify destroy sockets on the removed IP address
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_1,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
|
||||
// network disconnect
|
||||
agent1.disconnect()
|
||||
agent1.eventuallyExpect<OnNetworkDestroyed>()
|
||||
// verify destroy sockets on the removed IP address
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_2,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByLocalAddress(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDestroySocketForRemovedIpAddressFromMultipleNonVpnNetworks() {
|
||||
val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111")
|
||||
val addressV6_2 = InetAddress.getByName("2001:DB8:0200::2222")
|
||||
val addressV6LinkLocal = InetAddress.getByName("FE80::1234")
|
||||
val addressV4_1 = InetAddress.getByName("192.0.2.10")
|
||||
|
||||
// add 4 test networks(non VPN). 3 have a same IP address, and 1 network has different one.
|
||||
val (cellAgent, cellCallback) = prepareNetworkAgent(
|
||||
"rmnet1",
|
||||
arrayListOf(addressV6_1.toLinkAddress(), addressV4_1.toLinkAddress()),
|
||||
makeNc(transportType = TRANSPORT_CELLULAR)
|
||||
)
|
||||
|
||||
val (wlanAgent, wlanCallback) = prepareNetworkAgent(
|
||||
"wlan0",
|
||||
arrayListOf(),
|
||||
makeNc(transportType = TRANSPORT_WIFI)
|
||||
)
|
||||
|
||||
val (testAgent, testCallback) = prepareNetworkAgent(
|
||||
"test2",
|
||||
arrayListOf(addressV6_2.toLinkAddress()),
|
||||
makeNc(transportType = TRANSPORT_TEST)
|
||||
)
|
||||
|
||||
val (ethAgent, ethCallback) = prepareNetworkAgent(
|
||||
"eth1",
|
||||
arrayListOf(addressV6_1.toLinkAddress()),
|
||||
makeNc(transportType = TRANSPORT_ETHERNET)
|
||||
)
|
||||
|
||||
// Add IP addresses on network#2
|
||||
val linkProperties = makeLp("wlan0")
|
||||
linkProperties.addLinkAddress(addressV6LinkLocal.toLinkAddress())
|
||||
linkProperties.addLinkAddress(addressV6_1.toLinkAddress())
|
||||
wlanAgent.sendLinkProperties(linkProperties)
|
||||
|
||||
// Remove IP addresses network#1
|
||||
val linkProperties1 = makeLp("rmnet1")
|
||||
cellAgent.sendLinkProperties(linkProperties1)
|
||||
cellCallback.eventuallyExpect<LinkPropertiesChanged> {
|
||||
it.network == cellAgent.network && it.lp.addresses.size == 0
|
||||
}
|
||||
|
||||
// verify destroy sockets on the removed IP address and exempt netId lists.
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_1,
|
||||
setOf(
|
||||
Range.create(MIN_NET_ID, wlanAgent.network.netId - 1),
|
||||
Range.create(wlanAgent.network.netId + 1, ethAgent.network.netId - 1),
|
||||
Range.create(ethAgent.network.netId + 1, MAX_NET_ID)
|
||||
),
|
||||
null
|
||||
)
|
||||
// verify destroy sockets on the removed IP address
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV4_1,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
|
||||
// Remove IP addresses from network#2
|
||||
val linkProperties2 = makeLp("wlan3")
|
||||
wlanAgent.sendLinkProperties(linkProperties2)
|
||||
wlanCallback.eventuallyExpect<LinkPropertiesChanged> {
|
||||
it.network == wlanAgent.network && it.lp.interfaceName == "wlan3"
|
||||
}
|
||||
// verify destroy sockets on the removed IP address and exempt netId lists.
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_1,
|
||||
setOf(
|
||||
Range.create(MIN_NET_ID, ethAgent.network.netId - 1),
|
||||
Range.create(ethAgent.network.netId + 1, MAX_NET_ID)
|
||||
),
|
||||
null
|
||||
)
|
||||
// verify not to destroy sockets on the link local address at NetworkAgent update.
|
||||
verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6LinkLocal,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
|
||||
// disconnect all test networks
|
||||
cellAgent.disconnect()
|
||||
wlanAgent.disconnect()
|
||||
testAgent.disconnect()
|
||||
ethAgent.disconnect()
|
||||
ethAgent.eventuallyExpect<OnNetworkDestroyed>()
|
||||
|
||||
// verify destroy sockets on the removed IP address
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_2,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_1,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByLocalAddress(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDestroySocketForRemovedIpAddressFromMultipleVpnNetworks() {
|
||||
val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111")
|
||||
val addressV6_2 = InetAddress.getByName("2001:DB8:0200::2222")
|
||||
val addressV6_3 = InetAddress.getByName("2001:DB8:0333::3333")
|
||||
val addressV6LinkLocal = InetAddress.getByName("FE80::1234")
|
||||
val addressV4_1 = InetAddress.getByName("192.0.2.10")
|
||||
// add 3 VPN test networks and 1 non-VPN network.
|
||||
|
||||
val (vpnAgent1, vpnCallback1) = prepareNetworkAgent(
|
||||
"ipsec1",
|
||||
arrayListOf(addressV6_1.toLinkAddress(), addressV4_1.toLinkAddress()),
|
||||
vpnNc(setOf(TEST_UID_RANGE1))
|
||||
)
|
||||
|
||||
val (vpnAgent2, vpnCallback2) = prepareNetworkAgent(
|
||||
"ipsec2",
|
||||
arrayListOf(addressV6LinkLocal.toLinkAddress(), addressV6_1.toLinkAddress()),
|
||||
vpnNc(setOf(TEST_UID_RANGE2))
|
||||
)
|
||||
val (vpnAgent3, vpnCallback3) = prepareNetworkAgent(
|
||||
"ipsec3",
|
||||
arrayListOf(addressV6_1.toLinkAddress()),
|
||||
vpnNc(setOf(TEST_UID_RANGE3))
|
||||
)
|
||||
val (testAgent, testCallback) = prepareNetworkAgent(
|
||||
"test4",
|
||||
arrayListOf(addressV6_2.toLinkAddress(), addressV4_1.toLinkAddress()),
|
||||
makeNc(TRANSPORT_TEST)
|
||||
)
|
||||
// Remove IP addresses network#1
|
||||
val linkProperties1 = makeLp("ipsec21")
|
||||
vpnAgent1.sendLinkProperties(linkProperties1)
|
||||
vpnCallback1.eventuallyExpect<LinkPropertiesChanged> {
|
||||
it.network == vpnAgent1.network && it.lp.interfaceName == "ipsec21"
|
||||
}
|
||||
// verify destroy sockets on the removed IP address(TEST_IPV6_ADDRESS1) and VPN's uid range.
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_1,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
vpnAgent1.nc.uids
|
||||
)
|
||||
// verify destroy sockets on the removed IP address(TEST_IPV4_ADDRESS1). network#1 and
|
||||
// network#4 use TEST_IPV4_ADDRESS1, however network#4 is not VPN network, so we destroy
|
||||
// sockets only based on the removed IP address.
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV4_1,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
|
||||
// Change IP addresses network#2
|
||||
val linkProperties2 = makeLp("ipsec2")
|
||||
linkProperties2.addLinkAddress(addressV6_3.toLinkAddress())
|
||||
vpnAgent2.sendLinkProperties(linkProperties2)
|
||||
vpnCallback2.eventuallyExpect<LinkPropertiesChanged> { it.network == vpnAgent2.network &&
|
||||
it.lp.interfaceName == "ipsec2" && it.lp.addresses.contains(addressV6_3)
|
||||
}
|
||||
// verify destroy sockets on the removed IP address(TEST_IPV6_ADDRESS1) and VPN's uid range.
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_1,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
vpnAgent2.nc.uids
|
||||
)
|
||||
// verify not to destroy sockets on the link local address at NetworkAgent update.
|
||||
verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6LinkLocal,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
|
||||
// disconnect all test networks
|
||||
vpnAgent1.disconnect()
|
||||
vpnAgent2.disconnect()
|
||||
vpnAgent3.disconnect()
|
||||
testAgent.disconnect()
|
||||
testAgent.eventuallyExpect<OnNetworkDestroyed>()
|
||||
|
||||
// verify destroy sockets on the removed IP address.
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_3,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
verify(destroySocketsWrapper).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6_2,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
verify(destroySocketsWrapper, times(2)).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV4_1,
|
||||
setOf(Range.create(MIN_NET_ID, MAX_NET_ID)),
|
||||
null
|
||||
)
|
||||
verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByLocalAddress(any(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDestroySocketWithNetlinkMessageForLinkLocalAddress() {
|
||||
val addressV6LinkLocal = InetAddress.getByName("FE80::1234")
|
||||
val testInterfaceId = 10
|
||||
val nlMsgLinkLocalAddressRemoved = RtNetlinkAddressMessage(
|
||||
StructNlMsgHdr(
|
||||
30 /* payload length */,
|
||||
NetlinkConstants.RTM_DELADDR,
|
||||
0 /* flages */,
|
||||
0 /* seq */
|
||||
),
|
||||
StructIfaddrMsg(
|
||||
10 /* family */,
|
||||
64 /* prefix length */,
|
||||
0 /* flags */,
|
||||
0 /* scope */,
|
||||
testInterfaceId /* interfaceId */
|
||||
),
|
||||
addressV6LinkLocal,
|
||||
null,
|
||||
0 /* flags */
|
||||
)
|
||||
csHandler.post{deps.netlinkMessageUpdate?.accept(nlMsgLinkLocalAddressRemoved)}
|
||||
|
||||
// verify destroy a socket on link local address with corresponding interface ID.
|
||||
verify(destroySocketsWrapper, timeout(100)).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV6LinkLocal,
|
||||
testInterfaceId
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDestroySocketWithNetlinkMessageForOemNetwork() {
|
||||
val addressV4 = InetAddress.getByName("203.0.113.10")
|
||||
val nlMsgAddressRemoved = RtNetlinkAddressMessage(
|
||||
StructNlMsgHdr(
|
||||
30 /* payload length */,
|
||||
NetlinkConstants.RTM_DELADDR,
|
||||
0 /* flags */,
|
||||
0 /* seq */
|
||||
),
|
||||
StructIfaddrMsg(
|
||||
10 /* family */,
|
||||
64 /* prefix length */,
|
||||
0 /* flags */,
|
||||
0 /* scope */,
|
||||
5 /* interfaceId */
|
||||
),
|
||||
addressV4,
|
||||
null,
|
||||
0 /* flags */
|
||||
)
|
||||
csHandler.post{deps.netlinkMessageUpdate?.accept(nlMsgAddressRemoved)}
|
||||
|
||||
// verify destroy non platform socket(netId < MIN_NET_ID) with the RTM_DELADDR message
|
||||
verify(destroySocketsWrapper, timeout(100)).destroyLiveTcpSocketsByLocalAddress(
|
||||
addressV4,
|
||||
setOf(Range.create(0, MIN_NET_ID - 1)),
|
||||
null /* uidRanges */
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDestroySocketsLackingPermissionForPermissionUpdate() {
|
||||
val inOrder = inOrder(destroySocketsWrapper)
|
||||
val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111")
|
||||
val addressV4_1 = InetAddress.getByName("192.0.2.10")
|
||||
|
||||
doNothing().`when`(netd).networkSetPermissionForNetwork(anyInt(), anyInt())
|
||||
|
||||
val ncRestricted =
|
||||
cellNc().removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||
val ncNotRestricted =
|
||||
cellNc().addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||
|
||||
val (agent, callback) = prepareNetworkAgent(
|
||||
"rmnet1",
|
||||
arrayListOf(addressV6_1.toLinkAddress(), addressV4_1.toLinkAddress()),
|
||||
ncRestricted
|
||||
)
|
||||
|
||||
agent.sendNetworkCapabilities(ncNotRestricted)
|
||||
callback.eventuallyExpect<CapabilitiesChanged> {
|
||||
it.network == agent.network &&
|
||||
it.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||
}
|
||||
inOrder.verify(destroySocketsWrapper, never())
|
||||
.destroyLiveTcpSocketsLackingPermission(anyInt(), anyInt())
|
||||
agent.sendNetworkCapabilities(ncRestricted)
|
||||
callback.eventuallyExpect<Lost>()
|
||||
inOrder.verify(destroySocketsWrapper, times(2))
|
||||
.destroyLiveTcpSocketsLackingPermission(
|
||||
agent.network.netId,
|
||||
INetd.PERMISSION_SYSTEM
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDestroySocketsLackingPermissionForDelayedTeardown() {
|
||||
val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111")
|
||||
doNothing().`when`(netd).networkSetPermissionForNetwork(anyInt(), anyInt())
|
||||
val (agent, callback) = prepareNetworkAgent(
|
||||
"rmnet1",
|
||||
arrayListOf(addressV6_1.toLinkAddress()),
|
||||
makeNc(TRANSPORT_CELLULAR)
|
||||
)
|
||||
agent.sendTeardownDelayMs(1)
|
||||
agent.disconnect()
|
||||
agent.eventuallyExpect<OnNetworkDestroyed>()
|
||||
|
||||
verify(destroySocketsWrapper, times(2)).destroyLiveTcpSocketsLackingPermission(
|
||||
agent.network.netId,
|
||||
INetd.PERMISSION_SYSTEM
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import android.net.INetworkMonitor
|
|||
import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS
|
||||
import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP
|
||||
import android.net.INetworkMonitorCallbacks
|
||||
import android.net.KeepalivePacketData
|
||||
import android.net.LinkProperties
|
||||
import android.net.LocalNetworkConfig
|
||||
import android.net.Network
|
||||
|
|
@ -35,13 +36,36 @@ import android.net.NetworkProvider
|
|||
import android.net.NetworkRequest
|
||||
import android.net.NetworkScore
|
||||
import android.net.NetworkTestResultParcelable
|
||||
import android.net.QosFilter
|
||||
import android.net.Uri
|
||||
import android.net.networkstack.NetworkStackClientBase
|
||||
import android.os.HandlerThread
|
||||
import android.os.Looper
|
||||
import com.android.net.module.util.ArrayTrackRecord
|
||||
import com.android.testutils.TestableNetworkAgent.Event
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnAddKeepalivePacketFilter
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnAutomaticReconnectDisabled
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnBandwidthUpdateRequested
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnDscpPolicyStatusUpdated
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnNetworkCreated
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnNetworkDestroyed
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnNetworkUnwanted
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnRegisterQosCallback
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnRemoveKeepalivePacketFilter
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnSaveAcceptUnvalidated
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnSignalStrengthThresholdsUpdated
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnStartSocketKeepalive
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnStopSocketKeepalive
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnUnregisterQosCallback
|
||||
import com.android.testutils.TestableNetworkAgent.Event.OnValidationStatus
|
||||
import com.android.testutils.TestableNetworkCallback
|
||||
import com.android.testutils.TestableNetworkCallback.Event.Available
|
||||
import com.android.testutils.TestableNetworkCallback.Event.Lost
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
|
|
@ -87,9 +111,104 @@ class CSAgentWrapper(
|
|||
private var nmValidationResult = NO_PROBE_RESULT
|
||||
private var nmProbesCompleted = NO_PROBE_RESULT
|
||||
private var nmProbesSucceeded = NO_PROBE_RESULT
|
||||
val history = ArrayTrackRecord<Event>().newReadHead()
|
||||
val DEFAULT_TIMEOUT_MS = 5000L
|
||||
|
||||
override val network: Network get() = agent.network!!
|
||||
|
||||
inner class TestAgent : NetworkAgent {
|
||||
constructor(
|
||||
context: Context,
|
||||
looper: Looper,
|
||||
tag: String,
|
||||
nc: NetworkCapabilities,
|
||||
lp: LinkProperties,
|
||||
lnc: LocalNetworkConfig?,
|
||||
score: NetworkScore,
|
||||
nac: NetworkAgentConfig,
|
||||
provider: NetworkProvider?
|
||||
) :
|
||||
super(context, looper, tag, nc, lp, lnc, score, nac, provider) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
context: Context,
|
||||
looper: Looper,
|
||||
tag: String,
|
||||
nc: NetworkCapabilities,
|
||||
lp: LinkProperties,
|
||||
score: Int,
|
||||
nac: NetworkAgentConfig,
|
||||
provider: NetworkProvider?
|
||||
) :
|
||||
super(context, looper, tag, nc, lp, score, nac, provider) {
|
||||
}
|
||||
|
||||
override fun onBandwidthUpdateRequested() {
|
||||
history.add(OnBandwidthUpdateRequested)
|
||||
}
|
||||
|
||||
override fun onNetworkUnwanted() {
|
||||
history.add(OnNetworkUnwanted)
|
||||
}
|
||||
|
||||
override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) {
|
||||
history.add(OnAddKeepalivePacketFilter(slot, packet))
|
||||
}
|
||||
|
||||
override fun onRemoveKeepalivePacketFilter(slot: Int) {
|
||||
history.add(OnRemoveKeepalivePacketFilter(slot))
|
||||
}
|
||||
|
||||
override fun onStartSocketKeepalive(
|
||||
slot: Int,
|
||||
interval: Duration,
|
||||
packet: KeepalivePacketData
|
||||
) {
|
||||
history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet))
|
||||
}
|
||||
|
||||
override fun onStopSocketKeepalive(slot: Int) {
|
||||
history.add(OnStopSocketKeepalive(slot))
|
||||
}
|
||||
|
||||
override fun onSaveAcceptUnvalidated(accept: Boolean) {
|
||||
history.add(OnSaveAcceptUnvalidated(accept))
|
||||
}
|
||||
|
||||
override fun onAutomaticReconnectDisabled() {
|
||||
history.add(OnAutomaticReconnectDisabled)
|
||||
}
|
||||
|
||||
override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) {
|
||||
history.add(OnSignalStrengthThresholdsUpdated(thresholds))
|
||||
}
|
||||
|
||||
override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) {
|
||||
history.add(OnRegisterQosCallback(qosCallbackId, filter))
|
||||
}
|
||||
|
||||
override fun onQosCallbackUnregistered(qosCallbackId: Int) {
|
||||
history.add(OnUnregisterQosCallback(qosCallbackId))
|
||||
}
|
||||
|
||||
override fun onValidationStatus(status: Int, uri: Uri?) {
|
||||
history.add(OnValidationStatus(status, uri))
|
||||
}
|
||||
|
||||
override fun onNetworkCreated() {
|
||||
history.add(OnNetworkCreated)
|
||||
}
|
||||
|
||||
override fun onNetworkDestroyed() {
|
||||
history.add(OnNetworkDestroyed)
|
||||
}
|
||||
|
||||
override fun onDscpPolicyStatusUpdated(policyId: Int, status: Int) {
|
||||
history.add(OnDscpPolicyStatusUpdated(policyId, status))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// Capture network monitor callbacks and simulate network monitor
|
||||
val validateAnswer = Answer {
|
||||
|
|
@ -108,12 +227,13 @@ class CSAgentWrapper(
|
|||
)
|
||||
|
||||
// Create the actual agent. NetworkAgent is abstract, so make an anonymous subclass.
|
||||
if (deps.isAtLeastS()) {
|
||||
agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
|
||||
nc, lp, lnc?.value, score.value, nac, provider) {}
|
||||
agent = if (deps.isAtLeastS()) {
|
||||
TestAgent(
|
||||
context, csHandlerThread.looper, TAG, nc, lp, lnc?.value,
|
||||
score.value, nac, provider)
|
||||
} else {
|
||||
agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
|
||||
nc, lp, 50 /* score */, nac, provider) {}
|
||||
TestAgent(
|
||||
context, csHandlerThread.looper, TAG, nc, lp, 50 /* score */, nac, provider)
|
||||
}
|
||||
agent.register()
|
||||
assertEquals(agent.network!!.netId, nmNetworkCaptor.value.netId)
|
||||
|
|
@ -200,6 +320,7 @@ class CSAgentWrapper(
|
|||
fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
|
||||
fun sendNetworkCapabilities(nc: NetworkCapabilities) = agent.sendNetworkCapabilities(nc)
|
||||
fun sendLinkProperties(lp: LinkProperties) = agent.sendLinkProperties(lp)
|
||||
fun sendTeardownDelayMs(delayMs: Int) = agent.setTeardownDelayMillis(delayMs)
|
||||
|
||||
fun connectWithCaptivePortal(redirectUrl: String) {
|
||||
setCaptivePortal(redirectUrl)
|
||||
|
|
@ -225,4 +346,16 @@ class CSAgentWrapper(
|
|||
probesSucceeded = NO_PROBE_RESULT
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <reified T : Event> expect(test: (T) -> Boolean = { true }): T {
|
||||
val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
|
||||
assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
|
||||
assertTrue(test(foundCallback), "Unexpected callback : $foundCallback")
|
||||
return foundCallback
|
||||
}
|
||||
|
||||
inline fun <reified T : Event> eventuallyExpect() =
|
||||
history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also {
|
||||
assertNotNull(it, "Callback ${T::class} not received")
|
||||
} as T
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,12 +61,15 @@ import android.permission.PermissionManager.PermissionResult
|
|||
import android.telephony.SubscriptionManager
|
||||
import android.telephony.TelephonyManager
|
||||
import android.testing.TestableContext
|
||||
import android.util.Range
|
||||
import android.util.SparseArray
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.internal.app.IBatteryStats
|
||||
import com.android.internal.util.test.BroadcastInterceptingContext
|
||||
import com.android.modules.utils.build.SdkLevel
|
||||
import com.android.net.module.util.ArrayTrackRecord
|
||||
import com.android.net.module.util.SharedLog
|
||||
import com.android.net.module.util.netlink.NetlinkMessage
|
||||
import com.android.networkstack.apishim.common.UnsupportedApiLevelException
|
||||
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker
|
||||
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
|
||||
|
|
@ -84,10 +87,12 @@ import com.android.server.connectivity.QuicConnectionCloser
|
|||
import com.android.server.connectivity.SatelliteAccessController
|
||||
import com.android.testutils.visibleOnHandlerThread
|
||||
import com.android.testutils.waitForIdle
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.BiConsumer
|
||||
import java.util.function.Consumer
|
||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
import kotlin.test.assertNotNull
|
||||
|
|
@ -288,8 +293,22 @@ open class CSTest {
|
|||
}
|
||||
|
||||
// Class to be mocked and used to verify destroy sockets methods call
|
||||
// TODO: Move to use TestableCallback-style object with a TrackRecord inside to check.
|
||||
open inner class DestroySocketsWrapper {
|
||||
open fun destroyLiveTcpSocketsByOwnerUids(ownerUids: Set<Int>) {}
|
||||
open fun destroyLiveTcpSocketsByLocalAddress(
|
||||
address: InetAddress,
|
||||
netIdRange: Set<Range<Int>>?,
|
||||
uidRanges: Set<Range<Int>>?
|
||||
) {}
|
||||
open fun destroyLiveTcpSocketsByLocalAddress(
|
||||
address: InetAddress?,
|
||||
interfaceId: Int
|
||||
) {}
|
||||
open fun destroyLiveTcpSocketsLackingPermission(
|
||||
netId: Int,
|
||||
permission: Int
|
||||
) {}
|
||||
}
|
||||
|
||||
inner class CSDeps : ConnectivityService.Dependencies() {
|
||||
|
|
@ -316,7 +335,6 @@ open class CSTest {
|
|||
listener: BiConsumer<Int, Int>,
|
||||
handler: Handler
|
||||
) = if (SdkLevel.isAtLeastT()) mock<CarrierPrivilegeAuthenticator>() else null
|
||||
|
||||
var satelliteNetworkFallbackUidUpdate = BiConsumer<Set<Int>, Set<Int>> {_, _ -> }
|
||||
override fun makeSatelliteAccessController(
|
||||
context: Context,
|
||||
|
|
@ -428,6 +446,55 @@ open class CSTest {
|
|||
networkForNetId: SparseArray<NetworkAgentInfo>,
|
||||
handler: Handler
|
||||
): QuicConnectionCloser = quicConnectionCloser
|
||||
|
||||
override fun flagConnectivityServiceDestroySocket() = true
|
||||
|
||||
override fun destroyLiveTcpSocketsByLocalAddress(
|
||||
address: InetAddress,
|
||||
netIdRange: Set<Range<Int>>?,
|
||||
uidRanges: Set<Range<Int>>?
|
||||
) {
|
||||
// Call mocked destroyLiveTcpSocketsByLocalAddress so that test can verify this method
|
||||
// call
|
||||
destroySocketsWrapper.destroyLiveTcpSocketsByLocalAddress(
|
||||
address,
|
||||
netIdRange,
|
||||
uidRanges
|
||||
)
|
||||
}
|
||||
|
||||
override fun destroyLiveTcpSocketsByLocalAddress(
|
||||
address: InetAddress,
|
||||
interfaceId: Int
|
||||
) {
|
||||
// Call mocked destroyLiveTcpSocketsByLocalAddress so that test can verify this method
|
||||
// call
|
||||
destroySocketsWrapper.destroyLiveTcpSocketsByLocalAddress(
|
||||
address,
|
||||
interfaceId
|
||||
)
|
||||
}
|
||||
|
||||
override fun destroyLiveTcpSocketsLackingPermission(
|
||||
netId: Int,
|
||||
permission: Int
|
||||
) {
|
||||
destroySocketsWrapper.destroyLiveTcpSocketsLackingPermission(
|
||||
netId,
|
||||
permission
|
||||
)
|
||||
}
|
||||
|
||||
var netlinkMessageUpdate = Consumer<NetlinkMessage> {_ -> }
|
||||
override fun makeAddressUpdateMonitor(
|
||||
h: Handler,
|
||||
log: SharedLog,
|
||||
tag: String,
|
||||
consumer: Consumer<NetlinkMessage>
|
||||
): ConnectivityService.AddressUpdateMonitor {
|
||||
netlinkMessageUpdate = consumer
|
||||
return ConnectivityService.AddressUpdateMonitor(h, log, tag, consumer)
|
||||
}
|
||||
}
|
||||
|
||||
inner class PermDeps : PermissionMonitor.Dependencies() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue