Files
android_packages_modules_Co…/service/src/com/android/server/ConnectivityService.java

11147 lines
498 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2008 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;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_DNS_EVENTS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_TCP_METRICS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY;
import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired;
import static android.os.Process.INVALID_UID;
import static android.os.Process.VPN_UID;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.system.OsConstants.ETH_P_ALL;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
import static java.util.Map.Entry;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.CaptivePortal;
import android.net.CaptivePortalData;
import android.net.ConnectionInfo;
import android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
import android.net.ConnectivityDiagnosticsManager.DataStallReport;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.BlockedReason;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.RestrictBackgroundStatus;
import android.net.ConnectivityResources;
import android.net.ConnectivitySettingsManager;
import android.net.DataStallReportParcelable;
import android.net.DnsResolverServiceManager;
import android.net.DscpPolicy;
import android.net.ICaptivePortal;
import android.net.IConnectivityDiagnosticsCallback;
import android.net.IConnectivityManager;
import android.net.IDnsResolver;
import android.net.INetd;
import android.net.INetworkActivityListener;
import android.net.INetworkAgent;
import android.net.INetworkMonitor;
import android.net.INetworkMonitorCallbacks;
import android.net.INetworkOfferCallback;
import android.net.IOnCompleteListener;
import android.net.IQosCallback;
import android.net.ISocketKeepaliveCallback;
import android.net.InetAddresses;
import android.net.IpMemoryStore;
import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.MatchAllNetworkSpecifier;
import android.net.NativeNetworkConfig;
import android.net.NativeNetworkType;
import android.net.NattSocketKeepalive;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkMonitorManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkPolicyManager.NetworkPolicyCallback;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkState;
import android.net.NetworkStateSnapshot;
import android.net.NetworkTestResultParcelable;
import android.net.NetworkUtils;
import android.net.NetworkWatchlistManager;
import android.net.OemNetworkPreferences;
import android.net.PrivateDnsConfigParcel;
import android.net.ProfileNetworkPreference;
import android.net.ProxyInfo;
import android.net.QosCallbackException;
import android.net.QosFilter;
import android.net.QosSocketFilter;
import android.net.QosSocketInfo;
import android.net.RouteInfo;
import android.net.RouteInfoParcel;
import android.net.SocketKeepalive;
import android.net.TetheringManager;
import android.net.TransportInfo;
import android.net.UidRange;
import android.net.UidRangeParcel;
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnTransportInfo;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
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.resolv.aidl.DnsHealthEventParcel;
import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.PrivateDnsConfig;
import android.net.util.InterfaceParams;
import android.net.util.MultinetworkPolicyTracker;
import android.os.BatteryStatsManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.sysprop.NetworkProperties;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.connectivity.resources.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils;
import com.android.modules.utils.BasicShellCommandHandler;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.DeviceConfigUtils;
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.NetworkCapabilitiesUtils;
import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.TcUtils;
import com.android.net.module.util.netlink.InetDiagMessage;
import com.android.server.connectivity.AutodestructReference;
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ConnectivityFlags;
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.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.NetworkOffer;
import com.android.server.connectivity.NetworkRanker;
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProfileNetworkPreferenceList;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.connectivity.UidRangeUtils;
import libcore.io.IoUtils;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringJoiner;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
*/
public class ConnectivityService extends IConnectivityManager.Stub
implements PendingIntent.OnFinished {
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final String DIAG_ARG = "--diag";
public static final String SHORT_ARG = "--short";
private static final String NETWORK_ARG = "networks";
private static final String REQUEST_ARG = "requests";
private static final String TRAFFICCONTROLLER_ARG = "trafficcontroller";
private static final boolean DBG = true;
private static final boolean DDBG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean LOGD_BLOCKED_NETWORKINFO = true;
/**
* Default URL to use for {@link #getCaptivePortalServerUrl()}. This should not be changed
* by OEMs for configuration purposes, as this value is overridden by
* ConnectivitySettingsManager.CAPTIVE_PORTAL_HTTP_URL.
* R.string.config_networkCaptivePortalServerUrl should be overridden instead for this purpose
* (preferably via runtime resource overlays).
*/
private static final String DEFAULT_CAPTIVE_PORTAL_HTTP_URL =
"http://connectivitycheck.gstatic.com/generate_204";
// TODO: create better separation between radio types and network types
// how long to wait before switching back to a radio's default network
private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
// system property that can override the above value
private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
"android.telephony.apn-restore";
// How long to wait before putting up a "This network doesn't have an Internet connection,
// connect anyway?" dialog after the user selects a network that doesn't validate.
private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
// Default to 30s linger time-out, and 5s for nascent network. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
private static final int DEFAULT_NASCENT_DELAY_MS = 5_000;
// The maximum number of network request allowed per uid before an exception is thrown.
@VisibleForTesting
static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
// The maximum number of network request allowed for system UIDs before an exception is thrown.
@VisibleForTesting
static final int MAX_NETWORK_REQUESTS_PER_SYSTEM_UID = 250;
@VisibleForTesting
protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it.
@VisibleForTesting
protected int mNascentDelayMs;
// True if the cell radio of the device is capable of time-sharing.
@VisibleForTesting
protected boolean mCellularRadioTimesharingCapable = true;
// How long to delay to removal of a pending intent based request.
// See ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
private final int mReleasePendingIntentDelayMs;
private MockableSystemProperties mSystemProperties;
@VisibleForTesting
protected final PermissionMonitor mPermissionMonitor;
@VisibleForTesting
final PerUidCounter mNetworkRequestCounter;
@VisibleForTesting
final PerUidCounter mSystemNetworkRequestCounter;
private volatile boolean mLockdownEnabled;
/**
* Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in
* internal handler thread, they don't need a lock.
*/
private SparseIntArray mUidBlockedReasons = new SparseIntArray();
private final Context mContext;
private final ConnectivityResources mResources;
// The Context is created for UserHandle.ALL.
private final Context mUserAllContext;
private final Dependencies mDeps;
private final ConnectivityFlags mFlags;
// 0 is full bad, 100 is full good
private int mDefaultInetConditionPublished = 0;
@VisibleForTesting
protected IDnsResolver mDnsResolver;
@VisibleForTesting
protected INetd mNetd;
private DscpPolicyTracker mDscpPolicyTracker = null;
private NetworkStatsManager mStatsManager;
private NetworkPolicyManager mPolicyManager;
private final NetdCallback mNetdCallback;
private final BpfNetMaps mBpfNetMaps;
/**
* TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple
* instances.
*/
@GuardedBy("mTNSLock")
private TestNetworkService mTNS;
private final Object mTNSLock = new Object();
private String mCurrentTcpBufferSizes;
private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
new Class[] { ConnectivityService.class, NetworkAgent.class, NetworkAgentInfo.class });
private enum ReapUnvalidatedNetworks {
// Tear down networks that have no chance (e.g. even if validated) of becoming
// the highest scoring network satisfying a NetworkRequest. This should be passed when
// all networks have been rematched against all NetworkRequests.
REAP,
// Don't reap networks. This should be passed when some networks have not yet been
// rematched against all NetworkRequests.
DONT_REAP
}
private enum UnneededFor {
LINGER, // Determine whether this network is unneeded and should be lingered.
TEARDOWN, // Determine whether this network is unneeded and should be torn down.
}
/**
* For per-app preferences, requests contain an int to signify which request
* should have priority. The order is passed to netd which will use it together
* with UID ranges to generate the corresponding IP rule. This serves to
* direct device-originated data traffic of the specific UIDs to the correct
* default network for each app.
* Order ints passed to netd must be in the 0~999 range. Larger values code for
* a lower priority, {@see NativeUidRangeConfig}
*
* Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
* The default request uses PREFERENCE_ORDER_DEFAULT.
*/
// Used when sending to netd to code for "no order".
static final int PREFERENCE_ORDER_NONE = 0;
// Order for requests that don't code for a per-app preference. As it is
// out of the valid range, the corresponding order should be
// PREFERENCE_ORDER_NONE when sending to netd.
@VisibleForTesting
static final int PREFERENCE_ORDER_INVALID = Integer.MAX_VALUE;
// As a security feature, VPNs have the top priority.
static final int PREFERENCE_ORDER_VPN = 0; // Netd supports only 0 for VPN.
// Order of per-app OEM preference. See {@link #setOemNetworkPreference}.
@VisibleForTesting
static final int PREFERENCE_ORDER_OEM = 10;
// Order of per-profile preference, such as used by enterprise networks.
// See {@link #setProfileNetworkPreference}.
@VisibleForTesting
static final int PREFERENCE_ORDER_PROFILE = 20;
// Order of user setting to prefer mobile data even when networks with
// better scores are connected.
// See {@link ConnectivitySettingsManager#setMobileDataPreferredUids}
@VisibleForTesting
static final int PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED = 30;
// Preference order that signifies the network shouldn't be set as a default network for
// the UIDs, only give them access to it. TODO : replace this with a boolean
// in NativeUidRangeConfig
@VisibleForTesting
static final int PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT = 999;
// Bound for the lowest valid preference order.
static final int PREFERENCE_ORDER_LOWEST = 999;
/**
* used internally to clear a wakelock when transitioning
* from one net to another. Clear happens when we get a new
* network - EVENT_EXPIRE_NET_TRANSITION_WAKELOCK happens
* after a timeout if no network is found (typically 1 min).
*/
private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8;
/**
* used internally to reload global proxy settings
*/
private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = 9;
/**
* PAC manager has received new port.
*/
private static final int EVENT_PROXY_HAS_CHANGED = 16;
/**
* used internally when registering NetworkProviders
* obj = NetworkProviderInfo
*/
private static final int EVENT_REGISTER_NETWORK_PROVIDER = 17;
/**
* used internally when registering NetworkAgents
* obj = Messenger
*/
private static final int EVENT_REGISTER_NETWORK_AGENT = 18;
/**
* used to add a network request
* includes a NetworkRequestInfo
*/
private static final int EVENT_REGISTER_NETWORK_REQUEST = 19;
/**
* indicates a timeout period is over - check if we had a network yet or not
* and if not, call the timeout callback (but leave the request live until they
* cancel it.
* includes a NetworkRequestInfo
*/
private static final int EVENT_TIMEOUT_NETWORK_REQUEST = 20;
/**
* used to add a network listener - no request
* includes a NetworkRequestInfo
*/
private static final int EVENT_REGISTER_NETWORK_LISTENER = 21;
/**
* used to remove a network request, either a listener or a real request
* arg1 = UID of caller
* obj = NetworkRequest
*/
private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
/**
* used internally when registering NetworkProviders
* obj = Messenger
*/
private static final int EVENT_UNREGISTER_NETWORK_PROVIDER = 23;
/**
* used internally to expire a wakelock when transitioning
* from one net to another. Expire happens when we fail to find
* a new network (typically after 1 minute) -
* EVENT_CLEAR_NET_TRANSITION_WAKELOCK happens if we had found
* a replacement network.
*/
private static final int EVENT_EXPIRE_NET_TRANSITION_WAKELOCK = 24;
/**
* used to add a network request with a pending intent
* obj = NetworkRequestInfo
*/
private static final int EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT = 26;
/**
* used to remove a pending intent and its associated network request.
* arg1 = UID of caller
* obj = PendingIntent
*/
private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27;
/**
* used to specify whether a network should be used even if unvalidated.
* arg1 = whether to accept the network if it's unvalidated (1 or 0)
* arg2 = whether to remember this choice in the future (1 or 0)
* obj = network
*/
private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
/**
* used to ask the user to confirm a connection to an unvalidated network.
* obj = network
*/
private static final int EVENT_PROMPT_UNVALIDATED = 29;
/**
* used internally to (re)configure always-on networks.
*/
private static final int EVENT_CONFIGURE_ALWAYS_ON_NETWORKS = 30;
/**
* used to add a network listener with a pending intent
* obj = NetworkRequestInfo
*/
private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
/**
* used to specify whether a network should not be penalized when it becomes unvalidated.
*/
private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
/**
* used to handle reported network connectivity. May trigger revalidation of a network.
*/
private static final int EVENT_REPORT_NETWORK_CONNECTIVITY = 36;
// Handle changes in Private DNS settings.
private static final int EVENT_PRIVATE_DNS_SETTINGS_CHANGED = 37;
// Handle private DNS validation status updates.
private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
/**
* used to remove a network request, either a listener or a real request and call unavailable
* arg1 = UID of caller
* obj = NetworkRequest
*/
private static final int EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE = 39;
/**
* Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
* been tested.
* obj = {@link NetworkTestedResults} representing information sent from NetworkMonitor.
* data = PersistableBundle of extras passed from NetworkMonitor. If {@link
* NetworkMonitorCallbacks#notifyNetworkTested} is called, this will be null.
*/
private static final int EVENT_NETWORK_TESTED = 41;
/**
* Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the private DNS
* config was resolved.
* obj = PrivateDnsConfig
* arg2 = netid
*/
private static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = 42;
/**
* Request ConnectivityService display provisioning notification.
* arg1 = Whether to make the notification visible.
* arg2 = NetID.
* obj = Intent to be launched when notification selected by user, null if !arg1.
*/
private static final int EVENT_PROVISIONING_NOTIFICATION = 43;
/**
* Used to specify whether a network should be used even if connectivity is partial.
* arg1 = whether to accept the network if its connectivity is partial (1 for true or 0 for
* false)
* arg2 = whether to remember this choice in the future (1 for true or 0 for false)
* obj = network
*/
private static final int EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY = 44;
/**
* Event for NetworkMonitor to inform ConnectivityService that the probe status has changed.
* Both of the arguments are bitmasks, and the value of bits come from
* INetworkMonitor.NETWORK_VALIDATION_PROBE_*.
* arg1 = unused
* arg2 = netId
* obj = A Pair of integers: the bitmasks of, respectively, completed and successful probes.
*/
public static final int EVENT_PROBE_STATUS_CHANGED = 45;
/**
* Event for NetworkMonitor to inform ConnectivityService that captive portal data has changed.
* arg1 = unused
* arg2 = netId
* obj = captive portal data
*/
private static final int EVENT_CAPPORT_DATA_CHANGED = 46;
/**
* Used by setRequireVpnForUids.
* arg1 = whether the specified UID ranges are required to use a VPN.
* obj = Array of UidRange objects.
*/
private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;
/**
* Used internally when setting the default networks for OemNetworkPreferences.
* obj = Pair<OemNetworkPreferences, listener>
*/
private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48;
/**
* Used to indicate the system default network becomes active.
*/
private static final int EVENT_REPORT_NETWORK_ACTIVITY = 49;
/**
* Used internally when setting a network preference for a user profile.
* obj = Pair<ProfileNetworkPreference, Listener>
*/
private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50;
/**
* Event to specify that reasons for why an uid is blocked changed.
* arg1 = uid
* arg2 = blockedReasons
*/
private static final int EVENT_UID_BLOCKED_REASON_CHANGED = 51;
/**
* Event to register a new network offer
* obj = NetworkOffer
*/
private static final int EVENT_REGISTER_NETWORK_OFFER = 52;
/**
* Event to unregister an existing network offer
* obj = INetworkOfferCallback
*/
private static final int EVENT_UNREGISTER_NETWORK_OFFER = 53;
/**
* Used internally when MOBILE_DATA_PREFERRED_UIDS setting changed.
*/
private static final int EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED = 54;
/**
* Event to set temporary allow bad wifi within a limited time to override
* {@code config_networkAvoidBadWifi}.
*/
private static final int EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL = 55;
/**
* Used internally when INGRESS_RATE_LIMIT_BYTES_PER_SECOND setting changes.
*/
private static final int EVENT_INGRESS_RATE_LIMIT_CHANGED = 56;
/**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
private static final int PROVISIONING_NOTIFICATION_SHOW = 1;
/**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be hidden.
*/
private static final int PROVISIONING_NOTIFICATION_HIDE = 0;
/**
* The maximum alive time to allow bad wifi configuration for testing.
*/
private static final long MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS = 5 * 60 * 1000L;
/**
* The priority of the tc police rate limiter -- smaller value is higher priority.
* This value needs to be coordinated with PRIO_CLAT, PRIO_TETHER4, and PRIO_TETHER6.
*/
private static final short TC_PRIO_POLICE = 1;
/**
* The BPF program attached to the tc-police hook to account for to-be-dropped traffic.
*/
private static final String TC_POLICE_BPF_PROG_PATH =
"/sys/fs/bpf/prog_netd_schedact_ingress_account";
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
}
private static IDnsResolver getDnsResolver(Context context) {
final DnsResolverServiceManager dsm = context.getSystemService(
DnsResolverServiceManager.class);
return IDnsResolver.Stub.asInterface(dsm.getService());
}
/** Handler thread used for all of the handlers below. */
@VisibleForTesting
protected final HandlerThread mHandlerThread;
/** Handler used for internal events. */
final private InternalHandler mHandler;
/** Handler used for incoming {@link NetworkStateTracker} events. */
final private NetworkStateTrackerHandler mTrackerHandler;
/** Handler used for processing {@link android.net.ConnectivityDiagnosticsManager} events */
@VisibleForTesting
final ConnectivityDiagnosticsHandler mConnectivityDiagnosticsHandler;
private final DnsManager mDnsManager;
private final NetworkRanker mNetworkRanker;
private boolean mSystemReady;
private Intent mInitialBroadcast;
private PowerManager.WakeLock mNetTransitionWakeLock;
private final PowerManager.WakeLock mPendingIntentWakeLock;
// A helper object to track the current default HTTP proxy. ConnectivityService needs to tell
// the world when it changes.
@VisibleForTesting
protected final ProxyTracker mProxyTracker;
final private SettingsObserver mSettingsObserver;
private UserManager mUserManager;
// the set of network types that can only be enabled by system/sig apps
private List<Integer> mProtectedNetworks;
private Set<String> mWolSupportedInterfaces;
private final TelephonyManager mTelephonyManager;
private final CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
private final AppOpsManager mAppOpsManager;
private final LocationPermissionChecker mLocationPermissionChecker;
private KeepaliveTracker mKeepaliveTracker;
private QosCallbackTracker mQosCallbackTracker;
private NetworkNotificationManager mNotifier;
private LingerMonitor mLingerMonitor;
// sequence number of NetworkRequests
private int mNextNetworkRequestId = NetworkRequest.FIRST_REQUEST_ID;
// Sequence number for NetworkProvider IDs.
private final AtomicInteger mNextNetworkProviderId = new AtomicInteger(
NetworkProvider.FIRST_PROVIDER_ID);
// NetworkRequest activity String log entries.
private static final int MAX_NETWORK_REQUEST_LOGS = 20;
private final LocalLog mNetworkRequestInfoLogs = new LocalLog(MAX_NETWORK_REQUEST_LOGS);
// NetworkInfo blocked and unblocked String log entries
private static final int MAX_NETWORK_INFO_LOGS = 40;
private final LocalLog mNetworkInfoBlockingLogs = new LocalLog(MAX_NETWORK_INFO_LOGS);
private static final int MAX_WAKELOCK_LOGS = 20;
private final LocalLog mWakelockLogs = new LocalLog(MAX_WAKELOCK_LOGS);
private int mTotalWakelockAcquisitions = 0;
private int mTotalWakelockReleases = 0;
private long mTotalWakelockDurationMs = 0;
private long mMaxWakelockDurationMs = 0;
private long mLastWakeLockAcquireTimestamp = 0;
private final IpConnectivityLog mMetricsLog;
@GuardedBy("mBandwidthRequests")
private final SparseArray<Integer> mBandwidthRequests = new SparseArray(10);
@VisibleForTesting
final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
@VisibleForTesting
final Map<IBinder, ConnectivityDiagnosticsCallbackInfo> mConnectivityDiagnosticsCallbacks =
new HashMap<>();
// Rate limit applicable to all internet capable networks (-1 = disabled). This value is
// configured via {@link
// ConnectivitySettingsManager#INGRESS_RATE_LIMIT_BYTES_PER_SECOND}
// Only the handler thread is allowed to access this field.
private long mIngressRateLimit = -1;
/**
* Implements support for the legacy "one network per network type" model.
*
* We used to have a static array of NetworkStateTrackers, one for each
* network type, but that doesn't work any more now that we can have,
* for example, more that one wifi network. This class stores all the
* NetworkAgentInfo objects that support a given type, but the legacy
* API will only see the first one.
*
* It serves two main purposes:
*
* 1. Provide information about "the network for a given type" (since this
* API only supports one).
* 2. Send legacy connectivity change broadcasts. Broadcasts are sent if
* the first network for a given type changes, or if the default network
* changes.
*/
@VisibleForTesting
static class LegacyTypeTracker {
private static final boolean DBG = true;
private static final boolean VDBG = false;
/**
* Array of lists, one per legacy network type (e.g., TYPE_MOBILE_MMS).
* Each list holds references to all NetworkAgentInfos that are used to
* satisfy requests for that network type.
*
* This array is built out at startup such that an unsupported network
* doesn't get an ArrayList instance, making this a tristate:
* unsupported, supported but not active and active.
*
* The actual lists are populated when we scan the network types that
* are supported on this device.
*
* Threading model:
* - addSupportedType() is only called in the constructor
* - add(), update(), remove() are only called from the ConnectivityService handler thread.
* They are therefore not thread-safe with respect to each other.
* - getNetworkForType() can be called at any time on binder threads. It is synchronized
* on mTypeLists to be thread-safe with respect to a concurrent remove call.
* - getRestoreTimerForType(type) is also synchronized on mTypeLists.
* - dump is thread-safe with respect to concurrent add and remove calls.
*/
private final ArrayList<NetworkAgentInfo> mTypeLists[];
@NonNull
private final ConnectivityService mService;
// Restore timers for requestNetworkForFeature (network type -> timer in ms). Types without
// an entry have no timer (equivalent to -1). Lazily loaded.
@NonNull
private ArrayMap<Integer, Integer> mRestoreTimers = new ArrayMap<>();
LegacyTypeTracker(@NonNull ConnectivityService service) {
mService = service;
mTypeLists = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
}
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is
// addressed.
@TargetApi(Build.VERSION_CODES.S)
public void loadSupportedTypes(@NonNull Context ctx, @NonNull TelephonyManager tm) {
final PackageManager pm = ctx.getPackageManager();
if (pm.hasSystemFeature(FEATURE_WIFI)) {
addSupportedType(TYPE_WIFI);
}
if (pm.hasSystemFeature(FEATURE_WIFI_DIRECT)) {
addSupportedType(TYPE_WIFI_P2P);
}
if (tm.isDataCapable()) {
// Telephony does not have granular support for these types: they are either all
// supported, or none is supported
addSupportedType(TYPE_MOBILE);
addSupportedType(TYPE_MOBILE_MMS);
addSupportedType(TYPE_MOBILE_SUPL);
addSupportedType(TYPE_MOBILE_DUN);
addSupportedType(TYPE_MOBILE_HIPRI);
addSupportedType(TYPE_MOBILE_FOTA);
addSupportedType(TYPE_MOBILE_IMS);
addSupportedType(TYPE_MOBILE_CBS);
addSupportedType(TYPE_MOBILE_IA);
addSupportedType(TYPE_MOBILE_EMERGENCY);
}
if (pm.hasSystemFeature(FEATURE_BLUETOOTH)) {
addSupportedType(TYPE_BLUETOOTH);
}
if (pm.hasSystemFeature(FEATURE_WATCH)) {
// TYPE_PROXY is only used on Wear
addSupportedType(TYPE_PROXY);
}
// Ethernet is often not specified in the configs, although many devices can use it via
// USB host adapters. Add it as long as the ethernet service is here.
if (ctx.getSystemService(Context.ETHERNET_SERVICE) != null) {
addSupportedType(TYPE_ETHERNET);
}
// Always add TYPE_VPN as a supported type
addSupportedType(TYPE_VPN);
}
private void addSupportedType(int type) {
if (mTypeLists[type] != null) {
throw new IllegalStateException(
"legacy list for type " + type + "already initialized");
}
mTypeLists[type] = new ArrayList<>();
}
public boolean isTypeSupported(int type) {
return isNetworkTypeValid(type) && mTypeLists[type] != null;
}
public NetworkAgentInfo getNetworkForType(int type) {
synchronized (mTypeLists) {
if (isTypeSupported(type) && !mTypeLists[type].isEmpty()) {
return mTypeLists[type].get(0);
}
}
return null;
}
public int getRestoreTimerForType(int type) {
synchronized (mTypeLists) {
if (mRestoreTimers == null) {
mRestoreTimers = loadRestoreTimers();
}
return mRestoreTimers.getOrDefault(type, -1);
}
}
private ArrayMap<Integer, Integer> loadRestoreTimers() {
final String[] configs = mService.mResources.get().getStringArray(
R.array.config_legacy_networktype_restore_timers);
final ArrayMap<Integer, Integer> ret = new ArrayMap<>(configs.length);
for (final String config : configs) {
final String[] splits = TextUtils.split(config, ",");
if (splits.length != 2) {
logwtf("Invalid restore timer token count: " + config);
continue;
}
try {
ret.put(Integer.parseInt(splits[0]), Integer.parseInt(splits[1]));
} catch (NumberFormatException e) {
logwtf("Invalid restore timer number format: " + config, e);
}
}
return ret;
}
private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type,
boolean isDefaultNetwork) {
if (DBG) {
log("Sending " + state
+ " broadcast for type " + type + " " + nai.toShortString()
+ " isDefaultNetwork=" + isDefaultNetwork);
}
}
// When a lockdown VPN connects, send another CONNECTED broadcast for the underlying
// network type, to preserve previous behaviour.
private void maybeSendLegacyLockdownBroadcast(@NonNull NetworkAgentInfo vpnNai) {
if (vpnNai != mService.getLegacyLockdownNai()) return;
if (vpnNai.declaredUnderlyingNetworks == null
|| vpnNai.declaredUnderlyingNetworks.length != 1) {
Log.wtf(TAG, "Legacy lockdown VPN must have exactly one underlying network: "
+ Arrays.toString(vpnNai.declaredUnderlyingNetworks));
return;
}
final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork(
vpnNai.declaredUnderlyingNetworks[0]);
if (underlyingNai == null) return;
final int type = underlyingNai.networkInfo.getType();
final DetailedState state = DetailedState.CONNECTED;
maybeLogBroadcast(underlyingNai, state, type, true /* isDefaultNetwork */);
mService.sendLegacyNetworkBroadcast(underlyingNai, state, type);
}
/** Adds the given network to the specified legacy type list. */
public void add(int type, NetworkAgentInfo nai) {
if (!isTypeSupported(type)) {
return; // Invalid network type.
}
if (VDBG) log("Adding agent " + nai + " for legacy network type " + type);
ArrayList<NetworkAgentInfo> list = mTypeLists[type];
if (list.contains(nai)) {
return;
}
synchronized (mTypeLists) {
list.add(nai);
}
// Send a broadcast if this is the first network of its type or if it's the default.
final boolean isDefaultNetwork = mService.isDefaultNetwork(nai);
// If a legacy lockdown VPN is active, override the NetworkInfo state in all broadcasts
// to preserve previous behaviour.
final DetailedState state = mService.getLegacyLockdownState(DetailedState.CONNECTED);
if ((list.size() == 1) || isDefaultNetwork) {
maybeLogBroadcast(nai, state, type, isDefaultNetwork);
mService.sendLegacyNetworkBroadcast(nai, state, type);
}
if (type == TYPE_VPN && state == DetailedState.CONNECTED) {
maybeSendLegacyLockdownBroadcast(nai);
}
}
/** Removes the given network from the specified legacy type list. */
public void remove(int type, NetworkAgentInfo nai, boolean wasDefault) {
ArrayList<NetworkAgentInfo> list = mTypeLists[type];
if (list == null || list.isEmpty()) {
return;
}
final boolean wasFirstNetwork = list.get(0).equals(nai);
synchronized (mTypeLists) {
if (!list.remove(nai)) {
return;
}
}
if (wasFirstNetwork || wasDefault) {
maybeLogBroadcast(nai, DetailedState.DISCONNECTED, type, wasDefault);
mService.sendLegacyNetworkBroadcast(nai, DetailedState.DISCONNECTED, type);
}
if (!list.isEmpty() && wasFirstNetwork) {
if (DBG) log("Other network available for type " + type +
", sending connected broadcast");
final NetworkAgentInfo replacement = list.get(0);
maybeLogBroadcast(replacement, DetailedState.CONNECTED, type,
mService.isDefaultNetwork(replacement));
mService.sendLegacyNetworkBroadcast(replacement, DetailedState.CONNECTED, type);
}
}
/** Removes the given network from all legacy type lists. */
public void remove(NetworkAgentInfo nai, boolean wasDefault) {
if (VDBG) log("Removing agent " + nai + " wasDefault=" + wasDefault);
for (int type = 0; type < mTypeLists.length; type++) {
remove(type, nai, wasDefault);
}
}
// send out another legacy broadcast - currently only used for suspend/unsuspend
// toggle
public void update(NetworkAgentInfo nai) {
final boolean isDefault = mService.isDefaultNetwork(nai);
final DetailedState state = nai.networkInfo.getDetailedState();
for (int type = 0; type < mTypeLists.length; type++) {
final ArrayList<NetworkAgentInfo> list = mTypeLists[type];
final boolean contains = (list != null && list.contains(nai));
final boolean isFirst = contains && (nai == list.get(0));
if (isFirst || contains && isDefault) {
maybeLogBroadcast(nai, state, type, isDefault);
mService.sendLegacyNetworkBroadcast(nai, state, type);
}
}
}
public void dump(IndentingPrintWriter pw) {
pw.println("mLegacyTypeTracker:");
pw.increaseIndent();
pw.print("Supported types:");
for (int type = 0; type < mTypeLists.length; type++) {
if (mTypeLists[type] != null) pw.print(" " + type);
}
pw.println();
pw.println("Current state:");
pw.increaseIndent();
synchronized (mTypeLists) {
for (int type = 0; type < mTypeLists.length; type++) {
if (mTypeLists[type] == null || mTypeLists[type].isEmpty()) continue;
for (NetworkAgentInfo nai : mTypeLists[type]) {
pw.println(type + " " + nai.toShortString());
}
}
}
pw.decreaseIndent();
pw.decreaseIndent();
pw.println();
}
}
private final LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(this);
final LocalPriorityDump mPriorityDumper = new LocalPriorityDump();
/**
* Helper class which parses out priority arguments and dumps sections according to their
* priority. If priority arguments are omitted, function calls the legacy dump command.
*/
private class LocalPriorityDump {
private static final String PRIORITY_ARG = "--dump-priority";
private static final String PRIORITY_ARG_HIGH = "HIGH";
private static final String PRIORITY_ARG_NORMAL = "NORMAL";
LocalPriorityDump() {}
private void dumpHigh(FileDescriptor fd, PrintWriter pw) {
doDump(fd, pw, new String[] {DIAG_ARG});
doDump(fd, pw, new String[] {SHORT_ARG});
}
private void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
doDump(fd, pw, args);
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (args == null) {
dumpNormal(fd, pw, args);
return;
}
String priority = null;
for (int argIndex = 0; argIndex < args.length; argIndex++) {
if (args[argIndex].equals(PRIORITY_ARG) && argIndex + 1 < args.length) {
argIndex++;
priority = args[argIndex];
}
}
if (PRIORITY_ARG_HIGH.equals(priority)) {
dumpHigh(fd, pw);
} else if (PRIORITY_ARG_NORMAL.equals(priority)) {
dumpNormal(fd, pw, args);
} else {
// ConnectivityService publishes binder service using publishBinderService() with
// no priority assigned will be treated as NORMAL priority. Dumpsys does not send
// "--dump-priority" arguments to the service. Thus, dump NORMAL only to align the
// legacy output for dumpsys connectivity.
// TODO: Integrate into signal dump.
dumpNormal(fd, pw, args);
}
}
}
/**
* Keeps track of the number of requests made under different uids.
*/
public static class PerUidCounter {
private final int mMaxCountPerUid;
// Map from UID to number of NetworkRequests that UID has filed.
@VisibleForTesting
@GuardedBy("mUidToNetworkRequestCount")
final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
/**
* Constructor
*
* @param maxCountPerUid the maximum count per uid allowed
*/
public PerUidCounter(final int maxCountPerUid) {
mMaxCountPerUid = maxCountPerUid;
}
/**
* Increments the request count of the given uid. Throws an exception if the number
* of open requests for the uid exceeds the value of maxCounterPerUid which is the value
* passed into the constructor. see: {@link #PerUidCounter(int)}.
*
* @throws ServiceSpecificException with
* {@link ConnectivityManager.Errors.TOO_MANY_REQUESTS} if the number of requests for
* the uid exceed the allowed number.
*
* @param uid the uid that the request was made under
*/
public void incrementCountOrThrow(final int uid) {
synchronized (mUidToNetworkRequestCount) {
incrementCountOrThrow(uid, 1 /* numToIncrement */);
}
}
private void incrementCountOrThrow(final int uid, final int numToIncrement) {
final int newRequestCount =
mUidToNetworkRequestCount.get(uid, 0) + numToIncrement;
if (newRequestCount >= mMaxCountPerUid
// HACK : the system server is allowed to go over the request count limit
// when it is creating requests on behalf of another app (but not itself,
// so it can still detect its own request leaks). This only happens in the
// per-app API flows in which case the old requests for that particular
// UID will be removed soon.
// TODO : instead of this hack, addPerAppDefaultNetworkRequests and other
// users of transact() should unregister the requests to decrease the count
// before they increase it again by creating a new NRI. Then remove the
// transact() method.
&& (Process.myUid() == uid || Process.myUid() != Binder.getCallingUid())) {
throw new ServiceSpecificException(
ConnectivityManager.Errors.TOO_MANY_REQUESTS,
"Uid " + uid + " exceeded its allotted requests limit");
}
mUidToNetworkRequestCount.put(uid, newRequestCount);
}
/**
* Decrements the request count of the given uid.
*
* @param uid the uid that the request was made under
*/
public void decrementCount(final int uid) {
synchronized (mUidToNetworkRequestCount) {
decrementCount(uid, 1 /* numToDecrement */);
}
}
private void decrementCount(final int uid, final int numToDecrement) {
final int newRequestCount =
mUidToNetworkRequestCount.get(uid, 0) - numToDecrement;
if (newRequestCount < 0) {
logwtf("BUG: too small request count " + newRequestCount + " for UID " + uid);
} else if (newRequestCount == 0) {
mUidToNetworkRequestCount.delete(uid);
} else {
mUidToNetworkRequestCount.put(uid, newRequestCount);
}
}
}
/**
* Dependencies of ConnectivityService, for injection in tests.
*/
@VisibleForTesting
public static class Dependencies {
public int getCallingUid() {
return Binder.getCallingUid();
}
/**
* Get system properties to use in ConnectivityService.
*/
public MockableSystemProperties getSystemProperties() {
return new MockableSystemProperties();
}
/**
* Get the {@link ConnectivityResources} to use in ConnectivityService.
*/
public ConnectivityResources getResources(@NonNull Context ctx) {
return new ConnectivityResources(ctx);
}
/**
* Create a HandlerThread to use in ConnectivityService.
*/
public HandlerThread makeHandlerThread() {
return new HandlerThread("ConnectivityServiceThread");
}
/**
* Get a reference to the ModuleNetworkStackClient.
*/
public NetworkStackClientBase getNetworkStack() {
return ModuleNetworkStackClient.getInstance(null);
}
/**
* @see ProxyTracker
*/
public ProxyTracker makeProxyTracker(@NonNull Context context,
@NonNull Handler connServiceHandler) {
return new ProxyTracker(context, connServiceHandler, EVENT_PROXY_HAS_CHANGED);
}
/**
* @see NetIdManager
*/
public NetIdManager makeNetIdManager() {
return new NetIdManager();
}
/**
* @see NetworkUtils#queryUserAccess(int, int)
*/
public boolean queryUserAccess(int uid, Network network, ConnectivityService cs) {
return cs.queryUserAccess(uid, network);
}
/**
* Gets the UID that owns a socket connection. Needed because opening SOCK_DIAG sockets
* requires CAP_NET_ADMIN, which the unit tests do not have.
*/
public int getConnectionOwnerUid(int protocol, InetSocketAddress local,
InetSocketAddress remote) {
return InetDiagMessage.getConnectionOwnerUid(protocol, local, remote);
}
/**
* @see MultinetworkPolicyTracker
*/
public MultinetworkPolicyTracker makeMultinetworkPolicyTracker(
@NonNull Context c, @NonNull Handler h, @NonNull Runnable r) {
return new MultinetworkPolicyTracker(c, h, r);
}
/**
* @see BatteryStatsManager
*/
public void reportNetworkInterfaceForTransports(Context context, String iface,
int[] transportTypes) {
final BatteryStatsManager batteryStats =
context.getSystemService(BatteryStatsManager.class);
batteryStats.reportNetworkInterfaceForTransports(iface, transportTypes);
}
public boolean getCellular464XlatEnabled() {
return NetworkProperties.isCellular464XlatEnabled().orElse(true);
}
/**
* @see PendingIntent#intentFilterEquals
*/
public boolean intentFilterEquals(PendingIntent a, PendingIntent b) {
return a.intentFilterEquals(b);
}
/**
* @see LocationPermissionChecker
*/
public LocationPermissionChecker makeLocationPermissionChecker(Context context) {
return new LocationPermissionChecker(context);
}
/**
* @see CarrierPrivilegeAuthenticator
*/
public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
@NonNull final Context context, @NonNull final TelephonyManager tm) {
if (SdkLevel.isAtLeastT()) {
return new CarrierPrivilegeAuthenticator(context, tm);
} else {
return null;
}
}
/**
* @see DeviceConfigUtils#isFeatureEnabled
*/
public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
TETHERING_MODULE_NAME, defaultEnabled);
}
/**
* Get the BpfNetMaps implementation to use in ConnectivityService.
* @param netd
* @return BpfNetMaps implementation.
*/
public BpfNetMaps getBpfNetMaps(INetd netd) {
return new BpfNetMaps(netd);
}
/**
* Wraps {@link TcUtils#tcFilterAddDevIngressPolice}
*/
public void enableIngressRateLimit(String iface, long rateInBytesPerSecond) {
final InterfaceParams params = InterfaceParams.getByName(iface);
if (params == null) {
// the interface might have disappeared.
logw("Failed to get interface params for interface " + iface);
return;
}
try {
// converting rateInBytesPerSecond from long to int is safe here because the
// setting's range is limited to INT_MAX.
// TODO: add long/uint64 support to tcFilterAddDevIngressPolice.
Log.i(TAG,
"enableIngressRateLimit on " + iface + ": " + rateInBytesPerSecond + "B/s");
TcUtils.tcFilterAddDevIngressPolice(params.index, TC_PRIO_POLICE, (short) ETH_P_ALL,
(int) rateInBytesPerSecond, TC_POLICE_BPF_PROG_PATH);
} catch (IOException e) {
loge("TcUtils.tcFilterAddDevIngressPolice(ifaceIndex=" + params.index
+ ", PRIO_POLICE, ETH_P_ALL, rateInBytesPerSecond="
+ rateInBytesPerSecond + ", bpfProgPath=" + TC_POLICE_BPF_PROG_PATH
+ ") failure: ", e);
}
}
/**
* Wraps {@link TcUtils#tcFilterDelDev}
*/
public void disableIngressRateLimit(String iface) {
final InterfaceParams params = InterfaceParams.getByName(iface);
if (params == null) {
// the interface might have disappeared.
logw("Failed to get interface params for interface " + iface);
return;
}
try {
Log.i(TAG,
"disableIngressRateLimit on " + iface);
TcUtils.tcFilterDelDev(params.index, true, TC_PRIO_POLICE, (short) ETH_P_ALL);
} catch (IOException e) {
loge("TcUtils.tcFilterDelDev(ifaceIndex=" + params.index
+ ", ingress=true, PRIO_POLICE, ETH_P_ALL) failure: ", e);
}
}
}
public ConnectivityService(Context context) {
this(context, getDnsResolver(context), new IpConnectivityLog(),
INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
new Dependencies());
}
@VisibleForTesting
protected ConnectivityService(Context context, IDnsResolver dnsresolver,
IpConnectivityLog logger, INetd netd, Dependencies deps) {
if (DBG) log("ConnectivityService starting up");
mDeps = Objects.requireNonNull(deps, "missing Dependencies");
mFlags = new ConnectivityFlags();
mSystemProperties = mDeps.getSystemProperties();
mNetIdManager = mDeps.makeNetIdManager();
mContext = Objects.requireNonNull(context, "missing Context");
mResources = deps.getResources(mContext);
mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID);
mSystemNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID);
mMetricsLog = logger;
mNetworkRanker = new NetworkRanker();
final NetworkRequest defaultInternetRequest = createDefaultRequest();
mDefaultRequest = new NetworkRequestInfo(
Process.myUid(), defaultInternetRequest, null,
null /* binder */, NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
null /* attributionTags */);
mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
mDefaultNetworkRequests.add(mDefaultRequest);
mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
mDefaultMobileDataRequest = createDefaultInternetRequestForTransport(
NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
// The default WiFi request is a background request so that apps using WiFi are
// migrated to a better network (typically ethernet) when one comes up, instead
// of staying on WiFi forever.
mDefaultWifiRequest = createDefaultInternetRequestForTransport(
NetworkCapabilities.TRANSPORT_WIFI, NetworkRequest.Type.BACKGROUND_REQUEST);
mDefaultVehicleRequest = createAlwaysOnRequestForCapability(
NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL,
NetworkRequest.Type.BACKGROUND_REQUEST);
mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
// TODO: Consider making the timer customizable.
mNascentDelayMs = DEFAULT_NASCENT_DELAY_MS;
mCellularRadioTimesharingCapable =
mResources.get().getBoolean(R.bool.config_cellular_radio_timesharing_capable);
mHandlerThread = mDeps.makeHandlerThread();
mHandlerThread.start();
mHandler = new InternalHandler(mHandlerThread.getLooper());
mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
mConnectivityDiagnosticsHandler =
new ConnectivityDiagnosticsHandler(mHandlerThread.getLooper());
mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
mStatsManager = mContext.getSystemService(NetworkStatsManager.class);
mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
mDnsResolver = Objects.requireNonNull(dnsresolver, "missing IDnsResolver");
mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
mNetd = netd;
mBpfNetMaps = mDeps.getBpfNetMaps(netd);
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
mCarrierPrivilegeAuthenticator =
mDeps.makeCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
// To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
// reading existing policy from disk.
mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);
final PowerManager powerManager = (PowerManager) context.getSystemService(
Context.POWER_SERVICE);
mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mLegacyTypeTracker.loadSupportedTypes(mContext, mTelephonyManager);
mProtectedNetworks = new ArrayList<>();
int[] protectedNetworks = mResources.get().getIntArray(R.array.config_protectedNetworks);
for (int p : protectedNetworks) {
if (mLegacyTypeTracker.isTypeSupported(p) && !mProtectedNetworks.contains(p)) {
mProtectedNetworks.add(p);
} else {
if (DBG) loge("Ignoring protectedNetwork " + p);
}
}
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mPermissionMonitor = new PermissionMonitor(mContext, mNetd, mBpfNetMaps);
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
// Listen for user add/removes to inform PermissionMonitor.
// Should run on mHandler to avoid any races.
final IntentFilter userIntentFilter = new IntentFilter();
userIntentFilter.addAction(Intent.ACTION_USER_ADDED);
userIntentFilter.addAction(Intent.ACTION_USER_REMOVED);
mUserAllContext.registerReceiver(mUserIntentReceiver, userIntentFilter,
null /* broadcastPermission */, mHandler);
// Listen to package add/removes for netd
final IntentFilter packageIntentFilter = new IntentFilter();
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
packageIntentFilter.addDataScheme("package");
mUserAllContext.registerReceiver(mPackageIntentReceiver, packageIntentFilter,
null /* broadcastPermission */, mHandler);
mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mHandler, mNetd);
mNetdCallback = new NetdCallback();
try {
mNetd.registerUnsolicitedEventListener(mNetdCallback);
} catch (RemoteException | ServiceSpecificException e) {
loge("Error registering event listener :" + e);
}
mSettingsObserver = new SettingsObserver(mContext, mHandler);
registerSettingsCallbacks();
mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager);
mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter);
final int dailyLimit = Settings.Global.getInt(mContext.getContentResolver(),
ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
LingerMonitor.DEFAULT_NOTIFICATION_DAILY_LIMIT);
final long rateLimit = Settings.Global.getLong(mContext.getContentResolver(),
ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS);
mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
mMultinetworkPolicyTracker = mDeps.makeMultinetworkPolicyTracker(
mContext, mHandler, () -> updateAvoidBadWifi());
mMultinetworkPolicyTracker.start();
mDnsManager = new DnsManager(mContext, mDnsResolver);
registerPrivateDnsSettingsCallbacks();
// This NAI is a sentinel used to offer no service to apps that are on a multi-layer
// request that doesn't allow fallback to the default network. It should never be visible
// to apps. As such, it's not in the list of NAIs and doesn't need many of the normal
// arguments like the handler or the DnsResolver.
// TODO : remove this ; it is probably better handled with a sentinel request.
mNoServiceNetwork = new NetworkAgentInfo(null,
new Network(INetd.UNREACHABLE_NET_ID),
new NetworkInfo(TYPE_NONE, 0, "", ""),
new LinkProperties(), new NetworkCapabilities(),
new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null,
new NetworkAgentConfig(), this, null, null, 0, INVALID_UID,
mLingerDelayMs, mQosCallbackTracker, mDeps);
try {
// DscpPolicyTracker cannot run on S because on S the tethering module can only load
// BPF programs/maps into /sys/fs/tethering/bpf, which the system server cannot access.
// Even if it could, running on S would at least require mocking out the BPF map,
// otherwise the unit tests will fail on pre-T devices where the seccomp filter blocks
// the bpf syscall. http://aosp/1907693
if (SdkLevel.isAtLeastT()) {
mDscpPolicyTracker = new DscpPolicyTracker();
}
} catch (ErrnoException e) {
loge("Unable to create DscpPolicyTracker");
}
mIngressRateLimit = ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
mContext);
}
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
return createDefaultNetworkCapabilitiesForUidRangeSet(Collections.singleton(
new UidRange(uid, uid)));
}
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRangeSet(
@NonNull final Set<UidRange> uidRangeSet) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
netCap.setUids(UidRange.toIntRanges(uidRangeSet));
return netCap;
}
private NetworkRequest createDefaultRequest() {
return createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.REQUEST);
}
private NetworkRequest createDefaultInternetRequestForTransport(
int transportType, NetworkRequest.Type type) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
if (transportType > TYPE_NONE) {
netCap.addTransportType(transportType);
}
return createNetworkRequest(type, netCap);
}
private NetworkRequest createNetworkRequest(
NetworkRequest.Type type, NetworkCapabilities netCap) {
return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type);
}
private NetworkRequest createAlwaysOnRequestForCapability(int capability,
NetworkRequest.Type type) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.clearAll();
netCap.addCapability(capability);
netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type);
}
// Used only for testing.
// TODO: Delete this and either:
// 1. Give FakeSettingsProvider the ability to send settings change notifications (requires
// changing ContentResolver to make registerContentObserver non-final).
// 2. Give FakeSettingsProvider an alternative notification mechanism and have the test use it
// by subclassing SettingsObserver.
@VisibleForTesting
void updateAlwaysOnNetworks() {
mHandler.sendEmptyMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
}
// See FakeSettingsProvider comment above.
@VisibleForTesting
void updatePrivateDnsSettings() {
mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
}
@VisibleForTesting
void updateMobileDataPreferredUids() {
mHandler.sendEmptyMessage(EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
}
@VisibleForTesting
void updateIngressRateLimit() {
mHandler.sendEmptyMessage(EVENT_INGRESS_RATE_LIMIT_CHANGED);
}
private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, int id) {
final boolean enable = mContext.getResources().getBoolean(id);
handleAlwaysOnNetworkRequest(networkRequest, enable);
}
private void handleAlwaysOnNetworkRequest(
NetworkRequest networkRequest, String settingName, boolean defaultValue) {
final boolean enable = toBool(Settings.Global.getInt(
mContext.getContentResolver(), settingName, encodeBool(defaultValue)));
handleAlwaysOnNetworkRequest(networkRequest, enable);
}
private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, boolean enable) {
final boolean isEnabled = (mNetworkRequests.get(networkRequest) != null);
if (enable == isEnabled) {
return; // Nothing to do.
}
if (enable) {
handleRegisterNetworkRequest(new NetworkRequestInfo(
Process.myUid(), networkRequest, null /* messenger */, null /* binder */,
NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
null /* attributionTags */));
} else {
handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
/* callOnUnavailable */ false);
}
}
private void handleConfigureAlwaysOnNetworks() {
handleAlwaysOnNetworkRequest(mDefaultMobileDataRequest,
ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON, true /* defaultValue */);
handleAlwaysOnNetworkRequest(mDefaultWifiRequest,
ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED, false /* defaultValue */);
final boolean vehicleAlwaysRequested = mResources.get().getBoolean(
R.bool.config_vehicleInternalNetworkAlwaysRequested);
handleAlwaysOnNetworkRequest(mDefaultVehicleRequest, vehicleAlwaysRequested);
}
// Note that registering observer for setting do not get initial callback when registering,
// callers must fetch the initial value of the setting themselves if needed.
private void registerSettingsCallbacks() {
// Watch for global HTTP proxy changes.
mSettingsObserver.observe(
Settings.Global.getUriFor(Settings.Global.HTTP_PROXY),
EVENT_APPLY_GLOBAL_HTTP_PROXY);
// Watch for whether or not to keep mobile data always on.
mSettingsObserver.observe(
Settings.Global.getUriFor(ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON),
EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
// Watch for whether or not to keep wifi always on.
mSettingsObserver.observe(
Settings.Global.getUriFor(ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED),
EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
// Watch for mobile data preferred uids changes.
mSettingsObserver.observe(
Settings.Secure.getUriFor(ConnectivitySettingsManager.MOBILE_DATA_PREFERRED_UIDS),
EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
// Watch for ingress rate limit changes.
mSettingsObserver.observe(
Settings.Global.getUriFor(
ConnectivitySettingsManager.INGRESS_RATE_LIMIT_BYTES_PER_SECOND),
EVENT_INGRESS_RATE_LIMIT_CHANGED);
}
private void registerPrivateDnsSettingsCallbacks() {
for (Uri uri : DnsManager.getPrivateDnsSettingsUris()) {
mSettingsObserver.observe(uri, EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
}
}
private synchronized int nextNetworkRequestId() {
// TODO: Consider handle wrapping and exclude {@link NetworkRequest#REQUEST_ID_NONE} if
// doing that.
return mNextNetworkRequestId++;
}
@VisibleForTesting
protected NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) {
if (network == null) {
return null;
}
return getNetworkAgentInfoForNetId(network.getNetId());
}
private NetworkAgentInfo getNetworkAgentInfoForNetId(int netId) {
synchronized (mNetworkForNetId) {
return mNetworkForNetId.get(netId);
}
}
// TODO: determine what to do when more than one VPN applies to |uid|.
private NetworkAgentInfo getVpnForUid(int uid) {
synchronized (mNetworkForNetId) {
for (int i = 0; i < mNetworkForNetId.size(); i++) {
final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
if (nai.isVPN() && nai.everConnected && nai.networkCapabilities.appliesToUid(uid)) {
return nai;
}
}
}
return null;
}
private Network[] getVpnUnderlyingNetworks(int uid) {
if (mLockdownEnabled) return null;
final NetworkAgentInfo nai = getVpnForUid(uid);
if (nai != null) return nai.declaredUnderlyingNetworks;
return null;
}
private NetworkAgentInfo getNetworkAgentInfoForUid(int uid) {
NetworkAgentInfo nai = getDefaultNetworkForUid(uid);
final Network[] networks = getVpnUnderlyingNetworks(uid);
if (networks != null) {
// getUnderlyingNetworks() returns:
// null => there was no VPN, or the VPN didn't specify anything, so we use the default.
// empty array => the VPN explicitly said "no default network".
// non-empty array => the VPN specified one or more default networks; we use the
// first one.
if (networks.length > 0) {
nai = getNetworkAgentInfoForNetwork(networks[0]);
} else {
nai = null;
}
}
return nai;
}
/**
* Check if UID should be blocked from using the specified network.
*/
private boolean isNetworkWithCapabilitiesBlocked(@Nullable final NetworkCapabilities nc,
final int uid, final boolean ignoreBlocked) {
// Networks aren't blocked when ignoring blocked status
if (ignoreBlocked) {
return false;
}
if (isUidBlockedByVpn(uid, mVpnBlockedUidRanges)) return true;
final long ident = Binder.clearCallingIdentity();
try {
final boolean metered = nc == null ? true : nc.isMetered();
return mPolicyManager.isUidNetworkingBlocked(uid, metered);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void maybeLogBlockedNetworkInfo(NetworkInfo ni, int uid) {
if (ni == null || !LOGD_BLOCKED_NETWORKINFO) {
return;
}
final boolean blocked;
synchronized (mBlockedAppUids) {
if (ni.getDetailedState() == DetailedState.BLOCKED && mBlockedAppUids.add(uid)) {
blocked = true;
} else if (ni.isConnected() && mBlockedAppUids.remove(uid)) {
blocked = false;
} else {
return;
}
}
String action = blocked ? "BLOCKED" : "UNBLOCKED";
log(String.format("Returning %s NetworkInfo to uid=%d", action, uid));
mNetworkInfoBlockingLogs.log(action + " " + uid);
}
private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net, int blocked) {
if (nri == null || net == null || !LOGD_BLOCKED_NETWORKINFO) {
return;
}
final String action = (blocked != 0) ? "BLOCKED" : "UNBLOCKED";
final int requestId = nri.getActiveRequest() != null
? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId;
mNetworkInfoBlockingLogs.log(String.format(
"%s %d(%d) on netId %d: %s", action, nri.mAsUid, requestId, net.getNetId(),
Integer.toHexString(blocked)));
}
/**
* Apply any relevant filters to the specified {@link NetworkInfo} for the given UID. For
* example, this may mark the network as {@link DetailedState#BLOCKED} based
* on {@link #isNetworkWithCapabilitiesBlocked}.
*/
@NonNull
private NetworkInfo filterNetworkInfo(@NonNull NetworkInfo networkInfo, int type,
@NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) {
final NetworkInfo filtered = new NetworkInfo(networkInfo);
// Many legacy types (e.g,. TYPE_MOBILE_HIPRI) are not actually a property of the network
// but only exists if an app asks about them or requests them. Ensure the requesting app
// gets the type it asks for.
filtered.setType(type);
if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
filtered.setDetailedState(DetailedState.BLOCKED, null /* reason */,
null /* extraInfo */);
}
filterForLegacyLockdown(filtered);
return filtered;
}
private NetworkInfo getFilteredNetworkInfo(NetworkAgentInfo nai, int uid,
boolean ignoreBlocked) {
return filterNetworkInfo(nai.networkInfo, nai.networkInfo.getType(),
nai.networkCapabilities, uid, ignoreBlocked);
}
/**
* Return NetworkInfo for the active (i.e., connected) network interface.
* It is assumed that at most one network is active at a time. If more
* than one is active, it is indeterminate which will be returned.
* @return the info for the active network, or {@code null} if none is
* active
*/
@Override
public NetworkInfo getActiveNetworkInfo() {
enforceAccessPermission();
final int uid = mDeps.getCallingUid();
final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
if (nai == null) return null;
final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false);
maybeLogBlockedNetworkInfo(networkInfo, uid);
return networkInfo;
}
@Override
public Network getActiveNetwork() {
enforceAccessPermission();
return getActiveNetworkForUidInternal(mDeps.getCallingUid(), false);
}
@Override
public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) {
PermissionUtils.enforceNetworkStackPermission(mContext);
return getActiveNetworkForUidInternal(uid, ignoreBlocked);
}
private Network getActiveNetworkForUidInternal(final int uid, boolean ignoreBlocked) {
final NetworkAgentInfo vpnNai = getVpnForUid(uid);
if (vpnNai != null) {
final NetworkCapabilities requiredCaps = createDefaultNetworkCapabilitiesForUid(uid);
if (requiredCaps.satisfiedByNetworkCapabilities(vpnNai.networkCapabilities)) {
return vpnNai.network;
}
}
NetworkAgentInfo nai = getDefaultNetworkForUid(uid);
if (nai == null || isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid,
ignoreBlocked)) {
return null;
}
return nai.network;
}
@Override
public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
PermissionUtils.enforceNetworkStackPermission(mContext);
final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
if (nai == null) return null;
return getFilteredNetworkInfo(nai, uid, ignoreBlocked);
}
/** Returns a NetworkInfo object for a network that doesn't exist. */
private NetworkInfo makeFakeNetworkInfo(int networkType, int uid) {
final NetworkInfo info = new NetworkInfo(networkType, 0 /* subtype */,
getNetworkTypeName(networkType), "" /* subtypeName */);
info.setIsAvailable(true);
// For compatibility with legacy code, return BLOCKED instead of DISCONNECTED when
// background data is restricted.
final NetworkCapabilities nc = new NetworkCapabilities(); // Metered.
final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, false)
? DetailedState.BLOCKED
: DetailedState.DISCONNECTED;
info.setDetailedState(state, null /* reason */, null /* extraInfo */);
filterForLegacyLockdown(info);
return info;
}
private NetworkInfo getFilteredNetworkInfoForType(int networkType, int uid) {
if (!mLegacyTypeTracker.isTypeSupported(networkType)) {
return null;
}
final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
if (nai == null) {
return makeFakeNetworkInfo(networkType, uid);
}
return filterNetworkInfo(nai.networkInfo, networkType, nai.networkCapabilities, uid,
false);
}
@Override
public NetworkInfo getNetworkInfo(int networkType) {
enforceAccessPermission();
final int uid = mDeps.getCallingUid();
if (getVpnUnderlyingNetworks(uid) != null) {
// A VPN is active, so we may need to return one of its underlying networks. This
// information is not available in LegacyTypeTracker, so we have to get it from
// getNetworkAgentInfoForUid.
final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
if (nai == null) return null;
final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false);
if (networkInfo.getType() == networkType) {
return networkInfo;
}
}
return getFilteredNetworkInfoForType(networkType, uid);
}
@Override
public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) {
enforceAccessPermission();
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return null;
return getFilteredNetworkInfo(nai, uid, ignoreBlocked);
}
@Override
public NetworkInfo[] getAllNetworkInfo() {
enforceAccessPermission();
final ArrayList<NetworkInfo> result = new ArrayList<>();
for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
networkType++) {
NetworkInfo info = getNetworkInfo(networkType);
if (info != null) {
result.add(info);
}
}
return result.toArray(new NetworkInfo[result.size()]);
}
@Override
public Network getNetworkForType(int networkType) {
enforceAccessPermission();
if (!mLegacyTypeTracker.isTypeSupported(networkType)) {
return null;
}
final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
if (nai == null) {
return null;
}
final int uid = mDeps.getCallingUid();
if (isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) {
return null;
}
return nai.network;
}
@Override
public Network[] getAllNetworks() {
enforceAccessPermission();
synchronized (mNetworkForNetId) {
final Network[] result = new Network[mNetworkForNetId.size()];
for (int i = 0; i < mNetworkForNetId.size(); i++) {
result[i] = mNetworkForNetId.valueAt(i).network;
}
return result;
}
}
@Override
public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(
int userId, String callingPackageName, @Nullable String callingAttributionTag) {
// The basic principle is: if an app's traffic could possibly go over a
// network, without the app doing anything multinetwork-specific,
// (hence, by "default"), then include that network's capabilities in
// the array.
//
// In the normal case, app traffic only goes over the system's default
// network connection, so that's the only network returned.
//
// With a VPN in force, some app traffic may go into the VPN, and thus
// over whatever underlying networks the VPN specifies, while other app
// traffic may go over the system default network (e.g.: a split-tunnel
// VPN, or an app disallowed by the VPN), so the set of networks
// returned includes the VPN's underlying networks and the system
// default.
enforceAccessPermission();
HashMap<Network, NetworkCapabilities> result = new HashMap<>();
for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
if (!nri.isBeingSatisfied()) {
continue;
}
final NetworkAgentInfo nai = nri.getSatisfier();
final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
if (null != nc
&& nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
&& !result.containsKey(nai.network)) {
result.put(
nai.network,
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
nc, false /* includeLocationSensitiveInfo */,
getCallingPid(), mDeps.getCallingUid(), callingPackageName,
callingAttributionTag));
}
}
// No need to check mLockdownEnabled. If it's true, getVpnUnderlyingNetworks returns null.
final Network[] networks = getVpnUnderlyingNetworks(mDeps.getCallingUid());
if (null != networks) {
for (final Network network : networks) {
final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
if (null != nc) {
result.put(
network,
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
nc,
false /* includeLocationSensitiveInfo */,
getCallingPid(), mDeps.getCallingUid(), callingPackageName,
callingAttributionTag));
}
}
}
NetworkCapabilities[] out = new NetworkCapabilities[result.size()];
out = result.values().toArray(out);
return out;
}
@Override
public boolean isNetworkSupported(int networkType) {
enforceAccessPermission();
return mLegacyTypeTracker.isTypeSupported(networkType);
}
/**
* Return LinkProperties for the active (i.e., connected) default
* network interface for the calling uid.
* @return the ip properties for the active network, or {@code null} if
* none is active
*/
@Override
public LinkProperties getActiveLinkProperties() {
enforceAccessPermission();
final int uid = mDeps.getCallingUid();
NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
if (nai == null) return null;
return linkPropertiesRestrictedForCallerPermissions(nai.linkProperties,
Binder.getCallingPid(), uid);
}
@Override
public LinkProperties getLinkPropertiesForType(int networkType) {
enforceAccessPermission();
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
final LinkProperties lp = getLinkProperties(nai);
if (lp == null) return null;
return linkPropertiesRestrictedForCallerPermissions(
lp, Binder.getCallingPid(), mDeps.getCallingUid());
}
// TODO - this should be ALL networks
@Override
public LinkProperties getLinkProperties(Network network) {
enforceAccessPermission();
final LinkProperties lp = getLinkProperties(getNetworkAgentInfoForNetwork(network));
if (lp == null) return null;
return linkPropertiesRestrictedForCallerPermissions(
lp, Binder.getCallingPid(), mDeps.getCallingUid());
}
@Nullable
private LinkProperties getLinkProperties(@Nullable NetworkAgentInfo nai) {
if (nai == null) {
return null;
}
synchronized (nai) {
return nai.linkProperties;
}
}
@Override
@Nullable
public LinkProperties redactLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid,
@NonNull String packageName, @Nullable String callingAttributionTag) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(lp);
enforceNetworkStackOrSettingsPermission();
if (!checkAccessPermission(-1 /* pid */, uid)) {
return null;
}
return linkPropertiesRestrictedForCallerPermissions(lp, -1 /* callerPid */, uid);
}
private NetworkCapabilities getNetworkCapabilitiesInternal(Network network) {
return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
}
private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
if (nai == null) return null;
synchronized (nai) {
return networkCapabilitiesRestrictedForCallerPermissions(
nai.networkCapabilities, Binder.getCallingPid(), mDeps.getCallingUid());
}
}
@Override
public NetworkCapabilities getNetworkCapabilities(Network network, String callingPackageName,
@Nullable String callingAttributionTag) {
mAppOpsManager.checkPackage(mDeps.getCallingUid(), callingPackageName);
enforceAccessPermission();
return createWithLocationInfoSanitizedIfNecessaryWhenParceled(
getNetworkCapabilitiesInternal(network),
false /* includeLocationSensitiveInfo */,
getCallingPid(), mDeps.getCallingUid(), callingPackageName, callingAttributionTag);
}
@Override
public NetworkCapabilities redactNetworkCapabilitiesForPackage(@NonNull NetworkCapabilities nc,
int uid, @NonNull String packageName, @Nullable String callingAttributionTag) {
Objects.requireNonNull(nc);
Objects.requireNonNull(packageName);
enforceNetworkStackOrSettingsPermission();
if (!checkAccessPermission(-1 /* pid */, uid)) {
return null;
}
return createWithLocationInfoSanitizedIfNecessaryWhenParceled(
networkCapabilitiesRestrictedForCallerPermissions(nc, -1 /* callerPid */, uid),
true /* includeLocationSensitiveInfo */, -1 /* callingPid */, uid, packageName,
callingAttributionTag);
}
@VisibleForTesting
NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions(
NetworkCapabilities nc, int callerPid, int callerUid) {
// Note : here it would be nice to check ACCESS_NETWORK_STATE and return null, but
// this would be expensive (one more permission check every time any NC callback is
// sent) and possibly dangerous : apps normally can't lose ACCESS_NETWORK_STATE, if
// it happens for some reason (e.g. the package is uninstalled while CS is trying to
// send the callback) it would crash the system server with NPE.
final NetworkCapabilities newNc = new NetworkCapabilities(nc);
if (!checkSettingsPermission(callerPid, callerUid)) {
newNc.setUids(null);
newNc.setSSID(null);
// TODO: Processes holding NETWORK_FACTORY should be able to see the underlying networks
newNc.setUnderlyingNetworks(null);
}
if (newNc.getNetworkSpecifier() != null) {
newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact());
}
newNc.setAdministratorUids(new int[0]);
if (!checkAnyPermissionOf(
callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
newNc.setAccessUids(new ArraySet<>());
newNc.setSubscriptionIds(Collections.emptySet());
}
return newNc;
}
/**
* Wrapper used to cache the permission check results performed for the corresponding
* app. This avoids performing multiple permission checks for different fields in
* NetworkCapabilities.
* Note: This wrapper does not support any sort of invalidation and thus must not be
* persistent or long-lived. It may only be used for the time necessary to
* compute the redactions required by one particular NetworkCallback or
* synchronous call.
*/
private class RedactionPermissionChecker {
private final int mCallingPid;
private final int mCallingUid;
@NonNull private final String mCallingPackageName;
@Nullable private final String mCallingAttributionTag;
private Boolean mHasLocationPermission = null;
private Boolean mHasLocalMacAddressPermission = null;
private Boolean mHasSettingsPermission = null;
RedactionPermissionChecker(int callingPid, int callingUid,
@NonNull String callingPackageName, @Nullable String callingAttributionTag) {
mCallingPid = callingPid;
mCallingUid = callingUid;
mCallingPackageName = callingPackageName;
mCallingAttributionTag = callingAttributionTag;
}
private boolean hasLocationPermissionInternal() {
final long token = Binder.clearCallingIdentity();
try {
return mLocationPermissionChecker.checkLocationPermission(
mCallingPackageName, mCallingAttributionTag, mCallingUid,
null /* message */);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Returns whether the app holds location permission or not (might return cached result
* if the permission was already checked before).
*/
public boolean hasLocationPermission() {
if (mHasLocationPermission == null) {
// If there is no cached result, perform the check now.
mHasLocationPermission = hasLocationPermissionInternal();
}
return mHasLocationPermission;
}
/**
* Returns whether the app holds local mac address permission or not (might return cached
* result if the permission was already checked before).
*/
public boolean hasLocalMacAddressPermission() {
if (mHasLocalMacAddressPermission == null) {
// If there is no cached result, perform the check now.
mHasLocalMacAddressPermission =
checkLocalMacAddressPermission(mCallingPid, mCallingUid);
}
return mHasLocalMacAddressPermission;
}
/**
* Returns whether the app holds settings permission or not (might return cached
* result if the permission was already checked before).
*/
public boolean hasSettingsPermission() {
if (mHasSettingsPermission == null) {
// If there is no cached result, perform the check now.
mHasSettingsPermission = checkSettingsPermission(mCallingPid, mCallingUid);
}
return mHasSettingsPermission;
}
}
private static boolean shouldRedact(@NetworkCapabilities.RedactionType long redactions,
@NetworkCapabilities.NetCapability long redaction) {
return (redactions & redaction) != 0;
}
/**
* Use the provided |applicableRedactions| to check the receiving app's
* permissions and clear/set the corresponding bit in the returned bitmask. The bitmask
* returned will be used to ensure the necessary redactions are performed by NetworkCapabilities
* before being sent to the corresponding app.
*/
private @NetworkCapabilities.RedactionType long retrieveRequiredRedactions(
@NetworkCapabilities.RedactionType long applicableRedactions,
@NonNull RedactionPermissionChecker redactionPermissionChecker,
boolean includeLocationSensitiveInfo) {
long redactions = applicableRedactions;
if (shouldRedact(redactions, REDACT_FOR_ACCESS_FINE_LOCATION)) {
if (includeLocationSensitiveInfo
&& redactionPermissionChecker.hasLocationPermission()) {
redactions &= ~REDACT_FOR_ACCESS_FINE_LOCATION;
}
}
if (shouldRedact(redactions, REDACT_FOR_LOCAL_MAC_ADDRESS)) {
if (redactionPermissionChecker.hasLocalMacAddressPermission()) {
redactions &= ~REDACT_FOR_LOCAL_MAC_ADDRESS;
}
}
if (shouldRedact(redactions, REDACT_FOR_NETWORK_SETTINGS)) {
if (redactionPermissionChecker.hasSettingsPermission()) {
redactions &= ~REDACT_FOR_NETWORK_SETTINGS;
}
}
return redactions;
}
@VisibleForTesting
@Nullable
NetworkCapabilities createWithLocationInfoSanitizedIfNecessaryWhenParceled(
@Nullable NetworkCapabilities nc, boolean includeLocationSensitiveInfo,
int callingPid, int callingUid, @NonNull String callingPkgName,
@Nullable String callingAttributionTag) {
if (nc == null) {
return null;
}
// Avoid doing location permission check if the transport info has no location sensitive
// data.
final RedactionPermissionChecker redactionPermissionChecker =
new RedactionPermissionChecker(callingPid, callingUid, callingPkgName,
callingAttributionTag);
final long redactions = retrieveRequiredRedactions(
nc.getApplicableRedactions(), redactionPermissionChecker,
includeLocationSensitiveInfo);
final NetworkCapabilities newNc = new NetworkCapabilities(nc, redactions);
// Reset owner uid if not destined for the owner app.
// TODO : calling UID is redacted because apps should generally not know what UID is
// bringing up the VPN, but this should not apply to some very privileged apps like settings
if (callingUid != nc.getOwnerUid()) {
newNc.setOwnerUid(INVALID_UID);
return newNc;
}
// Allow VPNs to see ownership of their own VPN networks - not location sensitive.
if (nc.hasTransport(TRANSPORT_VPN)) {
// Owner UIDs already checked above. No need to re-check.
return newNc;
}
// If the calling does not want location sensitive data & target SDK >= S, then mask info.
// Else include the owner UID iff the calling has location permission to provide backwards
// compatibility for older apps.
if (!includeLocationSensitiveInfo
&& isTargetSdkAtleast(
Build.VERSION_CODES.S, callingUid, callingPkgName)) {
newNc.setOwnerUid(INVALID_UID);
return newNc;
}
// Reset owner uid if the app has no location permission.
if (!redactionPermissionChecker.hasLocationPermission()) {
newNc.setOwnerUid(INVALID_UID);
}
return newNc;
}
@NonNull
private LinkProperties linkPropertiesRestrictedForCallerPermissions(
LinkProperties lp, int callerPid, int callerUid) {
if (lp == null) return new LinkProperties();
// Note : here it would be nice to check ACCESS_NETWORK_STATE and return null, but
// this would be expensive (one more permission check every time any LP callback is
// sent) and possibly dangerous : apps normally can't lose ACCESS_NETWORK_STATE, if
// it happens for some reason (e.g. the package is uninstalled while CS is trying to
// send the callback) it would crash the system server with NPE.
// Only do a permission check if sanitization is needed, to avoid unnecessary binder calls.
final boolean needsSanitization =
(lp.getCaptivePortalApiUrl() != null || lp.getCaptivePortalData() != null);
if (!needsSanitization) {
return new LinkProperties(lp);
}
if (checkSettingsPermission(callerPid, callerUid)) {
return new LinkProperties(lp, true /* parcelSensitiveFields */);
}
final LinkProperties newLp = new LinkProperties(lp);
// Sensitive fields would not be parceled anyway, but sanitize for consistency before the
// object gets parceled.
newLp.setCaptivePortalApiUrl(null);
newLp.setCaptivePortalData(null);
return newLp;
}
private void restrictRequestUidsForCallerAndSetRequestorInfo(NetworkCapabilities nc,
int callerUid, String callerPackageName) {
// There is no need to track the effective UID of the request here. If the caller
// lacks the settings permission, the effective UID is the same as the calling ID.
if (!checkSettingsPermission()) {
// Unprivileged apps can only pass in null or their own UID.
if (nc.getUids() == null) {
// If the caller passes in null, the callback will also match networks that do not
// apply to its UID, similarly to what it would see if it called getAllNetworks.
// In this case, redact everything in the request immediately. This ensures that the
// app is not able to get any redacted information by filing an unredacted request
// and observing whether the request matches something.
if (nc.getNetworkSpecifier() != null) {
nc.setNetworkSpecifier(nc.getNetworkSpecifier().redact());
}
} else {
nc.setSingleUid(callerUid);
}
}
nc.setRequestorUidAndPackageName(callerUid, callerPackageName);
nc.setAdministratorUids(new int[0]);
// Clear owner UID; this can never come from an app.
nc.setOwnerUid(INVALID_UID);
}
private void restrictBackgroundRequestForCaller(NetworkCapabilities nc) {
if (!mPermissionMonitor.hasUseBackgroundNetworksPermission(mDeps.getCallingUid())) {
nc.addCapability(NET_CAPABILITY_FOREGROUND);
}
}
@Override
public @RestrictBackgroundStatus int getRestrictBackgroundStatusByCaller() {
enforceAccessPermission();
final int callerUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
return mPolicyManager.getRestrictBackgroundStatus(callerUid);
} finally {
Binder.restoreCallingIdentity(token);
}
}
// TODO: Consider delete this function or turn it into a no-op method.
@Override
public NetworkState[] getAllNetworkState() {
// This contains IMSI details, so make sure the caller is privileged.
PermissionUtils.enforceNetworkStackPermission(mContext);
final ArrayList<NetworkState> result = new ArrayList<>();
for (NetworkStateSnapshot snapshot : getAllNetworkStateSnapshots()) {
// NetworkStateSnapshot doesn't contain NetworkInfo, so need to fetch it from the
// NetworkAgentInfo.
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(snapshot.getNetwork());
if (nai != null && nai.networkInfo.isConnected()) {
result.add(new NetworkState(new NetworkInfo(nai.networkInfo),
snapshot.getLinkProperties(), snapshot.getNetworkCapabilities(),
snapshot.getNetwork(), snapshot.getSubscriberId()));
}
}
return result.toArray(new NetworkState[result.size()]);
}
@Override
@NonNull
public List<NetworkStateSnapshot> getAllNetworkStateSnapshots() {
// This contains IMSI details, so make sure the caller is privileged.
enforceNetworkStackOrSettingsPermission();
final ArrayList<NetworkStateSnapshot> result = new ArrayList<>();
for (Network network : getAllNetworks()) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai != null && nai.everConnected) {
// TODO (b/73321673) : NetworkStateSnapshot contains a copy of the
// NetworkCapabilities, which may contain UIDs of apps to which the
// network applies. Should the UIDs be cleared so as not to leak or
// interfere ?
result.add(nai.getNetworkStateSnapshot());
}
}
return result;
}
@Override
public boolean isActiveNetworkMetered() {
enforceAccessPermission();
final NetworkCapabilities caps = getNetworkCapabilitiesInternal(getActiveNetwork());
if (caps != null) {
return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
} else {
// Always return the most conservative value
return true;
}
}
/**
* Ensures that the system cannot call a particular method.
*/
private boolean disallowedBecauseSystemCaller() {
// TODO: start throwing a SecurityException when GnssLocationProvider stops calling
// requestRouteToHost. In Q, GnssLocationProvider is changed to not call requestRouteToHost
// for devices launched with Q and above. However, existing devices upgrading to Q and
// above must continued to be supported for few more releases.
if (isSystem(mDeps.getCallingUid()) && SystemProperties.getInt(
"ro.product.first_api_level", 0) > Build.VERSION_CODES.P) {
log("This method exists only for app backwards compatibility"
+ " and must not be called by system services.");
return true;
}
return false;
}
private int getAppUid(final String app, final UserHandle user) {
final PackageManager pm =
mContext.createContextAsUser(user, 0 /* flags */).getPackageManager();
final long token = Binder.clearCallingIdentity();
try {
return pm.getPackageUid(app, 0 /* flags */);
} catch (PackageManager.NameNotFoundException e) {
return -1;
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void verifyCallingUidAndPackage(String packageName, int callingUid) {
final UserHandle user = UserHandle.getUserHandleForUid(callingUid);
if (getAppUid(packageName, user) != callingUid) {
throw new SecurityException(packageName + " does not belong to uid " + callingUid);
}
}
/**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface.
* @param networkType the type of the network over which traffic to the
* specified host is to be routed
* @param hostAddress the IP address of the host to which the route is
* desired
* @return {@code true} on success, {@code false} on failure
*/
@Override
public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress,
String callingPackageName, String callingAttributionTag) {
if (disallowedBecauseSystemCaller()) {
return false;
}
verifyCallingUidAndPackage(callingPackageName, mDeps.getCallingUid());
enforceChangePermission(callingPackageName, callingAttributionTag);
if (mProtectedNetworks.contains(networkType)) {
enforceConnectivityRestrictedNetworksPermission();
}
InetAddress addr;
try {
addr = InetAddress.getByAddress(hostAddress);
} catch (UnknownHostException e) {
if (DBG) log("requestRouteToHostAddress got " + e.toString());
return false;
}
if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType);
return false;
}
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
if (nai == null) {
if (mLegacyTypeTracker.isTypeSupported(networkType) == false) {
if (DBG) log("requestRouteToHostAddress on unsupported network: " + networkType);
} else {
if (DBG) log("requestRouteToHostAddress on down network: " + networkType);
}
return false;
}
DetailedState netState;
synchronized (nai) {
netState = nai.networkInfo.getDetailedState();
}
if (netState != DetailedState.CONNECTED && netState != DetailedState.CAPTIVE_PORTAL_CHECK) {
if (VDBG) {
log("requestRouteToHostAddress on down network "
+ "(" + networkType + ") - dropped"
+ " netState=" + netState);
}
return false;
}
final int uid = mDeps.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
LinkProperties lp;
int netId;
synchronized (nai) {
lp = nai.linkProperties;
netId = nai.network.getNetId();
}
boolean ok = addLegacyRouteToHost(lp, addr, netId, uid);
if (DBG) {
log("requestRouteToHostAddress " + addr + nai.toShortString() + " ok=" + ok);
}
return ok;
} finally {
Binder.restoreCallingIdentity(token);
}
}
private boolean addLegacyRouteToHost(LinkProperties lp, InetAddress addr, int netId, int uid) {
RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
if (bestRoute == null) {
bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
} else {
String iface = bestRoute.getInterface();
if (bestRoute.getGateway().equals(addr)) {
// if there is no better route, add the implied hostroute for our gateway
bestRoute = RouteInfo.makeHostRoute(addr, iface);
} else {
// if we will connect to this through another route, add a direct route
// to it's gateway
bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
}
}
if (DBG) log("Adding legacy route " + bestRoute +
" for UID/PID " + uid + "/" + Binder.getCallingPid());
final String dst = bestRoute.getDestinationLinkAddress().toString();
final String nextHop = bestRoute.hasGateway()
? bestRoute.getGateway().getHostAddress() : "";
try {
mNetd.networkAddLegacyRoute(netId, bestRoute.getInterface(), dst, nextHop , uid);
} catch (RemoteException | ServiceSpecificException e) {
if (DBG) loge("Exception trying to add a route: " + e);
return false;
}
return true;
}
class DnsResolverUnsolicitedEventCallback extends
IDnsResolverUnsolicitedEventListener.Stub {
@Override
public void onPrivateDnsValidationEvent(final PrivateDnsValidationEventParcel event) {
try {
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_PRIVATE_DNS_VALIDATION_UPDATE,
new PrivateDnsValidationUpdate(event.netId,
InetAddresses.parseNumericAddress(event.ipAddress),
event.hostname, event.validation)));
} catch (IllegalArgumentException e) {
loge("Error parsing ip address in validation event");
}
}
@Override
public void onDnsHealthEvent(final DnsHealthEventParcel event) {
NetworkAgentInfo nai = getNetworkAgentInfoForNetId(event.netId);
// Netd event only allow registrants from system. Each NetworkMonitor thread is under
// the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd
// event callback for certain nai. e.g. cellular. Register here to pass to
// NetworkMonitor instead.
// TODO: Move the Dns Event to NetworkMonitor. NetdEventListenerService only allow one
// callback from each caller type. Need to re-factor NetdEventListenerService to allow
// multiple NetworkMonitor registrants.
if (nai != null && nai.satisfies(mDefaultRequest.mRequests.get(0))) {
nai.networkMonitor().notifyDnsResponse(event.healthResult);
}
}
@Override
public void onNat64PrefixEvent(final Nat64PrefixEventParcel event) {
mHandler.post(() -> handleNat64PrefixEvent(event.netId, event.prefixOperation,
event.prefixAddress, event.prefixLength));
}
@Override
public int getInterfaceVersion() {
return this.VERSION;
}
@Override
public String getInterfaceHash() {
return this.HASH;
}
}
@VisibleForTesting
protected final DnsResolverUnsolicitedEventCallback mResolverUnsolEventCallback =
new DnsResolverUnsolicitedEventCallback();
private void registerDnsResolverUnsolicitedEventListener() {
try {
mDnsResolver.registerUnsolicitedEventListener(mResolverUnsolEventCallback);
} catch (Exception e) {
loge("Error registering DnsResolver unsolicited event callback: " + e);
}
}
private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {
@Override
public void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_BLOCKED_REASON_CHANGED,
uid, blockedReasons));
}
};
private void handleUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
maybeNotifyNetworkBlockedForNewState(uid, blockedReasons);
setUidBlockedReasons(uid, blockedReasons);
}
private boolean checkAnyPermissionOf(String... permissions) {
for (String permission : permissions) {
if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
return true;
}
}
return false;
}
private boolean checkAnyPermissionOf(int pid, int uid, String... permissions) {
for (String permission : permissions) {
if (mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) {
return true;
}
}
return false;
}
private void enforceAnyPermissionOf(String... permissions) {
if (!checkAnyPermissionOf(permissions)) {
throw new SecurityException("Requires one of the following permissions: "
+ String.join(", ", permissions) + ".");
}
}
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
"ConnectivityService");
}
private void enforceAccessPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE,
"ConnectivityService");
}
private boolean checkAccessPermission(int pid, int uid) {
return mContext.checkPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, pid, uid)
== PERMISSION_GRANTED;
}
/**
* Performs a strict and comprehensive check of whether a calling package is allowed to
* change the state of network, as the condition differs for pre-M, M+, and
* privileged/preinstalled apps. The caller is expected to have either the
* CHANGE_NETWORK_STATE or the WRITE_SETTINGS permission declared. Either of these
* permissions allow changing network state; WRITE_SETTINGS is a runtime permission and
* can be revoked, but (except in M, excluding M MRs), CHANGE_NETWORK_STATE is a normal
* permission and cannot be revoked. See http://b/23597341
*
* Note: if the check succeeds because the application holds WRITE_SETTINGS, the operation
* of this app will be updated to the current time.
*/
private void enforceChangePermission(String callingPkg, String callingAttributionTag) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE)
== PackageManager.PERMISSION_GRANTED) {
return;
}
if (callingPkg == null) {
throw new SecurityException("Calling package name is null.");
}
final AppOpsManager appOpsMgr = mContext.getSystemService(AppOpsManager.class);
final int uid = mDeps.getCallingUid();
final int mode = appOpsMgr.noteOpNoThrow(AppOpsManager.OPSTR_WRITE_SETTINGS, uid,
callingPkg, callingAttributionTag, null /* message */);
if (mode == AppOpsManager.MODE_ALLOWED) {
return;
}
if ((mode == AppOpsManager.MODE_DEFAULT) && (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_SETTINGS) == PackageManager.PERMISSION_GRANTED)) {
return;
}
throw new SecurityException(callingPkg + " was not granted either of these permissions:"
+ android.Manifest.permission.CHANGE_NETWORK_STATE + ","
+ android.Manifest.permission.WRITE_SETTINGS + ".");
}
private void enforceSettingsPermission() {
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private void enforceNetworkFactoryPermission() {
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_FACTORY,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private void enforceNetworkFactoryOrSettingsPermission() {
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_FACTORY,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private void enforceNetworkFactoryOrTestNetworksPermission() {
enforceAnyPermissionOf(
android.Manifest.permission.MANAGE_TEST_NETWORKS,
android.Manifest.permission.NETWORK_FACTORY,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private boolean checkSettingsPermission() {
return checkAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private boolean checkSettingsPermission(int pid, int uid) {
return PERMISSION_GRANTED == mContext.checkPermission(
android.Manifest.permission.NETWORK_SETTINGS, pid, uid)
|| PERMISSION_GRANTED == mContext.checkPermission(
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid);
}
private void enforceNetworkStackOrSettingsPermission() {
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private void enforceNetworkStackSettingsOrSetup() {
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_SETUP_WIZARD,
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private void enforceAirplaneModePermission() {
enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_AIRPLANE_MODE,
android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_SETUP_WIZARD,
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private void enforceOemNetworkPreferencesPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE,
"ConnectivityService");
}
private void enforceManageTestNetworksPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_TEST_NETWORKS,
"ConnectivityService");
}
private boolean checkNetworkStackPermission() {
return checkAnyPermissionOf(
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private boolean checkNetworkStackPermission(int pid, int uid) {
return checkAnyPermissionOf(pid, uid,
android.Manifest.permission.NETWORK_STACK,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) {
return checkAnyPermissionOf(pid, uid,
android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_SETTINGS);
}
private void enforceConnectivityRestrictedNetworksPermission() {
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS,
"ConnectivityService");
return;
} catch (SecurityException e) { /* fallback to ConnectivityInternalPermission */ }
// TODO: Remove this fallback check after all apps have declared
// CONNECTIVITY_USE_RESTRICTED_NETWORKS.
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL,
"ConnectivityService");
}
private void enforceKeepalivePermission() {
mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
}
private boolean checkLocalMacAddressPermission(int pid, int uid) {
return PERMISSION_GRANTED == mContext.checkPermission(
Manifest.permission.LOCAL_MAC_ADDRESS, pid, uid);
}
private void sendConnectedBroadcast(NetworkInfo info) {
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
private void sendInetConditionBroadcast(NetworkInfo info) {
sendGeneralBroadcast(info, ConnectivityManager.INET_CONDITION_ACTION);
}
private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
Intent intent = new Intent(bcastType);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
if (info.isFailover()) {
intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
info.setFailover(false);
}
if (info.getReason() != null) {
intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
}
if (info.getExtraInfo() != null) {
intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
info.getExtraInfo());
}
intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
return intent;
}
private void sendGeneralBroadcast(NetworkInfo info, String bcastType) {
sendStickyBroadcast(makeGeneralIntent(info, bcastType));
}
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@TargetApi(Build.VERSION_CODES.S)
private void sendStickyBroadcast(Intent intent) {
synchronized (this) {
if (!mSystemReady
&& intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
mInitialBroadcast = new Intent(intent);
}
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
if (VDBG) {
log("sendStickyBroadcast: action=" + intent.getAction());
}
Bundle options = null;
final long ident = Binder.clearCallingIdentity();
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
final NetworkInfo ni = intent.getParcelableExtra(
ConnectivityManager.EXTRA_NETWORK_INFO);
final BroadcastOptions opts = BroadcastOptions.makeBasic();
opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
options = opts.toBundle();
intent.addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
}
try {
mUserAllContext.sendStickyBroadcast(intent, options);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
/**
* Called by SystemServer through ConnectivityManager when the system is ready.
*/
@Override
public void systemReady() {
if (mDeps.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Calling Uid is not system uid.");
}
systemReadyInternal();
}
/**
* Called when ConnectivityService can initialize remaining components.
*/
@VisibleForTesting
public void systemReadyInternal() {
// Load flags after PackageManager is ready to query module version
mFlags.loadFlags(mDeps, mContext);
// Since mApps in PermissionMonitor needs to be populated first to ensure that
// listening network request which is sent by MultipathPolicyTracker won't be added
// NET_CAPABILITY_FOREGROUND capability. Thus, MultipathPolicyTracker.start() must
// be called after PermissionMonitor#startMonitoring().
// Calling PermissionMonitor#startMonitoring() in systemReadyInternal() and the
// MultipathPolicyTracker.start() is called in NetworkPolicyManagerService#systemReady()
// to ensure the tracking will be initialized correctly.
mPermissionMonitor.startMonitoring();
mProxyTracker.loadGlobalProxy();
registerDnsResolverUnsolicitedEventListener();
synchronized (this) {
mSystemReady = true;
if (mInitialBroadcast != null) {
mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
mInitialBroadcast = null;
}
}
// Create network requests for always-on networks.
mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS));
// Update mobile data preference if necessary.
// Note that empty uid list can be skip here only because no uid rules applied before system
// ready. Normally, the empty uid list means to clear the uids rules on netd.
if (!ConnectivitySettingsManager.getMobileDataPreferredUids(mContext).isEmpty()) {
updateMobileDataPreferredUids();
}
}
/**
* Start listening for default data network activity state changes.
*/
@Override
public void registerNetworkActivityListener(@NonNull INetworkActivityListener l) {
mNetworkActivityTracker.registerNetworkActivityListener(l);
}
/**
* Stop listening for default data network activity state changes.
*/
@Override
public void unregisterNetworkActivityListener(@NonNull INetworkActivityListener l) {
mNetworkActivityTracker.unregisterNetworkActivityListener(l);
}
/**
* Check whether the default network radio is currently active.
*/
@Override
public boolean isDefaultNetworkActive() {
return mNetworkActivityTracker.isDefaultNetworkActive();
}
/**
* Reads the network specific MTU size from resources.
* and set it on it's iface.
*/
private void updateMtu(LinkProperties newLp, LinkProperties oldLp) {
final String iface = newLp.getInterfaceName();
final int mtu = newLp.getMtu();
if (oldLp == null && mtu == 0) {
// Silently ignore unset MTU value.
return;
}
if (oldLp != null && newLp.isIdenticalMtu(oldLp)) {
if (VDBG) log("identical MTU - not setting");
return;
}
if (!LinkProperties.isValidMtu(mtu, newLp.hasGlobalIpv6Address())) {
if (mtu != 0) loge("Unexpected mtu value: " + mtu + ", " + iface);
return;
}
// Cannot set MTU without interface name
if (TextUtils.isEmpty(iface)) {
loge("Setting MTU size with null iface.");
return;
}
try {
if (VDBG || DDBG) log("Setting MTU size: " + iface + ", " + mtu);
mNetd.interfaceSetMtu(iface, mtu);
} catch (RemoteException | ServiceSpecificException e) {
loge("exception in interfaceSetMtu()" + e);
}
}
@VisibleForTesting
protected static final String DEFAULT_TCP_BUFFER_SIZES = "4096,87380,110208,4096,16384,110208";
private void updateTcpBufferSizes(String tcpBufferSizes) {
String[] values = null;
if (tcpBufferSizes != null) {
values = tcpBufferSizes.split(",");
}
if (values == null || values.length != 6) {
if (DBG) log("Invalid tcpBufferSizes string: " + tcpBufferSizes +", using defaults");
tcpBufferSizes = DEFAULT_TCP_BUFFER_SIZES;
values = tcpBufferSizes.split(",");
}
if (tcpBufferSizes.equals(mCurrentTcpBufferSizes)) return;
try {
if (VDBG || DDBG) log("Setting tx/rx TCP buffers to " + tcpBufferSizes);
String rmemValues = String.join(" ", values[0], values[1], values[2]);
String wmemValues = String.join(" ", values[3], values[4], values[5]);
mNetd.setTcpRWmemorySize(rmemValues, wmemValues);
mCurrentTcpBufferSizes = tcpBufferSizes;
} catch (RemoteException | ServiceSpecificException e) {
loge("Can't set TCP buffer sizes:" + e);
}
}
@Override
public int getRestoreDefaultNetworkDelay(int networkType) {
String restoreDefaultNetworkDelayStr = mSystemProperties.get(
NETWORK_RESTORE_DELAY_PROP_NAME);
if(restoreDefaultNetworkDelayStr != null &&
restoreDefaultNetworkDelayStr.length() != 0) {
try {
return Integer.parseInt(restoreDefaultNetworkDelayStr);
} catch (NumberFormatException e) {
}
}
// if the system property isn't set, use the value for the apn type
int ret = RESTORE_DEFAULT_NETWORK_DELAY;
if (mLegacyTypeTracker.isTypeSupported(networkType)) {
ret = mLegacyTypeTracker.getRestoreTimerForType(networkType);
}
return ret;
}
private void dumpNetworkDiagnostics(IndentingPrintWriter pw) {
final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>();
final long DIAG_TIME_MS = 5000;
for (NetworkAgentInfo nai : networksSortedById()) {
PrivateDnsConfig privateDnsCfg = mDnsManager.getPrivateDnsConfig(nai.network);
// Start gathering diagnostic information.
netDiags.add(new NetworkDiagnostics(
nai.network,
new LinkProperties(nai.linkProperties), // Must be a copy.
privateDnsCfg,
DIAG_TIME_MS));
}
for (NetworkDiagnostics netDiag : netDiags) {
pw.println();
netDiag.waitForMeasurements();
netDiag.dump(pw);
}
}
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
@Nullable String[] args) {
if (!checkDumpPermission(mContext, TAG, writer)) return;
mPriorityDumper.dump(fd, writer, args);
}
private boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump " + tag + " from from pid="
+ Binder.getCallingPid() + ", uid=" + mDeps.getCallingUid()
+ " due to missing android.permission.DUMP permission");
return false;
} else {
return true;
}
}
private void doDump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
if (CollectionUtils.contains(args, DIAG_ARG)) {
dumpNetworkDiagnostics(pw);
return;
} else if (CollectionUtils.contains(args, NETWORK_ARG)) {
dumpNetworks(pw);
return;
} else if (CollectionUtils.contains(args, REQUEST_ARG)) {
dumpNetworkRequests(pw);
return;
} else if (CollectionUtils.contains(args, TRAFFICCONTROLLER_ARG)) {
boolean verbose = !CollectionUtils.contains(args, SHORT_ARG);
dumpTrafficController(pw, fd, verbose);
return;
}
pw.print("NetworkProviders for:");
for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
pw.print(" " + npi.name);
}
pw.println();
pw.println();
final NetworkAgentInfo defaultNai = getDefaultNetwork();
pw.print("Active default network: ");
if (defaultNai == null) {
pw.println("none");
} else {
pw.println(defaultNai.network.getNetId());
}
pw.println();
pw.println("Current network preferences: ");
pw.increaseIndent();
dumpNetworkPreferences(pw);
pw.decreaseIndent();
pw.println();
pw.println("Current Networks:");
pw.increaseIndent();
dumpNetworks(pw);
pw.decreaseIndent();
pw.println();
pw.println("Status for known UIDs:");
pw.increaseIndent();
final int size = mUidBlockedReasons.size();
for (int i = 0; i < size; i++) {
// Don't crash if the array is modified while dumping in bugreports.
try {
final int uid = mUidBlockedReasons.keyAt(i);
final int blockedReasons = mUidBlockedReasons.valueAt(i);
pw.println("UID=" + uid + " blockedReasons="
+ Integer.toHexString(blockedReasons));
} catch (ArrayIndexOutOfBoundsException e) {
pw.println(" ArrayIndexOutOfBoundsException");
} catch (ConcurrentModificationException e) {
pw.println(" ConcurrentModificationException");
}
}
pw.println();
pw.decreaseIndent();
pw.println("Network Requests:");
pw.increaseIndent();
dumpNetworkRequests(pw);
pw.decreaseIndent();
pw.println();
mLegacyTypeTracker.dump(pw);
pw.println();
mKeepaliveTracker.dump(pw);
pw.println();
dumpAvoidBadWifiSettings(pw);
pw.println();
if (!CollectionUtils.contains(args, SHORT_ARG)) {
pw.println();
pw.println("mNetworkRequestInfoLogs (most recent first):");
pw.increaseIndent();
mNetworkRequestInfoLogs.reverseDump(pw);
pw.decreaseIndent();
pw.println();
pw.println("mNetworkInfoBlockingLogs (most recent first):");
pw.increaseIndent();
mNetworkInfoBlockingLogs.reverseDump(pw);
pw.decreaseIndent();
pw.println();
pw.println("NetTransition WakeLock activity (most recent first):");
pw.increaseIndent();
pw.println("total acquisitions: " + mTotalWakelockAcquisitions);
pw.println("total releases: " + mTotalWakelockReleases);
pw.println("cumulative duration: " + (mTotalWakelockDurationMs / 1000) + "s");
pw.println("longest duration: " + (mMaxWakelockDurationMs / 1000) + "s");
if (mTotalWakelockAcquisitions > mTotalWakelockReleases) {
long duration = SystemClock.elapsedRealtime() - mLastWakeLockAcquireTimestamp;
pw.println("currently holding WakeLock for: " + (duration / 1000) + "s");
}
mWakelockLogs.reverseDump(pw);
pw.println();
pw.println("bandwidth update requests (by uid):");
pw.increaseIndent();
synchronized (mBandwidthRequests) {
for (int i = 0; i < mBandwidthRequests.size(); i++) {
pw.println("[" + mBandwidthRequests.keyAt(i)
+ "]: " + mBandwidthRequests.valueAt(i));
}
}
pw.decreaseIndent();
pw.decreaseIndent();
pw.println();
pw.println("mOemNetworkPreferencesLogs (most recent first):");
pw.increaseIndent();
mOemNetworkPreferencesLogs.reverseDump(pw);
pw.decreaseIndent();
}
pw.println();
pw.println();
pw.println("Permission Monitor:");
pw.increaseIndent();
mPermissionMonitor.dump(pw);
pw.decreaseIndent();
pw.println();
pw.println("Legacy network activity:");
pw.increaseIndent();
mNetworkActivityTracker.dump(pw);
pw.decreaseIndent();
}
private void dumpNetworks(IndentingPrintWriter pw) {
for (NetworkAgentInfo nai : networksSortedById()) {
pw.println(nai.toString());
pw.increaseIndent();
pw.println(String.format(
"Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d",
nai.numForegroundNetworkRequests(),
nai.numNetworkRequests() - nai.numRequestNetworkRequests(),
nai.numBackgroundNetworkRequests(),
nai.numNetworkRequests()));
pw.increaseIndent();
for (int i = 0; i < nai.numNetworkRequests(); i++) {
pw.println(nai.requestAt(i).toString());
}
pw.decreaseIndent();
pw.println("Inactivity Timers:");
pw.increaseIndent();
nai.dumpInactivityTimers(pw);
pw.decreaseIndent();
pw.decreaseIndent();
}
}
private void dumpNetworkPreferences(IndentingPrintWriter pw) {
if (!mProfileNetworkPreferences.isEmpty()) {
pw.println("Profile preferences:");
pw.increaseIndent();
pw.println(mProfileNetworkPreferences.preferences);
pw.decreaseIndent();
}
if (!mOemNetworkPreferences.isEmpty()) {
pw.println("OEM preferences:");
pw.increaseIndent();
pw.println(mOemNetworkPreferences);
pw.decreaseIndent();
}
if (!mMobileDataPreferredUids.isEmpty()) {
pw.println("Mobile data preferred UIDs:");
pw.increaseIndent();
pw.println(mMobileDataPreferredUids);
pw.decreaseIndent();
}
pw.println("Default requests:");
pw.increaseIndent();
dumpPerAppDefaultRequests(pw);
pw.decreaseIndent();
}
private void dumpPerAppDefaultRequests(IndentingPrintWriter pw) {
for (final NetworkRequestInfo defaultRequest : mDefaultNetworkRequests) {
if (mDefaultRequest == defaultRequest) {
continue;
}
final NetworkAgentInfo satisfier = defaultRequest.getSatisfier();
final String networkOutput;
if (null == satisfier) {
networkOutput = "null";
} else if (mNoServiceNetwork.equals(satisfier)) {
networkOutput = "no service network";
} else {
networkOutput = String.valueOf(satisfier.network.netId);
}
final String asUidString = (defaultRequest.mAsUid == defaultRequest.mUid)
? "" : " asUid: " + defaultRequest.mAsUid;
final String requestInfo = "Request: [uid/pid:" + defaultRequest.mUid + "/"
+ defaultRequest.mPid + asUidString + "]";
final String satisfierOutput = "Satisfier: [" + networkOutput + "]"
+ " Preference order: " + defaultRequest.mPreferenceOrder
+ " Tracked UIDs: " + defaultRequest.getUids();
pw.println(requestInfo + " - " + satisfierOutput);
}
}
private void dumpNetworkRequests(IndentingPrintWriter pw) {
NetworkRequestInfo[] infos = null;
while (infos == null) {
try {
infos = requestsSortedById();
} catch (ConcurrentModificationException e) {
// mNetworkRequests should only be accessed from handler thread, except dump().
// As dump() is never called in normal usage, it would be needlessly expensive
// to lock the collection only for its benefit. Instead, retry getting the
// requests if ConcurrentModificationException is thrown during dump().
}
}
for (NetworkRequestInfo nri : infos) {
pw.println(nri.toString());
}
}
private void dumpTrafficController(IndentingPrintWriter pw, final FileDescriptor fd,
boolean verbose) {
try {
mBpfNetMaps.dump(fd, verbose);
} catch (ServiceSpecificException e) {
pw.println(e.getMessage());
} catch (IOException e) {
loge("Dump BPF maps failed, " + e);
}
}
private void dumpAllRequestInfoLogsToLogcat() {
try (PrintWriter logPw = new PrintWriter(new Writer() {
@Override
public void write(final char[] cbuf, final int off, final int len) {
// This method is called with 0-length and 1-length arrays for empty strings
// or strings containing only the DEL character.
if (len <= 1) return;
Log.e(TAG, new String(cbuf, off, len));
}
@Override public void flush() {}
@Override public void close() {}
})) {
mNetworkRequestInfoLogs.dump(logPw);
}
}
/**
* Return an array of all current NetworkAgentInfos sorted by network id.
*/
private NetworkAgentInfo[] networksSortedById() {
NetworkAgentInfo[] networks = new NetworkAgentInfo[0];
networks = mNetworkAgentInfos.toArray(networks);
Arrays.sort(networks, Comparator.comparingInt(nai -> nai.network.getNetId()));
return networks;
}
/**
* Return an array of all current NetworkRequest sorted by request id.
*/
@VisibleForTesting
NetworkRequestInfo[] requestsSortedById() {
NetworkRequestInfo[] requests = new NetworkRequestInfo[0];
requests = getNrisFromGlobalRequests().toArray(requests);
// Sort the array based off the NRI containing the min requestId in its requests.
Arrays.sort(requests,
Comparator.comparingInt(nri -> Collections.min(nri.mRequests,
Comparator.comparingInt(req -> req.requestId)).requestId
)
);
return requests;
}
private boolean isLiveNetworkAgent(NetworkAgentInfo nai, int what) {
final NetworkAgentInfo officialNai = getNetworkAgentInfoForNetwork(nai.network);
if (officialNai != null && officialNai.equals(nai)) return true;
if (officialNai != null || VDBG) {
loge(eventName(what) + " - isLiveNetworkAgent found mismatched netId: " + officialNai +
" - " + nai);
}
return false;
}
// must be stateless - things change under us.
private class NetworkStateTrackerHandler extends Handler {
public NetworkStateTrackerHandler(Looper looper) {
super(looper);
}
private void maybeHandleNetworkAgentMessage(Message msg) {
final Pair<NetworkAgentInfo, Object> arg = (Pair<NetworkAgentInfo, Object>) msg.obj;
final NetworkAgentInfo nai = arg.first;
if (!mNetworkAgentInfos.contains(nai)) {
if (VDBG) {
log(String.format("%s from unknown NetworkAgent", eventName(msg.what)));
}
return;
}
switch (msg.what) {
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
(NetworkCapabilities) arg.second);
processCapabilitiesFromAgent(nai, networkCapabilities);
updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
break;
}
case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
LinkProperties newLp = (LinkProperties) arg.second;
processLinkPropertiesFromAgent(nai, newLp);
handleUpdateLinkProperties(nai, newLp);
break;
}
case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
NetworkInfo info = (NetworkInfo) arg.second;
updateNetworkInfo(nai, info);
break;
}
case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
updateNetworkScore(nai, (NetworkScore) arg.second);
break;
}
case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
if (nai.everConnected) {
loge("ERROR: cannot call explicitlySelected on already-connected network");
// Note that if the NAI had been connected, this would affect the
// score, and therefore would require re-mixing the score and performing
// a rematch.
}
nai.networkAgentConfig.explicitlySelected = toBool(msg.arg1);
nai.networkAgentConfig.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
// Mark the network as temporarily accepting partial connectivity so that it
// will be validated (and possibly become default) even if it only provides
// partial internet access. Note that if user connects to partial connectivity
// and choose "don't ask again", then wifi disconnected by some reasons(maybe
// out of wifi coverage) and if the same wifi is available again, the device
// will auto connect to this wifi even though the wifi has "no internet".
// TODO: Evaluate using a separate setting in IpMemoryStore.
nai.networkAgentConfig.acceptPartialConnectivity = toBool(msg.arg2);
break;
}
case NetworkAgent.EVENT_SOCKET_KEEPALIVE: {
mKeepaliveTracker.handleEventSocketKeepalive(nai, msg.arg1, msg.arg2);
break;
}
case NetworkAgent.EVENT_UNDERLYING_NETWORKS_CHANGED: {
// TODO: prevent loops, e.g., if a network declares itself as underlying.
final List<Network> underlying = (List<Network>) arg.second;
if (isLegacyLockdownNai(nai)
&& (underlying == null || underlying.size() != 1)) {
Log.wtf(TAG, "Legacy lockdown VPN " + nai.toShortString()
+ " must have exactly one underlying network: " + underlying);
}
final Network[] oldUnderlying = nai.declaredUnderlyingNetworks;
nai.declaredUnderlyingNetworks = (underlying != null)
? underlying.toArray(new Network[0]) : null;
if (!Arrays.equals(oldUnderlying, nai.declaredUnderlyingNetworks)) {
if (DBG) {
log(nai.toShortString() + " changed underlying networks to "
+ Arrays.toString(nai.declaredUnderlyingNetworks));
}
updateCapabilitiesForNetwork(nai);
notifyIfacesChangedForNetworkStats();
}
break;
}
case NetworkAgent.EVENT_TEARDOWN_DELAY_CHANGED: {
if (msg.arg1 >= 0 && msg.arg1 <= NetworkAgent.MAX_TEARDOWN_DELAY_MS) {
nai.teardownDelayMs = msg.arg1;
} else {
logwtf(nai.toShortString() + " set invalid teardown delay " + msg.arg1);
}
break;
}
case NetworkAgent.EVENT_LINGER_DURATION_CHANGED: {
nai.setLingerDuration((int) arg.second);
break;
}
case NetworkAgent.EVENT_ADD_DSCP_POLICY: {
DscpPolicy policy = (DscpPolicy) arg.second;
if (mDscpPolicyTracker != null) {
mDscpPolicyTracker.addDscpPolicy(nai, policy);
}
break;
}
case NetworkAgent.EVENT_REMOVE_DSCP_POLICY: {
if (mDscpPolicyTracker != null) {
mDscpPolicyTracker.removeDscpPolicy(nai, (int) arg.second);
}
break;
}
case NetworkAgent.EVENT_REMOVE_ALL_DSCP_POLICIES: {
if (mDscpPolicyTracker != null) {
mDscpPolicyTracker.removeAllDscpPolicies(nai);
}
break;
}
}
}
private boolean maybeHandleNetworkMonitorMessage(Message msg) {
final int netId = msg.arg2;
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
switch (msg.what) {
default:
return false;
case EVENT_PROBE_STATUS_CHANGED: {
if (nai == null) {
break;
}
final int probesCompleted = ((Pair<Integer, Integer>) msg.obj).first;
final int probesSucceeded = ((Pair<Integer, Integer>) msg.obj).second;
final boolean probePrivateDnsCompleted =
((probesCompleted & NETWORK_VALIDATION_PROBE_PRIVDNS) != 0);
final boolean privateDnsBroken =
((probesSucceeded & NETWORK_VALIDATION_PROBE_PRIVDNS) == 0);
if (probePrivateDnsCompleted) {
if (nai.networkCapabilities.isPrivateDnsBroken() != privateDnsBroken) {
nai.networkCapabilities.setPrivateDnsBroken(privateDnsBroken);
updateCapabilitiesForNetwork(nai);
}
// Only show the notification when the private DNS is broken and the
// PRIVATE_DNS_BROKEN notification hasn't shown since last valid.
if (privateDnsBroken && !nai.networkAgentConfig.hasShownBroken) {
showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN);
}
nai.networkAgentConfig.hasShownBroken = privateDnsBroken;
} else if (nai.networkCapabilities.isPrivateDnsBroken()) {
// If probePrivateDnsCompleted is false but nai.networkCapabilities says
// private DNS is broken, it means this network is being reevaluated.
// Either probing private DNS is not necessary any more or it hasn't been
// done yet. In either case, the networkCapabilities should be updated to
// reflect the new status.
nai.networkCapabilities.setPrivateDnsBroken(false);
updateCapabilitiesForNetwork(nai);
nai.networkAgentConfig.hasShownBroken = false;
}
break;
}
case EVENT_NETWORK_TESTED: {
final NetworkTestedResults results = (NetworkTestedResults) msg.obj;
if (nai == null) break;
handleNetworkTested(nai, results.mTestResult,
(results.mRedirectUrl == null) ? "" : results.mRedirectUrl);
break;
}
case EVENT_PROVISIONING_NOTIFICATION: {
final boolean visible = toBool(msg.arg1);
// If captive portal status has changed, update capabilities or disconnect.
if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
nai.lastCaptivePortalDetected = visible;
nai.everCaptivePortalDetected |= visible;
if (nai.lastCaptivePortalDetected &&
ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
== getCaptivePortalMode()) {
if (DBG) log("Avoiding captive portal network: " + nai.toShortString());
nai.onPreventAutomaticReconnect();
teardownUnneededNetwork(nai);
break;
}
updateCapabilitiesForNetwork(nai);
}
if (!visible) {
// Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other
// notifications belong to the same network may be cleared unexpectedly.
mNotifier.clearNotification(netId, NotificationType.SIGN_IN);
mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH);
} else {
if (nai == null) {
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
break;
}
if (!nai.networkAgentConfig.provisioningNotificationDisabled) {
mNotifier.showNotification(netId, NotificationType.SIGN_IN, nai, null,
(PendingIntent) msg.obj,
nai.networkAgentConfig.explicitlySelected);
}
}
break;
}
case EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
if (nai == null) break;
updatePrivateDns(nai, (PrivateDnsConfig) msg.obj);
break;
}
case EVENT_CAPPORT_DATA_CHANGED: {
if (nai == null) break;
handleCapportApiDataUpdate(nai, (CaptivePortalData) msg.obj);
break;
}
}
return true;
}
private void handleNetworkTested(
@NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) {
final boolean wasPartial = nai.partialConnectivity;
nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
final boolean partialConnectivityChanged =
(wasPartial != nai.partialConnectivity);
final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0);
final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai);
if (DBG) {
final String logMsg = !TextUtils.isEmpty(redirectUrl)
? " with redirect to " + redirectUrl
: "";
log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg);
}
if (valid != nai.lastValidated) {
final int oldScore = nai.getCurrentScore();
nai.lastValidated = valid;
nai.everValidated |= valid;
updateCapabilities(oldScore, nai, nai.networkCapabilities);
if (valid) {
handleFreshlyValidatedNetwork(nai);
// Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and
// LOST_INTERNET notifications if network becomes valid.
mNotifier.clearNotification(nai.network.getNetId(),
NotificationType.NO_INTERNET);
mNotifier.clearNotification(nai.network.getNetId(),
NotificationType.LOST_INTERNET);
mNotifier.clearNotification(nai.network.getNetId(),
NotificationType.PARTIAL_CONNECTIVITY);
mNotifier.clearNotification(nai.network.getNetId(),
NotificationType.PRIVATE_DNS_BROKEN);
// If network becomes valid, the hasShownBroken should be reset for
// that network so that the notification will be fired when the private
// DNS is broken again.
nai.networkAgentConfig.hasShownBroken = false;
}
} else if (partialConnectivityChanged) {
updateCapabilitiesForNetwork(nai);
}
updateInetCondition(nai);
// Let the NetworkAgent know the state of its network
// TODO: Evaluate to update partial connectivity to status to NetworkAgent.
nai.onValidationStatusChanged(
valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK,
redirectUrl);
// If NetworkMonitor detects partial connectivity before
// EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
// immediately. Re-notify partial connectivity silently if no internet
// notification already there.
if (!wasPartial && nai.partialConnectivity) {
// Remove delayed message if there is a pending message.
mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
handlePromptUnvalidated(nai.network);
}
if (wasValidated && !nai.lastValidated) {
handleNetworkUnvalidated(nai);
}
}
private int getCaptivePortalMode() {
return Settings.Global.getInt(mContext.getContentResolver(),
ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE,
ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
}
private boolean maybeHandleNetworkAgentInfoMessage(Message msg) {
switch (msg.what) {
default:
return false;
case NetworkAgentInfo.EVENT_NETWORK_LINGER_COMPLETE: {
NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj;
if (nai != null && isLiveNetworkAgent(nai, msg.what)) {
handleLingerComplete(nai);
}
break;
}
case NetworkAgentInfo.EVENT_AGENT_REGISTERED: {
handleNetworkAgentRegistered(msg);
break;
}
case NetworkAgentInfo.EVENT_AGENT_DISCONNECTED: {
handleNetworkAgentDisconnected(msg);
break;
}
}
return true;
}
@Override
public void handleMessage(Message msg) {
if (!maybeHandleNetworkMonitorMessage(msg)
&& !maybeHandleNetworkAgentInfoMessage(msg)) {
maybeHandleNetworkAgentMessage(msg);
}
}
}
private class NetworkMonitorCallbacks extends INetworkMonitorCallbacks.Stub {
private final int mNetId;
private final AutodestructReference<NetworkAgentInfo> mNai;
private NetworkMonitorCallbacks(NetworkAgentInfo nai) {
mNetId = nai.network.getNetId();
mNai = new AutodestructReference<>(nai);
}
@Override
public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT,
new Pair<>(mNai.getAndDestroy(), networkMonitor)));
}
@Override
public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
// Legacy version of notifyNetworkTestedWithExtras.
// Would only be called if the system has a NetworkStack module older than the
// framework, which does not happen in practice.
Log.wtf(TAG, "Deprecated notifyNetworkTested called: no action taken");
}
@Override
public void notifyNetworkTestedWithExtras(NetworkTestResultParcelable p) {
// Notify mTrackerHandler and mConnectivityDiagnosticsHandler of the event. Both use
// the same looper so messages will be processed in sequence.
final Message msg = mTrackerHandler.obtainMessage(
EVENT_NETWORK_TESTED,
0, mNetId,
new NetworkTestedResults(
mNetId, p.result, p.timestampMillis, p.redirectUrl));
mTrackerHandler.sendMessage(msg);
// Invoke ConnectivityReport generation for this Network test event.
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(mNetId);
if (nai == null) return;
// NetworkMonitor reports the network validation result as a bitmask while
// ConnectivityDiagnostics treats this value as an int. Convert the result to a single
// logical value for ConnectivityDiagnostics.
final int validationResult = networkMonitorValidationResultToConnDiagsValidationResult(
p.result);
final PersistableBundle extras = new PersistableBundle();
extras.putInt(KEY_NETWORK_VALIDATION_RESULT, validationResult);
extras.putInt(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK, p.probesSucceeded);
extras.putInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK, p.probesAttempted);
ConnectivityReportEvent reportEvent =
new ConnectivityReportEvent(p.timestampMillis, nai, extras);
final Message m = mConnectivityDiagnosticsHandler.obtainMessage(
ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED, reportEvent);
mConnectivityDiagnosticsHandler.sendMessage(m);
}
@Override
public void notifyPrivateDnsConfigResolved(PrivateDnsConfigParcel config) {
mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
EVENT_PRIVATE_DNS_CONFIG_RESOLVED,
0, mNetId, PrivateDnsConfig.fromParcel(config)));
}
@Override
public void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) {
mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
EVENT_PROBE_STATUS_CHANGED,
0, mNetId, new Pair<>(probesCompleted, probesSucceeded)));
}
@Override
public void notifyCaptivePortalDataChanged(CaptivePortalData data) {
mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
EVENT_CAPPORT_DATA_CHANGED,
0, mNetId, data));
}
@Override
public void showProvisioningNotification(String action, String packageName) {
final Intent intent = new Intent(action);
intent.setPackage(packageName);
final PendingIntent pendingIntent;
// Only the system server can register notifications with package "android"
final long token = Binder.clearCallingIdentity();
try {
pendingIntent = PendingIntent.getBroadcast(
mContext,
0 /* requestCode */,
intent,
PendingIntent.FLAG_IMMUTABLE);
} finally {
Binder.restoreCallingIdentity(token);
}
mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_SHOW,
mNetId, pendingIntent));
}
@Override
public void hideProvisioningNotification() {
mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE, mNetId));
}
@Override
public void notifyDataStallSuspected(DataStallReportParcelable p) {
ConnectivityService.this.notifyDataStallSuspected(p, mNetId);
}
@Override
public int getInterfaceVersion() {
return this.VERSION;
}
@Override
public String getInterfaceHash() {
return this.HASH;
}
}
/**
* Converts the given NetworkMonitor-specific validation result bitmask to a
* ConnectivityDiagnostics-specific validation result int.
*/
private int networkMonitorValidationResultToConnDiagsValidationResult(int validationResult) {
if ((validationResult & NETWORK_VALIDATION_RESULT_SKIPPED) != 0) {
return ConnectivityReport.NETWORK_VALIDATION_RESULT_SKIPPED;
}
if ((validationResult & NETWORK_VALIDATION_RESULT_VALID) == 0) {
return ConnectivityReport.NETWORK_VALIDATION_RESULT_INVALID;
}
return (validationResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0
? ConnectivityReport.NETWORK_VALIDATION_RESULT_PARTIALLY_VALID
: ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID;
}
private void notifyDataStallSuspected(DataStallReportParcelable p, int netId) {
log("Data stall detected with methods: " + p.detectionMethod);
final PersistableBundle extras = new PersistableBundle();
int detectionMethod = 0;
if (hasDataStallDetectionMethod(p, DETECTION_METHOD_DNS_EVENTS)) {
extras.putInt(KEY_DNS_CONSECUTIVE_TIMEOUTS, p.dnsConsecutiveTimeouts);
detectionMethod |= DETECTION_METHOD_DNS_EVENTS;
}
if (hasDataStallDetectionMethod(p, DETECTION_METHOD_TCP_METRICS)) {
extras.putInt(KEY_TCP_PACKET_FAIL_RATE, p.tcpPacketFailRate);
extras.putInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS,
p.tcpMetricsCollectionPeriodMillis);
detectionMethod |= DETECTION_METHOD_TCP_METRICS;
}
final Message msg = mConnectivityDiagnosticsHandler.obtainMessage(
ConnectivityDiagnosticsHandler.EVENT_DATA_STALL_SUSPECTED, detectionMethod, netId,
new Pair<>(p.timestampMillis, extras));
// NetworkStateTrackerHandler currently doesn't take any actions based on data
// stalls so send the message directly to ConnectivityDiagnosticsHandler and avoid
// the cost of going through two handlers.
mConnectivityDiagnosticsHandler.sendMessage(msg);
}
private boolean hasDataStallDetectionMethod(DataStallReportParcelable p, int detectionMethod) {
return (p.detectionMethod & detectionMethod) != 0;
}
private boolean networkRequiresPrivateDnsValidation(NetworkAgentInfo nai) {
return isPrivateDnsValidationRequired(nai.networkCapabilities);
}
private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
if (nai == null) return;
// If the Private DNS mode is opportunistic, reprogram the DNS servers
// in order to restart a validation pass from within netd.
final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig();
if (cfg.useTls && TextUtils.isEmpty(cfg.hostname)) {
updateDnses(nai.linkProperties, null, nai.network.getNetId());
}
}
private void handlePrivateDnsSettingsChanged() {
final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig();
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
handlePerNetworkPrivateDnsConfig(nai, cfg);
if (networkRequiresPrivateDnsValidation(nai)) {
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
}
}
private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) {
// Private DNS only ever applies to networks that might provide
// Internet access and therefore also require validation.
if (!networkRequiresPrivateDnsValidation(nai)) return;
// Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
// schedule DNS resolutions. If a DNS resolution is required the
// result will be sent back to us.
nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
// With Private DNS bypass support, we can proceed to update the
// Private DNS config immediately, even if we're in strict mode
// and have not yet resolved the provider name into a set of IPs.
updatePrivateDns(nai, cfg);
}
private void updatePrivateDns(NetworkAgentInfo nai, PrivateDnsConfig newCfg) {
mDnsManager.updatePrivateDns(nai.network, newCfg);
updateDnses(nai.linkProperties, null, nai.network.getNetId());
}
private void handlePrivateDnsValidationUpdate(PrivateDnsValidationUpdate update) {
NetworkAgentInfo nai = getNetworkAgentInfoForNetId(update.netId);
if (nai == null) {
return;
}
mDnsManager.updatePrivateDnsValidation(update);
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
private void handleNat64PrefixEvent(int netId, int operation, String prefixAddress,
int prefixLength) {
NetworkAgentInfo nai = mNetworkForNetId.get(netId);
if (nai == null) return;
log(String.format("NAT64 prefix changed on netId %d: operation=%d, %s/%d",
netId, operation, prefixAddress, prefixLength));
IpPrefix prefix = null;
if (operation == IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED) {
try {
prefix = new IpPrefix(InetAddresses.parseNumericAddress(prefixAddress),
prefixLength);
} catch (IllegalArgumentException e) {
loge("Invalid NAT64 prefix " + prefixAddress + "/" + prefixLength);
return;
}
}
nai.clatd.setNat64PrefixFromDns(prefix);
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
private void handleCapportApiDataUpdate(@NonNull final NetworkAgentInfo nai,
@Nullable final CaptivePortalData data) {
nai.capportApiData = data;
// CaptivePortalData will be merged into LinkProperties from NetworkAgentInfo
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
/**
* Updates the inactivity state from the network requests inside the NAI.
* @param nai the agent info to update
* @param now the timestamp of the event causing this update
* @return whether the network was inactive as a result of this update
*/
private boolean updateInactivityState(@NonNull final NetworkAgentInfo nai, final long now) {
// 1. Update the inactivity timer. If it's changed, reschedule or cancel the alarm.
// 2. If the network was inactive and there are now requests, unset inactive.
// 3. If this network is unneeded (which implies it is not lingering), and there is at least
// one lingered request, set inactive.
nai.updateInactivityTimer();
if (nai.isInactive() && nai.numForegroundNetworkRequests() > 0) {
if (DBG) log("Unsetting inactive " + nai.toShortString());
nai.unsetInactive();
logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER);
} else if (unneeded(nai, UnneededFor.LINGER) && nai.getInactivityExpiry() > 0) {
if (DBG) {
final int lingerTime = (int) (nai.getInactivityExpiry() - now);
log("Setting inactive " + nai.toShortString() + " for " + lingerTime + "ms");
}
nai.setInactive();
logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER);
return true;
}
return false;
}
private void handleNetworkAgentRegistered(Message msg) {
final NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj;
if (!mNetworkAgentInfos.contains(nai)) {
return;
}
if (msg.arg1 == NetworkAgentInfo.ARG_AGENT_SUCCESS) {
if (VDBG) log("NetworkAgent registered");
} else {
loge("Error connecting NetworkAgent");
mNetworkAgentInfos.remove(nai);
if (nai != null) {
final boolean wasDefault = isDefaultNetwork(nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.remove(nai.network.getNetId());
}
mNetIdManager.releaseNetId(nai.network.getNetId());
// Just in case.
mLegacyTypeTracker.remove(nai, wasDefault);
}
}
}
private void handleNetworkAgentDisconnected(Message msg) {
NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj;
disconnectAndDestroyNetwork(nai);
}
// Destroys a network, remove references to it from the internal state managed by
// ConnectivityService, free its interfaces and clean up.
// Must be called on the Handler thread.
private void disconnectAndDestroyNetwork(NetworkAgentInfo nai) {
ensureRunningOnConnectivityServiceThread();
if (!mNetworkAgentInfos.contains(nai)) return;
if (DBG) {
log(nai.toShortString() + " disconnected, was satisfying " + nai.numNetworkRequests());
}
// Clear all notifications of this network.
mNotifier.clearNotification(nai.network.getNetId());
// A network agent has disconnected.
// TODO - if we move the logic to the network agent (have them disconnect
// because they lost all their requests or because their score isn't good)
// then they would disconnect organically, report their new state and then
// disconnect the channel.
if (nai.networkInfo.isConnected()) {
nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
null, null);
}
final boolean wasDefault = isDefaultNetwork(nai);
if (wasDefault) {
mDefaultInetConditionPublished = 0;
}
notifyIfacesChangedForNetworkStats();
// TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
// by other networks that are already connected. Perhaps that can be done by
// sending all CALLBACK_LOST messages (for requests, not listens) at the end
// of rematchAllNetworksAndRequests
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK);
mQosCallbackTracker.handleNetworkReleased(nai.network);
for (String iface : nai.linkProperties.getAllInterfaceNames()) {
// Disable wakeup packet monitoring for each interface.
wakeupModifyInterface(iface, nai.networkCapabilities, false);
}
nai.networkMonitor().notifyNetworkDisconnected();
mNetworkAgentInfos.remove(nai);
nai.clatd.update();
synchronized (mNetworkForNetId) {
// Remove the NetworkAgent, but don't mark the netId as
// available until we've told netd to delete it below.
mNetworkForNetId.remove(nai.network.getNetId());
}
propagateUnderlyingNetworkCapabilities(nai.network);
// Remove all previously satisfied requests.
for (int i = 0; i < nai.numNetworkRequests(); i++) {
final NetworkRequest request = nai.requestAt(i);
final NetworkRequestInfo nri = mNetworkRequests.get(request);
final NetworkAgentInfo currentNetwork = nri.getSatisfier();
if (currentNetwork != null
&& currentNetwork.network.getNetId() == nai.network.getNetId()) {
// uid rules for this network will be removed in destroyNativeNetwork(nai).
// TODO : setting the satisfier is in fact the job of the rematch. Teach the
// rematch not to keep disconnected agents instead of setting it here ; this
// will also allow removing updating the offers below.
nri.setSatisfier(null, null);
for (final NetworkOfferInfo noi : mNetworkOffers) {
informOffer(nri, noi.offer, mNetworkRanker);
}
if (mDefaultRequest == nri) {
// TODO : make battery stats aware that since 2013 multiple interfaces may be
// active at the same time. For now keep calling this with the default
// network, because while incorrect this is the closest to the old (also
// incorrect) behavior.
mNetworkActivityTracker.updateDataActivityTracking(
null /* newNetwork */, nai);
ensureNetworkTransitionWakelock(nai.toShortString());
}
}
}
nai.clearInactivityState();
// TODO: mLegacyTypeTracker.remove seems redundant given there's a full rematch right after.
// Currently, deleting it breaks tests that check for the default network disconnecting.
// Find out why, fix the rematch code, and delete this.
mLegacyTypeTracker.remove(nai, wasDefault);
rematchAllNetworksAndRequests();
mLingerMonitor.noteDisconnect(nai);
// Immediate teardown.
if (nai.teardownDelayMs == 0) {
destroyNetwork(nai);
return;
}
// Delayed teardown.
if (nai.created) {
try {
mNetd.networkSetPermissionForNetwork(nai.network.netId, INetd.PERMISSION_SYSTEM);
} catch (RemoteException e) {
Log.d(TAG, "Error marking network restricted during teardown: ", e);
}
}
mHandler.postDelayed(() -> destroyNetwork(nai), nai.teardownDelayMs);
}
private void destroyNetwork(NetworkAgentInfo nai) {
if (nai.created) {
// Tell netd to clean up the configuration for this network
// (routing rules, DNS, etc).
// This may be slow as it requires a lot of netd shelling out to ip and
// ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
// after we've rematched networks with requests (which might change the default
// network or service a new request from an app), so network traffic isn't interrupted
// for an unnecessarily long time.
destroyNativeNetwork(nai);
mDnsManager.removeNetwork(nai.network);
// clean up tc police filters on interface.
if (canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) {
mDeps.disableIngressRateLimit(nai.linkProperties.getInterfaceName());
}
}
mNetIdManager.releaseNetId(nai.network.getNetId());
nai.onNetworkDestroyed();
}
private boolean createNativeNetwork(@NonNull NetworkAgentInfo nai) {
try {
// This should never fail. Specifying an already in use NetID will cause failure.
final NativeNetworkConfig config;
if (nai.isVPN()) {
if (getVpnType(nai) == VpnManager.TYPE_VPN_NONE) {
Log.wtf(TAG, "Unable to get VPN type from network " + nai.toShortString());
return false;
}
config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.VIRTUAL,
INetd.PERMISSION_NONE,
(nai.networkAgentConfig == null || !nai.networkAgentConfig.allowBypass),
getVpnType(nai), nai.networkAgentConfig.excludeLocalRouteVpn);
} else {
config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.PHYSICAL,
getNetworkPermission(nai.networkCapabilities), /*secure=*/ false,
VpnManager.TYPE_VPN_NONE, /*excludeLocalRoutes=*/ false);
}
mNetd.networkCreate(config);
mDnsResolver.createNetworkCache(nai.network.getNetId());
mDnsManager.updateTransportsForNetwork(nai.network.getNetId(),
nai.networkCapabilities.getTransportTypes());
return true;
} catch (RemoteException | ServiceSpecificException e) {
loge("Error creating network " + nai.toShortString() + ": " + e.getMessage());
return false;
}
}
private void destroyNativeNetwork(@NonNull NetworkAgentInfo nai) {
try {
mNetd.networkDestroy(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception destroying network(networkDestroy): " + e);
}
try {
mDnsResolver.destroyNetworkCache(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception destroying network: " + e);
}
}
// If this method proves to be too slow then we can maintain a separate
// pendingIntent => NetworkRequestInfo map.
// This method assumes that every non-null PendingIntent maps to exactly 1 NetworkRequestInfo.
private NetworkRequestInfo findExistingNetworkRequestInfo(PendingIntent pendingIntent) {
for (Map.Entry<NetworkRequest, NetworkRequestInfo> entry : mNetworkRequests.entrySet()) {
PendingIntent existingPendingIntent = entry.getValue().mPendingIntent;
if (existingPendingIntent != null &&
mDeps.intentFilterEquals(existingPendingIntent, pendingIntent)) {
return entry.getValue();
}
}
return null;
}
private void checkNrisConsistency(final NetworkRequestInfo nri) {
if (SdkLevel.isAtLeastT()) {
for (final NetworkRequestInfo n : mNetworkRequests.values()) {
if (n.mBinder != null && n.mBinder == nri.mBinder) {
// Temporary help to debug b/194394697 ; TODO : remove this function when the
// bug is fixed.
dumpAllRequestInfoLogsToLogcat();
throw new IllegalStateException("This NRI is already registered. New : " + nri
+ ", existing : " + n);
}
}
}
}
private boolean hasCarrierPrivilegeForNetworkCaps(final int callingUid,
@NonNull final NetworkCapabilities caps) {
if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
callingUid, caps);
}
return false;
}
private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) {
final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
// handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests.
ensureNotMultilayerRequest(nri, "handleRegisterNetworkRequestWithIntent");
final NetworkRequestInfo existingRequest =
findExistingNetworkRequestInfo(nri.mPendingIntent);
if (existingRequest != null) { // remove the existing request.
if (DBG) {
log("Replacing " + existingRequest.mRequests.get(0) + " with "
+ nri.mRequests.get(0) + " because their intents matched.");
}
handleReleaseNetworkRequest(existingRequest.mRequests.get(0), mDeps.getCallingUid(),
/* callOnUnavailable */ false);
}
handleRegisterNetworkRequest(nri);
}
private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) {
handleRegisterNetworkRequests(Collections.singleton(nri));
}
private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
ensureRunningOnConnectivityServiceThread();
NetworkRequest requestToBeReleased = null;
for (final NetworkRequestInfo nri : nris) {
mNetworkRequestInfoLogs.log("REGISTER " + nri);
checkNrisConsistency(nri);
for (final NetworkRequest req : nri.mRequests) {
mNetworkRequests.put(req, nri);
// TODO: Consider update signal strength for other types.
if (req.isListen()) {
for (final NetworkAgentInfo network : mNetworkAgentInfos) {
if (req.networkCapabilities.hasSignalStrength()
&& network.satisfiesImmutableCapabilitiesOf(req)) {
updateSignalStrengthThresholds(network, "REGISTER", req);
}
}
}
if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
if (!hasCarrierPrivilegeForNetworkCaps(nri.mUid, req.networkCapabilities)
&& !checkConnectivityRestrictedNetworksPermission(
nri.mPid, nri.mUid)) {
requestToBeReleased = req;
}
}
}
// If this NRI has a satisfier already, it is replacing an older request that
// has been removed. Track it.
final NetworkRequest activeRequest = nri.getActiveRequest();
if (null != activeRequest) {
// If there is an active request, then for sure there is a satisfier.
nri.getSatisfier().addRequest(activeRequest);
}
}
if (requestToBeReleased != null) {
releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased);
return;
}
if (mFlags.noRematchAllRequestsOnRegister()) {
rematchNetworksAndRequests(nris);
} else {
rematchAllNetworksAndRequests();
}
// Requests that have not been matched to a network will not have been sent to the
// providers, because the old satisfier and the new satisfier are the same (null in this
// case). Send these requests to the providers.
for (final NetworkRequestInfo nri : nris) {
for (final NetworkOfferInfo noi : mNetworkOffers) {
informOffer(nri, noi.offer, mNetworkRanker);
}
}
}
private void handleReleaseNetworkRequestWithIntent(@NonNull final PendingIntent pendingIntent,
final int callingUid) {
final NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
if (nri != null) {
// handleReleaseNetworkRequestWithIntent() paths don't apply to multilayer requests.
ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequestWithIntent");
handleReleaseNetworkRequest(
nri.mRequests.get(0),
callingUid,
/* callOnUnavailable */ false);
}
}
// Determines whether the network is the best (or could become the best, if it validated), for
// none of a particular type of NetworkRequests. The type of NetworkRequests considered depends
// on the value of reason:
//
// - UnneededFor.TEARDOWN: non-listen NetworkRequests. If a network is unneeded for this reason,
// then it should be torn down.
// - UnneededFor.LINGER: foreground NetworkRequests. If a network is unneeded for this reason,
// then it should be lingered.
private boolean unneeded(NetworkAgentInfo nai, UnneededFor reason) {
ensureRunningOnConnectivityServiceThread();
if (!nai.everConnected || nai.isVPN() || nai.isInactive()
|| nai.getScore().getKeepConnectedReason() != NetworkScore.KEEP_CONNECTED_NONE) {
return false;
}
final int numRequests;
switch (reason) {
case TEARDOWN:
numRequests = nai.numRequestNetworkRequests();
break;
case LINGER:
numRequests = nai.numForegroundNetworkRequests();
break;
default:
Log.wtf(TAG, "Invalid reason. Cannot happen.");
return true;
}
if (numRequests > 0) return false;
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
if (reason == UnneededFor.LINGER
&& !nri.isMultilayerRequest()
&& nri.mRequests.get(0).isBackgroundRequest()) {
// Background requests don't affect lingering.
continue;
}
if (isNetworkPotentialSatisfier(nai, nri)) {
return false;
}
}
return true;
}
private boolean isNetworkPotentialSatisfier(
@NonNull final NetworkAgentInfo candidate, @NonNull final NetworkRequestInfo nri) {
// listen requests won't keep up a network satisfying it. If this is not a multilayer
// request, return immediately. For multilayer requests, check to see if any of the
// multilayer requests may have a potential satisfier.
if (!nri.isMultilayerRequest() && (nri.mRequests.get(0).isListen()
|| nri.mRequests.get(0).isListenForBest())) {
return false;
}
for (final NetworkRequest req : nri.mRequests) {
// This multilayer listen request is satisfied therefore no further requests need to be
// evaluated deeming this network not a potential satisfier.
if ((req.isListen() || req.isListenForBest()) && nri.getActiveRequest() == req) {
return false;
}
// As non-multilayer listen requests have already returned, the below would only happen
// for a multilayer request therefore continue to the next request if available.
if (req.isListen() || req.isListenForBest()) {
continue;
}
// If this Network is already the highest scoring Network for a request, or if
// there is hope for it to become one if it validated, then it is needed.
if (candidate.satisfies(req)) {
// As soon as a network is found that satisfies a request, return. Specifically for
// multilayer requests, returning as soon as a NetworkAgentInfo satisfies a request
// is important so as to not evaluate lower priority requests further in
// nri.mRequests.
final NetworkAgentInfo champion = req.equals(nri.getActiveRequest())
? nri.getSatisfier() : null;
// Note that this catches two important cases:
// 1. Unvalidated cellular will not be reaped when unvalidated WiFi
// is currently satisfying the request. This is desirable when
// cellular ends up validating but WiFi does not.
// 2. Unvalidated WiFi will not be reaped when validated cellular
// is currently satisfying the request. This is desirable when
// WiFi ends up validating and out scoring cellular.
return mNetworkRanker.mightBeat(req, champion, candidate.getValidatedScoreable());
}
}
return false;
}
private NetworkRequestInfo getNriForAppRequest(
NetworkRequest request, int callingUid, String requestedOperation) {
// Looking up the app passed param request in mRequests isn't possible since it may return
// null for a request managed by a per-app default. Therefore use getNriForAppRequest() to
// do the lookup since that will also find per-app default managed requests.
// Additionally, this lookup needs to be relatively fast (hence the lookup optimization)
// to avoid potential race conditions when validating a package->uid mapping when sending
// the callback on the very low-chance that an application shuts down prior to the callback
// being sent.
final NetworkRequestInfo nri = mNetworkRequests.get(request) != null
? mNetworkRequests.get(request) : getNriForAppRequest(request);
if (nri != null) {
if (Process.SYSTEM_UID != callingUid && nri.mUid != callingUid) {
log(String.format("UID %d attempted to %s for unowned request %s",
callingUid, requestedOperation, nri));
return null;
}
}
return nri;
}
private void ensureNotMultilayerRequest(@NonNull final NetworkRequestInfo nri,
final String callingMethod) {
if (nri.isMultilayerRequest()) {
throw new IllegalStateException(
callingMethod + " does not support multilayer requests.");
}
}
private void handleTimedOutNetworkRequest(@NonNull final NetworkRequestInfo nri) {
ensureRunningOnConnectivityServiceThread();
// handleTimedOutNetworkRequest() is part of the requestNetwork() flow which works off of a
// single NetworkRequest and thus does not apply to multilayer requests.
ensureNotMultilayerRequest(nri, "handleTimedOutNetworkRequest");
if (mNetworkRequests.get(nri.mRequests.get(0)) == null) {
return;
}
if (nri.isBeingSatisfied()) {
return;
}
if (VDBG || (DBG && nri.mRequests.get(0).isRequest())) {
log("releasing " + nri.mRequests.get(0) + " (timeout)");
}
handleRemoveNetworkRequest(nri);
callCallbackForRequest(
nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
}
private void handleReleaseNetworkRequest(@NonNull final NetworkRequest request,
final int callingUid,
final boolean callOnUnavailable) {
final NetworkRequestInfo nri =
getNriForAppRequest(request, callingUid, "release NetworkRequest");
if (nri == null) {
return;
}
if (VDBG || (DBG && request.isRequest())) {
log("releasing " + request + " (release request)");
}
handleRemoveNetworkRequest(nri);
if (callOnUnavailable) {
callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
}
}
private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
ensureRunningOnConnectivityServiceThread();
for (final NetworkRequest req : nri.mRequests) {
if (null == mNetworkRequests.remove(req)) {
logw("Attempted removal of untracked request " + req + " for nri " + nri);
continue;
}
if (req.isListen()) {
removeListenRequestFromNetworks(req);
}
}
nri.unlinkDeathRecipient();
if (mDefaultNetworkRequests.remove(nri)) {
// If this request was one of the defaults, then the UID rules need to be updated
// WARNING : if the app(s) for which this network request is the default are doing
// traffic, this will kill their connected sockets, even if an equivalent request
// is going to be reinstated right away ; unconnected traffic will go on the default
// until the new default is set, which will happen very soon.
// TODO : The only way out of this is to diff old defaults and new defaults, and only
// remove ranges for those requests that won't have a replacement
final NetworkAgentInfo satisfier = nri.getSatisfier();
if (null != satisfier) {
try {
mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
satisfier.network.getNetId(),
toUidRangeStableParcels(nri.getUids()),
nri.getPreferenceOrderForNetd()));
} catch (RemoteException e) {
loge("Exception setting network preference default network", e);
}
}
}
nri.decrementRequestCount();
mNetworkRequestInfoLogs.log("RELEASE " + nri);
checkNrisConsistency(nri);
if (null != nri.getActiveRequest()) {
if (!nri.getActiveRequest().isListen()) {
removeSatisfiedNetworkRequestFromNetwork(nri);
}
}
// For all outstanding offers, cancel any of the layers of this NRI that used to be
// needed for this offer.
for (final NetworkOfferInfo noi : mNetworkOffers) {
for (final NetworkRequest req : nri.mRequests) {
if (req.isRequest() && noi.offer.neededFor(req)) {
noi.offer.onNetworkUnneeded(req);
}
}
}
}
private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
for (final NetworkRequestInfo nri : nris) {
if (mDefaultRequest == nri) {
// Make sure we never remove the default request.
continue;
}
handleRemoveNetworkRequest(nri);
}
}
private void removeListenRequestFromNetworks(@NonNull final NetworkRequest req) {
// listens don't have a singular affected Network. Check all networks to see
// if this listen request applies and remove it.
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
nai.removeRequest(req.requestId);
if (req.networkCapabilities.hasSignalStrength()
&& nai.satisfiesImmutableCapabilitiesOf(req)) {
updateSignalStrengthThresholds(nai, "RELEASE", req);
}
}
}
/**
* Remove a NetworkRequestInfo's satisfied request from its 'satisfier' (NetworkAgentInfo) and
* manage the necessary upkeep (linger, teardown networks, etc.) when doing so.
* @param nri the NetworkRequestInfo to disassociate from its current NetworkAgentInfo
*/
private void removeSatisfiedNetworkRequestFromNetwork(@NonNull final NetworkRequestInfo nri) {
boolean wasKept = false;
final NetworkAgentInfo nai = nri.getSatisfier();
if (nai != null) {
final int requestLegacyType = nri.getActiveRequest().legacyType;
final boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
nai.removeRequest(nri.getActiveRequest().requestId);
if (VDBG || DDBG) {
log(" Removing from current network " + nai.toShortString()
+ ", leaving " + nai.numNetworkRequests() + " requests.");
}
// If there are still lingered requests on this network, don't tear it down,
// but resume lingering instead.
final long now = SystemClock.elapsedRealtime();
if (updateInactivityState(nai, now)) {
notifyNetworkLosing(nai, now);
}
if (unneeded(nai, UnneededFor.TEARDOWN)) {
if (DBG) log("no live requests for " + nai.toShortString() + "; disconnecting");
teardownUnneededNetwork(nai);
} else {
wasKept = true;
}
if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
// Went from foreground to background.
updateCapabilitiesForNetwork(nai);
}
// Maintain the illusion. When this request arrived, we might have pretended
// that a network connected to serve it, even though the network was already
// connected. Now that this request has gone away, we might have to pretend
// that the network disconnected. LegacyTypeTracker will generate that
// phantom disconnect for this type.
if (requestLegacyType != TYPE_NONE) {
boolean doRemove = true;
if (wasKept) {
// check if any of the remaining requests for this network are for the
// same legacy type - if so, don't remove the nai
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest otherRequest = nai.requestAt(i);
if (otherRequest.legacyType == requestLegacyType
&& otherRequest.isRequest()) {
if (DBG) log(" still have other legacy request - leaving");
doRemove = false;
}
}
}
if (doRemove) {
mLegacyTypeTracker.remove(requestLegacyType, nai, false);
}
}
}
}
private PerUidCounter getRequestCounter(NetworkRequestInfo nri) {
return checkAnyPermissionOf(
nri.mPid, nri.mUid, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
? mSystemNetworkRequestCounter : mNetworkRequestCounter;
}
@Override
public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
enforceNetworkStackSettingsOrSetup();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
encodeBool(accept), encodeBool(always), network));
}
@Override
public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) {
enforceNetworkStackSettingsOrSetup();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY,
encodeBool(accept), encodeBool(always), network));
}
@Override
public void setAvoidUnvalidated(Network network) {
enforceNetworkStackSettingsOrSetup();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_AVOID_UNVALIDATED, network));
}
@Override
public void setTestAllowBadWifiUntil(long timeMs) {
enforceSettingsPermission();
if (!Build.isDebuggable()) {
throw new IllegalStateException("Does not support in non-debuggable build");
}
if (timeMs > System.currentTimeMillis() + MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS) {
throw new IllegalArgumentException("It should not exceed "
+ MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS + "ms from now");
}
mHandler.sendMessage(
mHandler.obtainMessage(EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL, timeMs));
}
private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
if (DBG) log("handleSetAcceptUnvalidated network=" + network +
" accept=" + accept + " always=" + always);
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) {
// Nothing to do.
return;
}
if (nai.everValidated) {
// The network validated while the dialog box was up. Take no action.
return;
}
if (!nai.networkAgentConfig.explicitlySelected) {
Log.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
}
if (accept != nai.networkAgentConfig.acceptUnvalidated) {
nai.networkAgentConfig.acceptUnvalidated = accept;
// If network becomes partial connectivity and user already accepted to use this
// network, we should respect the user's option and don't need to popup the
// PARTIAL_CONNECTIVITY notification to user again.
nai.networkAgentConfig.acceptPartialConnectivity = accept;
nai.updateScoreForNetworkAgentUpdate();
rematchAllNetworksAndRequests();
}
if (always) {
nai.onSaveAcceptUnvalidated(accept);
}
if (!accept) {
// Tell the NetworkAgent to not automatically reconnect to the network.
nai.onPreventAutomaticReconnect();
// Teardown the network.
teardownUnneededNetwork(nai);
}
}
private void handleSetAcceptPartialConnectivity(Network network, boolean accept,
boolean always) {
if (DBG) {
log("handleSetAcceptPartialConnectivity network=" + network + " accept=" + accept
+ " always=" + always);
}
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) {
// Nothing to do.
return;
}
if (nai.lastValidated) {
// The network validated while the dialog box was up. Take no action.
return;
}
if (accept != nai.networkAgentConfig.acceptPartialConnectivity) {
nai.networkAgentConfig.acceptPartialConnectivity = accept;
}
// TODO: Use the current design or save the user choice into IpMemoryStore.
if (always) {
nai.onSaveAcceptUnvalidated(accept);
}
if (!accept) {
// Tell the NetworkAgent to not automatically reconnect to the network.
nai.onPreventAutomaticReconnect();
// Tear down the network.
teardownUnneededNetwork(nai);
} else {
// Inform NetworkMonitor that partial connectivity is acceptable. This will likely
// result in a partial connectivity result which will be processed by
// maybeHandleNetworkMonitorMessage.
//
// TODO: NetworkMonitor does not refer to the "never ask again" bit. The bit is stored
// per network. Therefore, NetworkMonitor may still do https probe.
nai.networkMonitor().setAcceptPartialConnectivity();
}
}
private void handleSetAvoidUnvalidated(Network network) {
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null || nai.lastValidated) {
// Nothing to do. The network either disconnected or revalidated.
return;
}
if (!nai.avoidUnvalidated) {
nai.avoidUnvalidated = true;
nai.updateScoreForNetworkAgentUpdate();
rematchAllNetworksAndRequests();
}
}
private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
if (VDBG) log("scheduleUnvalidatedPrompt " + nai.network);
mHandler.sendMessageDelayed(
mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
PROMPT_UNVALIDATED_DELAY_MS);
}
@Override
public void startCaptivePortalApp(Network network) {
enforceNetworkStackOrSettingsPermission();
mHandler.post(() -> {
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return;
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return;
nai.networkMonitor().launchCaptivePortalApp();
});
}
/**
* NetworkStack endpoint to start the captive portal app. The NetworkStack needs to use this
* endpoint as it does not have INTERACT_ACROSS_USERS_FULL itself.
* @param network Network on which the captive portal was detected.
* @param appExtras Bundle to use as intent extras for the captive portal application.
* Must be treated as opaque to avoid preventing the captive portal app to
* update its arguments.
*/
@Override
public void startCaptivePortalAppInternal(Network network, Bundle appExtras) {
mContext.enforceCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
"ConnectivityService");
final Intent appIntent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
appIntent.putExtras(appExtras);
appIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
new CaptivePortal(new CaptivePortalImpl(network).asBinder()));
appIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
final long token = Binder.clearCallingIdentity();
try {
mContext.startActivityAsUser(appIntent, UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private class CaptivePortalImpl extends ICaptivePortal.Stub {
private final Network mNetwork;
private CaptivePortalImpl(Network network) {
mNetwork = network;
}
@Override
public void appResponse(final int response) {
if (response == CaptivePortal.APP_RETURN_WANTED_AS_IS) {
enforceSettingsPermission();
}
final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork);
if (nm == null) return;
nm.notifyCaptivePortalAppFinished(response);
}
@Override
public void appRequest(final int request) {
final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork);
if (nm == null) return;
if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
checkNetworkStackPermission();
nm.forceReevaluation(mDeps.getCallingUid());
}
}
@Nullable
private NetworkMonitorManager getNetworkMonitorManager(final Network network) {
// getNetworkAgentInfoForNetwork is thread-safe
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return null;
// nai.networkMonitor() is thread-safe
return nai.networkMonitor();
}
}
public boolean avoidBadWifi() {
return mMultinetworkPolicyTracker.getAvoidBadWifi();
}
/**
* Return whether the device should maintain continuous, working connectivity by switching away
* from WiFi networks having no connectivity.
* @see MultinetworkPolicyTracker#getAvoidBadWifi()
*/
public boolean shouldAvoidBadWifi() {
if (!checkNetworkStackPermission()) {
throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission");
}
return avoidBadWifi();
}
private void updateAvoidBadWifi() {
ensureRunningOnConnectivityServiceThread();
// Agent info scores and offer scores depend on whether cells yields to bad wifi.
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
nai.updateScoreForNetworkAgentUpdate();
}
// UpdateOfferScore will update mNetworkOffers inline, so make a copy first.
final ArrayList<NetworkOfferInfo> offersToUpdate = new ArrayList<>(mNetworkOffers);
for (final NetworkOfferInfo noi : offersToUpdate) {
updateOfferScore(noi.offer);
}
rematchAllNetworksAndRequests();
}
// TODO: Evaluate whether this is of interest to other consumers of
// MultinetworkPolicyTracker and worth moving out of here.
private void dumpAvoidBadWifiSettings(IndentingPrintWriter pw) {
final boolean configRestrict = mMultinetworkPolicyTracker.configRestrictsAvoidBadWifi();
if (!configRestrict) {
pw.println("Bad Wi-Fi avoidance: unrestricted");
return;
}
pw.println("Bad Wi-Fi avoidance: " + avoidBadWifi());
pw.increaseIndent();
pw.println("Config restrict: " + configRestrict);
final String value = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
String description;
// Can't use a switch statement because strings are legal case labels, but null is not.
if ("0".equals(value)) {
description = "get stuck";
} else if (value == null) {
description = "prompt";
} else if ("1".equals(value)) {
description = "avoid";
} else {
description = value + " (?)";
}
pw.println("User setting: " + description);
pw.println("Network overrides:");
pw.increaseIndent();
for (NetworkAgentInfo nai : networksSortedById()) {
if (nai.avoidUnvalidated) {
pw.println(nai.toShortString());
}
}
pw.decreaseIndent();
pw.decreaseIndent();
}
// TODO: This method is copied from TetheringNotificationUpdater. Should have a utility class to
// unify the method.
private static @NonNull String getSettingsPackageName(@NonNull final PackageManager pm) {
final Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS);
final ComponentName settingsComponent = settingsIntent.resolveActivity(pm);
return settingsComponent != null
? settingsComponent.getPackageName() : "com.android.settings";
}
private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) {
final String action;
final boolean highPriority;
switch (type) {
case NO_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
// High priority because it is only displayed for explicitly selected networks.
highPriority = true;
break;
case PRIVATE_DNS_BROKEN:
action = Settings.ACTION_WIRELESS_SETTINGS;
// High priority because we should let user know why there is no internet.
highPriority = true;
break;
case LOST_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION;
// High priority because it could help the user avoid unexpected data usage.
highPriority = true;
break;
case PARTIAL_CONNECTIVITY:
action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY;
// Don't bother the user with a high-priority notification if the network was not
// explicitly selected by the user.
highPriority = nai.networkAgentConfig.explicitlySelected;
break;
default:
Log.wtf(TAG, "Unknown notification type " + type);
return;
}
Intent intent = new Intent(action);
if (type != NotificationType.PRIVATE_DNS_BROKEN) {
intent.putExtra(ConnectivityManager.EXTRA_NETWORK, nai.network);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Some OEMs have their own Settings package. Thus, need to get the current using
// Settings package name instead of just use default name "com.android.settings".
final String settingsPkgName = getSettingsPackageName(mContext.getPackageManager());
intent.setClassName(settingsPkgName,
settingsPkgName + ".wifi.WifiNoInternetDialog");
}
PendingIntent pendingIntent = PendingIntent.getActivity(
mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
0 /* requestCode */,
intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mNotifier.showNotification(
nai.network.getNetId(), type, nai, null, pendingIntent, highPriority);
}
private boolean shouldPromptUnvalidated(NetworkAgentInfo nai) {
// Don't prompt if the network is validated, and don't prompt on captive portals
// because we're already prompting the user to sign in.
if (nai.everValidated || nai.everCaptivePortalDetected) {
return false;
}
// If a network has partial connectivity, always prompt unless the user has already accepted
// partial connectivity and selected don't ask again. This ensures that if the device
// automatically connects to a network that has partial Internet access, the user will
// always be able to use it, either because they've already chosen "don't ask again" or
// because we have prompt them.
if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) {
return true;
}
// If a network has no Internet access, only prompt if the network was explicitly selected
// and if the user has not already told us to use the network regardless of whether it
// validated or not.
if (nai.networkAgentConfig.explicitlySelected
&& !nai.networkAgentConfig.acceptUnvalidated) {
return true;
}
return false;
}
private void handlePromptUnvalidated(Network network) {
if (VDBG || DDBG) log("handlePromptUnvalidated " + network);
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null || !shouldPromptUnvalidated(nai)) {
return;
}
// Stop automatically reconnecting to this network in the future. Automatically connecting
// to a network that provides no or limited connectivity is not useful, because the user
// cannot use that network except through the notification shown by this method, and the
// notification is only shown if the network is explicitly selected by the user.
nai.onPreventAutomaticReconnect();
// TODO: Evaluate if it's needed to wait 8 seconds for triggering notification when
// NetworkMonitor detects the network is partial connectivity. Need to change the design to
// popup the notification immediately when the network is partial connectivity.
if (nai.partialConnectivity) {
showNetworkNotification(nai, NotificationType.PARTIAL_CONNECTIVITY);
} else {
showNetworkNotification(nai, NotificationType.NO_INTERNET);
}
}
private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
NetworkCapabilities nc = nai.networkCapabilities;
if (DBG) log("handleNetworkUnvalidated " + nai.toShortString() + " cap=" + nc);
if (!nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return;
}
if (mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
showNetworkNotification(nai, NotificationType.LOST_INTERNET);
}
}
@Override
public int getMultipathPreference(Network network) {
enforceAccessPermission();
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai != null && nai.networkCapabilities
.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
return ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED;
}
final NetworkPolicyManager netPolicyManager =
mContext.getSystemService(NetworkPolicyManager.class);
final long token = Binder.clearCallingIdentity();
final int networkPreference;
try {
networkPreference = netPolicyManager.getMultipathPreference(network);
} finally {
Binder.restoreCallingIdentity(token);
}
if (networkPreference != 0) {
return networkPreference;
}
return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
}
@Override
public NetworkRequest getDefaultRequest() {
return mDefaultRequest.mRequests.get(0);
}
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_EXPIRE_NET_TRANSITION_WAKELOCK:
case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: {
handleReleaseNetworkTransitionWakelock(msg.what);
break;
}
case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
mProxyTracker.loadDeprecatedGlobalHttpProxy();
break;
}
case EVENT_PROXY_HAS_CHANGED: {
final Pair<Network, ProxyInfo> arg = (Pair<Network, ProxyInfo>) msg.obj;
handleApplyDefaultProxy(arg.second);
break;
}
case EVENT_REGISTER_NETWORK_PROVIDER: {
handleRegisterNetworkProvider((NetworkProviderInfo) msg.obj);
break;
}
case EVENT_UNREGISTER_NETWORK_PROVIDER: {
handleUnregisterNetworkProvider((Messenger) msg.obj);
break;
}
case EVENT_REGISTER_NETWORK_OFFER: {
handleRegisterNetworkOffer((NetworkOffer) msg.obj);
break;
}
case EVENT_UNREGISTER_NETWORK_OFFER: {
final NetworkOfferInfo offer =
findNetworkOfferInfoByCallback((INetworkOfferCallback) msg.obj);
if (null != offer) {
handleUnregisterNetworkOffer(offer);
}
break;
}
case EVENT_REGISTER_NETWORK_AGENT: {
final Pair<NetworkAgentInfo, INetworkMonitor> arg =
(Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj;
handleRegisterNetworkAgent(arg.first, arg.second);
break;
}
case EVENT_REGISTER_NETWORK_REQUEST:
case EVENT_REGISTER_NETWORK_LISTENER: {
handleRegisterNetworkRequest((NetworkRequestInfo) msg.obj);
break;
}
case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT:
case EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT: {
handleRegisterNetworkRequestWithIntent(msg);
break;
}
case EVENT_TIMEOUT_NETWORK_REQUEST: {
NetworkRequestInfo nri = (NetworkRequestInfo) msg.obj;
handleTimedOutNetworkRequest(nri);
break;
}
case EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT: {
handleReleaseNetworkRequestWithIntent((PendingIntent) msg.obj, msg.arg1);
break;
}
case EVENT_RELEASE_NETWORK_REQUEST: {
handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
/* callOnUnavailable */ false);
break;
}
case EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE: {
handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
/* callOnUnavailable */ true);
break;
}
case EVENT_SET_ACCEPT_UNVALIDATED: {
Network network = (Network) msg.obj;
handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
break;
}
case EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY: {
Network network = (Network) msg.obj;
handleSetAcceptPartialConnectivity(network, toBool(msg.arg1),
toBool(msg.arg2));
break;
}
case EVENT_SET_AVOID_UNVALIDATED: {
handleSetAvoidUnvalidated((Network) msg.obj);
break;
}
case EVENT_PROMPT_UNVALIDATED: {
handlePromptUnvalidated((Network) msg.obj);
break;
}
case EVENT_CONFIGURE_ALWAYS_ON_NETWORKS: {
handleConfigureAlwaysOnNetworks();
break;
}
// Sent by KeepaliveTracker to process an app request on the state machine thread.
case NetworkAgent.CMD_START_SOCKET_KEEPALIVE: {
mKeepaliveTracker.handleStartKeepalive(msg);
break;
}
// Sent by KeepaliveTracker to process an app request on the state machine thread.
case NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE: {
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj);
int slot = msg.arg1;
int reason = msg.arg2;
mKeepaliveTracker.handleStopKeepalive(nai, slot, reason);
break;
}
case EVENT_REPORT_NETWORK_CONNECTIVITY: {
handleReportNetworkConnectivity((NetworkAgentInfo) msg.obj, msg.arg1,
toBool(msg.arg2));
break;
}
case EVENT_PRIVATE_DNS_SETTINGS_CHANGED:
handlePrivateDnsSettingsChanged();
break;
case EVENT_PRIVATE_DNS_VALIDATION_UPDATE:
handlePrivateDnsValidationUpdate(
(PrivateDnsValidationUpdate) msg.obj);
break;
case EVENT_UID_BLOCKED_REASON_CHANGED:
handleUidBlockedReasonChanged(msg.arg1, msg.arg2);
break;
case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
break;
case EVENT_SET_OEM_NETWORK_PREFERENCE: {
final Pair<OemNetworkPreferences, IOnCompleteListener> arg =
(Pair<OemNetworkPreferences, IOnCompleteListener>) msg.obj;
handleSetOemNetworkPreference(arg.first, arg.second);
break;
}
case EVENT_SET_PROFILE_NETWORK_PREFERENCE: {
final Pair<List<ProfileNetworkPreferenceList.Preference>,
IOnCompleteListener> arg =
(Pair<List<ProfileNetworkPreferenceList.Preference>,
IOnCompleteListener>) msg.obj;
handleSetProfileNetworkPreference(arg.first, arg.second);
break;
}
case EVENT_REPORT_NETWORK_ACTIVITY:
mNetworkActivityTracker.handleReportNetworkActivity();
break;
case EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED:
handleMobileDataPreferredUidsChanged();
break;
case EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL:
final long timeMs = ((Long) msg.obj).longValue();
mMultinetworkPolicyTracker.setTestAllowBadWifiUntil(timeMs);
break;
case EVENT_INGRESS_RATE_LIMIT_CHANGED:
handleIngressRateLimitChanged();
break;
}
}
}
@Override
@Deprecated
public int getLastTetherError(String iface) {
final TetheringManager tm = (TetheringManager) mContext.getSystemService(
Context.TETHERING_SERVICE);
return tm.getLastTetherError(iface);
}
@Override
@Deprecated
public String[] getTetherableIfaces() {
final TetheringManager tm = (TetheringManager) mContext.getSystemService(
Context.TETHERING_SERVICE);
return tm.getTetherableIfaces();
}
@Override
@Deprecated
public String[] getTetheredIfaces() {
final TetheringManager tm = (TetheringManager) mContext.getSystemService(
Context.TETHERING_SERVICE);
return tm.getTetheredIfaces();
}
@Override
@Deprecated
public String[] getTetheringErroredIfaces() {
final TetheringManager tm = (TetheringManager) mContext.getSystemService(
Context.TETHERING_SERVICE);
return tm.getTetheringErroredIfaces();
}
@Override
@Deprecated
public String[] getTetherableUsbRegexs() {
final TetheringManager tm = (TetheringManager) mContext.getSystemService(
Context.TETHERING_SERVICE);
return tm.getTetherableUsbRegexs();
}
@Override
@Deprecated
public String[] getTetherableWifiRegexs() {
final TetheringManager tm = (TetheringManager) mContext.getSystemService(
Context.TETHERING_SERVICE);
return tm.getTetherableWifiRegexs();
}
// Called when we lose the default network and have no replacement yet.
// This will automatically be cleared after X seconds or a new default network
// becomes CONNECTED, whichever happens first. The timer is started by the
// first caller and not restarted by subsequent callers.
private void ensureNetworkTransitionWakelock(String forWhom) {
synchronized (this) {
if (mNetTransitionWakeLock.isHeld()) {
return;
}
mNetTransitionWakeLock.acquire();
mLastWakeLockAcquireTimestamp = SystemClock.elapsedRealtime();
mTotalWakelockAcquisitions++;
}
mWakelockLogs.log("ACQUIRE for " + forWhom);
Message msg = mHandler.obtainMessage(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
final int lockTimeout = mResources.get().getInteger(
R.integer.config_networkTransitionTimeout);
mHandler.sendMessageDelayed(msg, lockTimeout);
}
// Called when we gain a new default network to release the network transition wakelock in a
// second, to allow a grace period for apps to reconnect over the new network. Pending expiry
// message is cancelled.
private void scheduleReleaseNetworkTransitionWakelock() {
synchronized (this) {
if (!mNetTransitionWakeLock.isHeld()) {
return; // expiry message released the lock first.
}
}
// Cancel self timeout on wakelock hold.
mHandler.removeMessages(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
Message msg = mHandler.obtainMessage(EVENT_CLEAR_NET_TRANSITION_WAKELOCK);
mHandler.sendMessageDelayed(msg, 1000);
}
// Called when either message of ensureNetworkTransitionWakelock or
// scheduleReleaseNetworkTransitionWakelock is processed.
private void handleReleaseNetworkTransitionWakelock(int eventId) {
String event = eventName(eventId);
synchronized (this) {
if (!mNetTransitionWakeLock.isHeld()) {
mWakelockLogs.log(String.format("RELEASE: already released (%s)", event));
Log.w(TAG, "expected Net Transition WakeLock to be held");
return;
}
mNetTransitionWakeLock.release();
long lockDuration = SystemClock.elapsedRealtime() - mLastWakeLockAcquireTimestamp;
mTotalWakelockDurationMs += lockDuration;
mMaxWakelockDurationMs = Math.max(mMaxWakelockDurationMs, lockDuration);
mTotalWakelockReleases++;
}
mWakelockLogs.log(String.format("RELEASE (%s)", event));
}
// 100 percent is full good, 0 is full bad.
@Override
public void reportInetCondition(int networkType, int percentage) {
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
if (nai == null) return;
reportNetworkConnectivity(nai.network, percentage > 50);
}
@Override
public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
enforceAccessPermission();
enforceInternetPermission();
final int uid = mDeps.getCallingUid();
final int connectivityInfo = encodeBool(hasConnectivity);
final NetworkAgentInfo nai;
if (network == null) {
nai = getDefaultNetwork();
} else {
nai = getNetworkAgentInfoForNetwork(network);
}
mHandler.sendMessage(
mHandler.obtainMessage(
EVENT_REPORT_NETWORK_CONNECTIVITY, uid, connectivityInfo, nai));
}
private void handleReportNetworkConnectivity(
@Nullable NetworkAgentInfo nai, int uid, boolean hasConnectivity) {
if (nai == null
|| nai != getNetworkAgentInfoForNetwork(nai.network)
|| nai.networkInfo.getState() == NetworkInfo.State.DISCONNECTED) {
return;
}
// Revalidate if the app report does not match our current validated state.
if (hasConnectivity == nai.lastValidated) {
mConnectivityDiagnosticsHandler.sendMessage(
mConnectivityDiagnosticsHandler.obtainMessage(
ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED,
new ReportedNetworkConnectivityInfo(
hasConnectivity, false /* isNetworkRevalidating */, uid, nai)));
return;
}
if (DBG) {
int netid = nai.network.getNetId();
log("reportNetworkConnectivity(" + netid + ", " + hasConnectivity + ") by " + uid);
}
// Validating a network that has not yet connected could result in a call to
// rematchNetworkAndRequests() which is not meant to work on such networks.
if (!nai.everConnected) {
return;
}
final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
if (isNetworkWithCapabilitiesBlocked(nc, uid, false)) {
return;
}
// Send CONNECTIVITY_REPORTED event before re-validating the Network to force an ordering of
// ConnDiags events. This ensures that #onNetworkConnectivityReported() will be called
// before #onConnectivityReportAvailable(), which is called once Network evaluation is
// completed.
mConnectivityDiagnosticsHandler.sendMessage(
mConnectivityDiagnosticsHandler.obtainMessage(
ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED,
new ReportedNetworkConnectivityInfo(
hasConnectivity, true /* isNetworkRevalidating */, uid, nai)));
nai.networkMonitor().forceReevaluation(uid);
}
// TODO: call into netd.
private boolean queryUserAccess(int uid, Network network) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return false;
// Any UID can use its default network.
if (nai == getDefaultNetworkForUid(uid)) return true;
// Privileged apps can use any network.
if (mPermissionMonitor.hasRestrictedNetworksPermission(uid)) {
return true;
}
// An unprivileged UID can use a VPN iff the VPN applies to it.
if (nai.isVPN()) {
return nai.networkCapabilities.appliesToUid(uid);
}
// An unprivileged UID can bypass the VPN that applies to it only if it can protect its
// sockets, i.e., if it is the owner.
final NetworkAgentInfo vpn = getVpnForUid(uid);
if (vpn != null && !vpn.networkAgentConfig.allowBypass
&& uid != vpn.networkCapabilities.getOwnerUid()) {
return false;
}
// The UID's permission must be at least sufficient for the network. Since the restricted
// permission was already checked above, that just leaves background networks.
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) {
return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid);
}
// Unrestricted network. Anyone gets to use it.
return true;
}
/**
* Returns information about the proxy a certain network is using. If given a null network, it
* it will return the proxy for the bound network for the caller app or the default proxy if
* none.
*
* @param network the network we want to get the proxy information for.
* @return Proxy information if a network has a proxy configured, or otherwise null.
*/
@Override
public ProxyInfo getProxyForNetwork(Network network) {
final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy();
if (globalProxy != null) return globalProxy;
if (network == null) {
// Get the network associated with the calling UID.
final Network activeNetwork = getActiveNetworkForUidInternal(mDeps.getCallingUid(),
true);
if (activeNetwork == null) {
return null;
}
return getLinkPropertiesProxyInfo(activeNetwork);
} else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network, this)) {
// Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
// caller may not have.
return getLinkPropertiesProxyInfo(network);
}
// No proxy info available if the calling UID does not have network access.
return null;
}
private ProxyInfo getLinkPropertiesProxyInfo(Network network) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return null;
synchronized (nai) {
final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy();
return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy);
}
}
@Override
public void setGlobalProxy(@Nullable final ProxyInfo proxyProperties) {
PermissionUtils.enforceNetworkStackPermission(mContext);
mProxyTracker.setGlobalProxy(proxyProperties);
}
@Override
@Nullable
public ProxyInfo getGlobalProxy() {
return mProxyTracker.getGlobalProxy();
}
private void handleApplyDefaultProxy(ProxyInfo proxy) {
if (proxy != null && TextUtils.isEmpty(proxy.getHost())
&& Uri.EMPTY.equals(proxy.getPacFileUrl())) {
proxy = null;
}
mProxyTracker.setDefaultProxy(proxy);
}
// If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called
// when any network changes proxy.
// TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a
// multi-network world where an app might be bound to a non-default network.
private void updateProxy(LinkProperties newLp, LinkProperties oldLp) {
ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy();
ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy();
if (!ProxyTracker.proxyInfoEqual(newProxyInfo, oldProxyInfo)) {
mProxyTracker.sendProxyBroadcast();
}
}
private static class SettingsObserver extends ContentObserver {
final private HashMap<Uri, Integer> mUriEventMap;
final private Context mContext;
final private Handler mHandler;
SettingsObserver(Context context, Handler handler) {
super(null);
mUriEventMap = new HashMap<>();
mContext = context;
mHandler = handler;
}
void observe(Uri uri, int what) {
mUriEventMap.put(uri, what);
final ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(uri, false, this);
}
@Override
public void onChange(boolean selfChange) {
Log.wtf(TAG, "Should never be reached.");
}
@Override
public void onChange(boolean selfChange, Uri uri) {
final Integer what = mUriEventMap.get(uri);
if (what != null) {
mHandler.obtainMessage(what).sendToTarget();
} else {
loge("No matching event to send for URI=" + uri);
}
}
}
private static void log(String s) {
Log.d(TAG, s);
}
private static void logw(String s) {
Log.w(TAG, s);
}
private static void logwtf(String s) {
Log.wtf(TAG, s);
}
private static void logwtf(String s, Throwable t) {
Log.wtf(TAG, s, t);
}
private static void loge(String s) {
Log.e(TAG, s);
}
private static void loge(String s, Throwable t) {
Log.e(TAG, s, t);
}
/**
* Return the information of all ongoing VPNs.
*
* <p>This method is used to update NetworkStatsService.
*
* <p>Must be called on the handler thread.
*/
private UnderlyingNetworkInfo[] getAllVpnInfo() {
ensureRunningOnConnectivityServiceThread();
if (mLockdownEnabled) {
return new UnderlyingNetworkInfo[0];
}
List<UnderlyingNetworkInfo> infoList = new ArrayList<>();
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
UnderlyingNetworkInfo info = createVpnInfo(nai);
if (info != null) {
infoList.add(info);
}
}
return infoList.toArray(new UnderlyingNetworkInfo[infoList.size()]);
}
/**
* @return VPN information for accounting, or null if we can't retrieve all required
* information, e.g underlying ifaces.
*/
private UnderlyingNetworkInfo createVpnInfo(NetworkAgentInfo nai) {
Network[] underlyingNetworks = nai.declaredUnderlyingNetworks;
// see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
// the underlyingNetworks list.
// TODO: stop using propagateUnderlyingCapabilities here, for example, by always
// initializing NetworkAgentInfo#declaredUnderlyingNetworks to an empty array.
if (underlyingNetworks == null && nai.propagateUnderlyingCapabilities()) {
final NetworkAgentInfo defaultNai = getDefaultNetworkForUid(
nai.networkCapabilities.getOwnerUid());
if (defaultNai != null) {
underlyingNetworks = new Network[] { defaultNai.network };
}
}
if (CollectionUtils.isEmpty(underlyingNetworks)) return null;
List<String> interfaces = new ArrayList<>();
for (Network network : underlyingNetworks) {
NetworkAgentInfo underlyingNai = getNetworkAgentInfoForNetwork(network);
if (underlyingNai == null) continue;
LinkProperties lp = underlyingNai.linkProperties;
for (String iface : lp.getAllInterfaceNames()) {
if (!TextUtils.isEmpty(iface)) {
interfaces.add(iface);
}
}
}
if (interfaces.isEmpty()) return null;
// Must be non-null or NetworkStatsService will crash.
// Cannot happen in production code because Vpn only registers the NetworkAgent after the
// tun or ipsec interface is created.
// TODO: Remove this check.
if (nai.linkProperties.getInterfaceName() == null) return null;
return new UnderlyingNetworkInfo(nai.networkCapabilities.getOwnerUid(),
nai.linkProperties.getInterfaceName(), interfaces);
}
// TODO This needs to be the default network that applies to the NAI.
private Network[] underlyingNetworksOrDefault(final int ownerUid,
Network[] underlyingNetworks) {
final Network defaultNetwork = getNetwork(getDefaultNetworkForUid(ownerUid));
if (underlyingNetworks == null && defaultNetwork != null) {
// null underlying networks means to track the default.
underlyingNetworks = new Network[] { defaultNetwork };
}
return underlyingNetworks;
}
// Returns true iff |network| is an underlying network of |nai|.
private boolean hasUnderlyingNetwork(NetworkAgentInfo nai, Network network) {
// TODO: support more than one level of underlying networks, either via a fixed-depth search
// (e.g., 2 levels of underlying networks), or via loop detection, or....
if (!nai.propagateUnderlyingCapabilities()) return false;
final Network[] underlying = underlyingNetworksOrDefault(
nai.networkCapabilities.getOwnerUid(), nai.declaredUnderlyingNetworks);
return CollectionUtils.contains(underlying, network);
}
/**
* Recompute the capabilities for any networks that had a specific network as underlying.
*
* When underlying networks change, such networks may have to update capabilities to reflect
* things like the metered bit, their transports, and so on. The capabilities are calculated
* immediately. This method runs on the ConnectivityService thread.
*/
private void propagateUnderlyingNetworkCapabilities(Network updatedNetwork) {
ensureRunningOnConnectivityServiceThread();
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
if (updatedNetwork == null || hasUnderlyingNetwork(nai, updatedNetwork)) {
updateCapabilitiesForNetwork(nai);
}
}
}
private boolean isUidBlockedByVpn(int uid, List<UidRange> blockedUidRanges) {
// Determine whether this UID is blocked because of always-on VPN lockdown. If a VPN applies
// to the UID, then the UID is not blocked because always-on VPN lockdown applies only when
// a VPN is not up.
final NetworkAgentInfo vpnNai = getVpnForUid(uid);
if (vpnNai != null && !vpnNai.networkAgentConfig.allowBypass) return false;
for (UidRange range : blockedUidRanges) {
if (range.contains(uid)) return true;
}
return false;
}
@Override
public void setRequireVpnForUids(boolean requireVpn, UidRange[] ranges) {
enforceNetworkStackOrSettingsPermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_REQUIRE_VPN_FOR_UIDS,
encodeBool(requireVpn), 0 /* arg2 */, ranges));
}
private void handleSetRequireVpnForUids(boolean requireVpn, UidRange[] ranges) {
if (DBG) {
Log.d(TAG, "Setting VPN " + (requireVpn ? "" : "not ") + "required for UIDs: "
+ Arrays.toString(ranges));
}
// Cannot use a Set since the list of UID ranges might contain duplicates.
final List<UidRange> newVpnBlockedUidRanges = new ArrayList(mVpnBlockedUidRanges);
for (int i = 0; i < ranges.length; i++) {
if (requireVpn) {
newVpnBlockedUidRanges.add(ranges[i]);
} else {
newVpnBlockedUidRanges.remove(ranges[i]);
}
}
try {
mNetd.networkRejectNonSecureVpn(requireVpn, toUidRangeStableParcels(ranges));
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "setRequireVpnForUids(" + requireVpn + ", "
+ Arrays.toString(ranges) + "): netd command failed: " + e);
}
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
final boolean curMetered = nai.networkCapabilities.isMetered();
maybeNotifyNetworkBlocked(nai, curMetered, curMetered,
mVpnBlockedUidRanges, newVpnBlockedUidRanges);
}
mVpnBlockedUidRanges = newVpnBlockedUidRanges;
}
@Override
public void setLegacyLockdownVpnEnabled(boolean enabled) {
enforceNetworkStackOrSettingsPermission();
mHandler.post(() -> mLockdownEnabled = enabled);
}
private boolean isLegacyLockdownNai(NetworkAgentInfo nai) {
return mLockdownEnabled
&& getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY
&& nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID);
}
private NetworkAgentInfo getLegacyLockdownNai() {
if (!mLockdownEnabled) {
return null;
}
// The legacy lockdown VPN always only applies to userId 0.
final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID);
if (nai == null || !isLegacyLockdownNai(nai)) return null;
// The legacy lockdown VPN must always have exactly one underlying network.
// This code may run on any thread and declaredUnderlyingNetworks may change, so store it in
// a local variable. There is no need to make a copy because its contents cannot change.
final Network[] underlying = nai.declaredUnderlyingNetworks;
if (underlying == null || underlying.length != 1) {
return null;
}
// The legacy lockdown VPN always uses the default network.
// If the VPN's underlying network is no longer the current default network, it means that
// the default network has just switched, and the VPN is about to disconnect.
// Report that the VPN is not connected, so the state of NetworkInfo objects overwritten
// by filterForLegacyLockdown will be set to CONNECTING and not CONNECTED.
final NetworkAgentInfo defaultNetwork = getDefaultNetwork();
if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) {
return null;
}
return nai;
};
// TODO: move all callers to filterForLegacyLockdown and delete this method.
// This likely requires making sendLegacyNetworkBroadcast take a NetworkInfo object instead of
// just a DetailedState object.
private DetailedState getLegacyLockdownState(DetailedState origState) {
if (origState != DetailedState.CONNECTED) {
return origState;
}
return (mLockdownEnabled && getLegacyLockdownNai() == null)
? DetailedState.CONNECTING
: DetailedState.CONNECTED;
}
private void filterForLegacyLockdown(NetworkInfo ni) {
if (!mLockdownEnabled || !ni.isConnected()) return;
// The legacy lockdown VPN replaces the state of every network in CONNECTED state with the
// state of its VPN. This is to ensure that when an underlying network connects, apps will
// not see a CONNECTIVITY_ACTION broadcast for a network in state CONNECTED until the VPN
// comes up, at which point there is a new CONNECTIVITY_ACTION broadcast for the underlying
// network, this time with a state of CONNECTED.
//
// Now that the legacy lockdown code lives in ConnectivityService, and no longer has access
// to the internal state of the Vpn object, always replace the state with CONNECTING. This
// is not too far off the truth, since an always-on VPN, when not connected, is always
// trying to reconnect.
if (getLegacyLockdownNai() == null) {
ni.setDetailedState(DetailedState.CONNECTING, "", null);
}
}
@Override
public void setProvisioningNotificationVisible(boolean visible, int networkType,
String action) {
enforceSettingsPermission();
if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
return;
}
final long ident = Binder.clearCallingIdentity();
try {
// Concatenate the range of types onto the range of NetIDs.
int id = NetIdManager.MAX_NET_ID + 1 + (networkType - ConnectivityManager.TYPE_NONE);
mNotifier.setProvNotificationVisible(visible, id, action);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void setAirplaneMode(boolean enable) {
enforceAirplaneModePermission();
final long ident = Binder.clearCallingIdentity();
try {
final ContentResolver cr = mContext.getContentResolver();
Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, encodeBool(enable));
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", enable);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void onUserAdded(@NonNull final UserHandle user) {
mPermissionMonitor.onUserAdded(user);
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
}
private void onUserRemoved(@NonNull final UserHandle user) {
mPermissionMonitor.onUserRemoved(user);
// If there was a network preference for this user, remove it.
handleSetProfileNetworkPreference(
List.of(new ProfileNetworkPreferenceList.Preference(user, null, true)),
null /* listener */);
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
}
private void onPackageChanged(@NonNull final String packageName) {
// This is necessary in case a package is added or removed, but also when it's replaced to
// run as a new UID by its manifest rules. Also, if a separate package shares the same UID
// as one in the preferences, then it should follow the same routing as that other package,
// which means updating the rules is never to be needed in this case (whether it joins or
// leaves a UID with a preference).
if (isMappedInOemNetworkPreference(packageName)) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
}
private final BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ensureRunningOnConnectivityServiceThread();
final String action = intent.getAction();
final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
// User should be filled for below intents, check the existence.
if (user == null) {
Log.wtf(TAG, intent.getAction() + " broadcast without EXTRA_USER");
return;
}
if (Intent.ACTION_USER_ADDED.equals(action)) {
onUserAdded(user);
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
onUserRemoved(user);
} else {
Log.wtf(TAG, "received unexpected intent: " + action);
}
}
};
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ensureRunningOnConnectivityServiceThread();
switch (intent.getAction()) {
case Intent.ACTION_PACKAGE_ADDED:
case Intent.ACTION_PACKAGE_REMOVED:
case Intent.ACTION_PACKAGE_REPLACED:
onPackageChanged(intent.getData().getSchemeSpecificPart());
break;
default:
Log.wtf(TAG, "received unexpected intent: " + intent.getAction());
}
}
};
private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
private static class NetworkProviderInfo {
public final String name;
public final Messenger messenger;
private final IBinder.DeathRecipient mDeathRecipient;
public final int providerId;
NetworkProviderInfo(String name, Messenger messenger, int providerId,
@NonNull IBinder.DeathRecipient deathRecipient) {
this.name = name;
this.messenger = messenger;
this.providerId = providerId;
mDeathRecipient = deathRecipient;
if (mDeathRecipient == null) {
throw new AssertionError("Must pass a deathRecipient");
}
}
void connect(Context context, Handler handler) {
try {
messenger.getBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
mDeathRecipient.binderDied();
}
}
}
private void ensureAllNetworkRequestsHaveType(List<NetworkRequest> requests) {
for (int i = 0; i < requests.size(); i++) {
ensureNetworkRequestHasType(requests.get(i));
}
}
private void ensureNetworkRequestHasType(NetworkRequest request) {
if (request.type == NetworkRequest.Type.NONE) {
throw new IllegalArgumentException(
"All NetworkRequests in ConnectivityService must have a type");
}
}
/**
* Tracks info about the requester.
* Also used to notice when the calling process dies so as to self-expire
*/
@VisibleForTesting
protected class NetworkRequestInfo implements IBinder.DeathRecipient {
// The requests to be satisfied in priority order. Non-multilayer requests will only have a
// single NetworkRequest in mRequests.
final List<NetworkRequest> mRequests;
// mSatisfier and mActiveRequest rely on one another therefore set them together.
void setSatisfier(
@Nullable final NetworkAgentInfo satisfier,
@Nullable final NetworkRequest activeRequest) {
mSatisfier = satisfier;
mActiveRequest = activeRequest;
}
// The network currently satisfying this NRI. Only one request in an NRI can have a
// satisfier. For non-multilayer requests, only non-listen requests can have a satisfier.
@Nullable
private NetworkAgentInfo mSatisfier;
NetworkAgentInfo getSatisfier() {
return mSatisfier;
}
// The request in mRequests assigned to a network agent. This is null if none of the
// requests in mRequests can be satisfied. This member has the constraint of only being
// accessible on the handler thread.
@Nullable
private NetworkRequest mActiveRequest;
NetworkRequest getActiveRequest() {
return mActiveRequest;
}
final PendingIntent mPendingIntent;
boolean mPendingIntentSent;
@Nullable
final Messenger mMessenger;
// Information about the caller that caused this object to be created.
@Nullable
private final IBinder mBinder;
final int mPid;
final int mUid;
final @NetworkCallback.Flag int mCallbackFlags;
@Nullable
final String mCallingAttributionTag;
// Counter keeping track of this NRI.
final PerUidCounter mPerUidCounter;
// Effective UID of this request. This is different from mUid when a privileged process
// files a request on behalf of another UID. This UID is used to determine blocked status,
// UID matching, and so on. mUid above is used for permission checks and to enforce the
// maximum limit of registered callbacks per UID.
final int mAsUid;
// Preference order of this request.
final int mPreferenceOrder;
// In order to preserve the mapping of NetworkRequest-to-callback when apps register
// callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
// maintained for keying off of. This is only a concern when the original nri
// mNetworkRequests changes which happens currently for apps that register callbacks to
// track the default network. In those cases, the nri is updated to have mNetworkRequests
// that match the per-app default nri that currently tracks the calling app's uid so that
// callbacks are fired at the appropriate time. When the callbacks fire,
// mNetworkRequestForCallback will be used so as to preserve the caller's mapping. When
// callbacks are updated to key off of an nri vs NetworkRequest, this stops being an issue.
// TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
@NonNull
private final NetworkRequest mNetworkRequestForCallback;
NetworkRequest getNetworkRequestForCallback() {
return mNetworkRequestForCallback;
}
/**
* Get the list of UIDs this nri applies to.
*/
@NonNull
Set<UidRange> getUids() {
// networkCapabilities.getUids() returns a defensive copy.
// multilayer requests will all have the same uids so return the first one.
final Set<UidRange> uids = mRequests.get(0).networkCapabilities.getUidRanges();
return (null == uids) ? new ArraySet<>() : uids;
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r,
@Nullable final PendingIntent pi, @Nullable String callingAttributionTag) {
this(asUid, Collections.singletonList(r), r, pi, callingAttributionTag,
PREFERENCE_ORDER_INVALID);
}
NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
@NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi,
@Nullable String callingAttributionTag, final int preferenceOrder) {
ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
mNetworkRequestForCallback = requestForCallback;
mPendingIntent = pi;
mMessenger = null;
mBinder = null;
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
mAsUid = asUid;
mPerUidCounter = getRequestCounter(this);
mPerUidCounter.incrementCountOrThrow(mUid);
/**
* Location sensitive data not included in pending intent. Only included in
* {@link NetworkCallback}.
*/
mCallbackFlags = NetworkCallback.FLAG_NONE;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = preferenceOrder;
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
@Nullable String callingAttributionTag) {
this(asUid, Collections.singletonList(r), r, m, binder, callbackFlags,
callingAttributionTag);
}
NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
@NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
@Nullable String callingAttributionTag) {
super();
ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
mNetworkRequestForCallback = requestForCallback;
mMessenger = m;
mBinder = binder;
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
mAsUid = asUid;
mPendingIntent = null;
mPerUidCounter = getRequestCounter(this);
mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = callbackFlags;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
linkDeathRecipient();
}
NetworkRequestInfo(@NonNull final NetworkRequestInfo nri,
@NonNull final List<NetworkRequest> r) {
super();
ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
mNetworkRequestForCallback = nri.getNetworkRequestForCallback();
final NetworkAgentInfo satisfier = nri.getSatisfier();
if (null != satisfier) {
// If the old NRI was satisfied by an NAI, then it may have had an active request.
// The active request is necessary to figure out what callbacks to send, in
// particular then a network updates its capabilities.
// As this code creates a new NRI with a new set of requests, figure out which of
// the list of requests should be the active request. It is always the first
// request of the list that can be satisfied by the satisfier since the order of
// requests is a priority order.
// Note even in the presence of a satisfier there may not be an active request,
// when the satisfier is the no-service network.
NetworkRequest activeRequest = null;
for (final NetworkRequest candidate : r) {
if (candidate.canBeSatisfiedBy(satisfier.networkCapabilities)) {
activeRequest = candidate;
break;
}
}
setSatisfier(satisfier, activeRequest);
}
mMessenger = nri.mMessenger;
mBinder = nri.mBinder;
mPid = nri.mPid;
mUid = nri.mUid;
mAsUid = nri.mAsUid;
mPendingIntent = nri.mPendingIntent;
mPerUidCounter = nri.mPerUidCounter;
mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = nri.mCallbackFlags;
mCallingAttributionTag = nri.mCallingAttributionTag;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
linkDeathRecipient();
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r) {
this(asUid, Collections.singletonList(r), PREFERENCE_ORDER_INVALID);
}
NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
final int preferenceOrder) {
this(asUid, r, r.get(0), null /* pi */, null /* callingAttributionTag */,
preferenceOrder);
}
// True if this NRI is being satisfied. It also accounts for if the nri has its satisifer
// set to the mNoServiceNetwork in which case mActiveRequest will be null thus returning
// false.
boolean isBeingSatisfied() {
return (null != mSatisfier && null != mActiveRequest);
}
boolean isMultilayerRequest() {
return mRequests.size() > 1;
}
private List<NetworkRequest> initializeRequests(List<NetworkRequest> r) {
// Creating a defensive copy to prevent the sender from modifying the list being
// reflected in the return value of this method.
final List<NetworkRequest> tempRequests = new ArrayList<>(r);
return Collections.unmodifiableList(tempRequests);
}
void decrementRequestCount() {
mPerUidCounter.decrementCount(mUid);
}
void linkDeathRecipient() {
if (null != mBinder) {
try {
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
binderDied();
}
}
}
void unlinkDeathRecipient() {
if (null != mBinder) {
try {
mBinder.unlinkToDeath(this, 0);
} catch (NoSuchElementException e) {
// Temporary workaround for b/194394697 pending analysis of additional logs
Log.wtf(TAG, "unlinkToDeath for already unlinked NRI " + this);
}
}
}
boolean hasHigherOrderThan(@NonNull final NetworkRequestInfo target) {
// Compare two preference orders.
return mPreferenceOrder < target.mPreferenceOrder;
}
int getPreferenceOrderForNetd() {
if (mPreferenceOrder >= PREFERENCE_ORDER_NONE
&& mPreferenceOrder <= PREFERENCE_ORDER_LOWEST) {
return mPreferenceOrder;
}
return PREFERENCE_ORDER_NONE;
}
@Override
public void binderDied() {
log("ConnectivityService NetworkRequestInfo binderDied(" +
"uid/pid:" + mUid + "/" + mPid + ", " + mRequests + ", " + mBinder + ")");
// As an immutable collection, mRequests cannot change by the time the
// lambda is evaluated on the handler thread so calling .get() from a binder thread
// is acceptable. Use handleReleaseNetworkRequest and not directly
// handleRemoveNetworkRequest so as to force a lookup in the requests map, in case
// the app already unregistered the request.
mHandler.post(() -> handleReleaseNetworkRequest(mRequests.get(0),
mUid, false /* callOnUnavailable */));
}
@Override
public String toString() {
final String asUidString = (mAsUid == mUid) ? "" : " asUid: " + mAsUid;
return "uid/pid:" + mUid + "/" + mPid + asUidString + " activeRequest: "
+ (mActiveRequest == null ? null : mActiveRequest.requestId)
+ " callbackRequest: "
+ mNetworkRequestForCallback.requestId
+ " " + mRequests
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
+ " callback flags: " + mCallbackFlags
+ " order: " + mPreferenceOrder;
}
}
// This checks that the passed capabilities either do not request a
// specific SSID/SignalStrength, or the calling app has permission to do so.
private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
int callerPid, int callerUid, String callerPackageName) {
if (null != nc.getSsid() && !checkSettingsPermission(callerPid, callerUid)) {
throw new SecurityException("Insufficient permissions to request a specific SSID");
}
if (nc.hasSignalStrength()
&& !checkNetworkSignalStrengthWakeupPermission(callerPid, callerUid)) {
throw new SecurityException(
"Insufficient permissions to request a specific signal strength");
}
mAppOpsManager.checkPackage(callerUid, callerPackageName);
if (!nc.getSubscriptionIds().isEmpty()) {
enforceNetworkFactoryPermission();
}
}
private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
final SortedSet<Integer> thresholds = new TreeSet<>();
synchronized (nai) {
// mNetworkRequests may contain the same value multiple times in case of
// multilayer requests. It won't matter in this case because the thresholds
// will then be the same and be deduplicated as they enter the `thresholds` set.
// TODO : have mNetworkRequests be a Set<NetworkRequestInfo> or the like.
for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
for (final NetworkRequest req : nri.mRequests) {
if (req.networkCapabilities.hasSignalStrength()
&& nai.satisfiesImmutableCapabilitiesOf(req)) {
thresholds.add(req.networkCapabilities.getSignalStrength());
}
}
}
}
return CollectionUtils.toIntArray(new ArrayList<>(thresholds));
}
private void updateSignalStrengthThresholds(
NetworkAgentInfo nai, String reason, NetworkRequest request) {
final int[] thresholdsArray = getSignalStrengthThresholds(nai);
if (VDBG || (DBG && !"CONNECT".equals(reason))) {
String detail;
if (request != null && request.networkCapabilities.hasSignalStrength()) {
detail = reason + " " + request.networkCapabilities.getSignalStrength();
} else {
detail = reason;
}
log(String.format("updateSignalStrengthThresholds: %s, sending %s to %s",
detail, Arrays.toString(thresholdsArray), nai.toShortString()));
}
nai.onSignalStrengthThresholdsUpdated(thresholdsArray);
}
private static void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
if (nc == null) {
return;
}
NetworkSpecifier ns = nc.getNetworkSpecifier();
if (ns == null) {
return;
}
if (ns instanceof MatchAllNetworkSpecifier) {
throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted");
}
}
private static void ensureListenableCapabilities(@NonNull final NetworkCapabilities nc) {
ensureValidNetworkSpecifier(nc);
if (nc.isPrivateDnsBroken()) {
throw new IllegalArgumentException("Can't request broken private DNS");
}
if (nc.hasAccessUids()) {
throw new IllegalArgumentException("Can't request access UIDs");
}
}
private void ensureRequestableCapabilities(@NonNull final NetworkCapabilities nc) {
ensureListenableCapabilities(nc);
final String badCapability = nc.describeFirstNonRequestableCapability();
if (badCapability != null) {
throw new IllegalArgumentException("Cannot request network with " + badCapability);
}
}
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@TargetApi(Build.VERSION_CODES.S)
private boolean isTargetSdkAtleast(int version, int callingUid,
@NonNull String callingPackageName) {
final UserHandle user = UserHandle.getUserHandleForUid(callingUid);
final PackageManager pm =
mContext.createContextAsUser(user, 0 /* flags */).getPackageManager();
try {
final int callingVersion = pm.getTargetSdkVersion(callingPackageName);
if (callingVersion < version) return false;
} catch (PackageManager.NameNotFoundException e) { }
return true;
}
@Override
public NetworkRequest requestNetwork(int asUid, NetworkCapabilities networkCapabilities,
int reqTypeInt, Messenger messenger, int timeoutMs, final IBinder binder,
int legacyType, int callbackFlags, @NonNull String callingPackageName,
@Nullable String callingAttributionTag) {
if (legacyType != TYPE_NONE && !checkNetworkStackPermission()) {
if (isTargetSdkAtleast(Build.VERSION_CODES.M, mDeps.getCallingUid(),
callingPackageName)) {
throw new SecurityException("Insufficient permissions to specify legacy type");
}
}
final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities;
final int callingUid = mDeps.getCallingUid();
// Privileged callers can track the default network of another UID by passing in a UID.
if (asUid != Process.INVALID_UID) {
enforceSettingsPermission();
} else {
asUid = callingUid;
}
final NetworkRequest.Type reqType;
try {
reqType = NetworkRequest.Type.values()[reqTypeInt];
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException("Unsupported request type " + reqTypeInt);
}
switch (reqType) {
case TRACK_DEFAULT:
// If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities}
// is unused and will be replaced by ones appropriate for the UID (usually, the
// calling app). This allows callers to keep track of the default network.
networkCapabilities = copyDefaultNetworkCapabilitiesForUid(
defaultNc, asUid, callingUid, callingPackageName);
enforceAccessPermission();
break;
case TRACK_SYSTEM_DEFAULT:
enforceSettingsPermission();
networkCapabilities = new NetworkCapabilities(defaultNc);
break;
case BACKGROUND_REQUEST:
enforceNetworkStackOrSettingsPermission();
// Fall-through since other checks are the same with normal requests.
case REQUEST:
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
callingAttributionTag);
// TODO: this is incorrect. We mark the request as metered or not depending on
// the state of the app when the request is filed, but we never change the
// request if the app changes network state. http://b/29964605
enforceMeteredApnPolicy(networkCapabilities);
break;
case LISTEN_FOR_BEST:
enforceAccessPermission();
networkCapabilities = new NetworkCapabilities(networkCapabilities);
break;
default:
throw new IllegalArgumentException("Unsupported request type " + reqType);
}
ensureRequestableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
// Enforce FOREGROUND if the caller does not have permission to use background network.
if (reqType == LISTEN_FOR_BEST) {
restrictBackgroundRequestForCaller(networkCapabilities);
}
// Set the UID range for this request to the single UID of the requester, unless the
// requester has the permission to specify other UIDs.
// This will overwrite any allowed UIDs in the requested capabilities. Though there
// are no visible methods to set the UIDs, an app could use reflection to try and get
// networks for other apps so it's essential that the UIDs are overwritten.
// Also set the requester UID and package name in the request.
restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
callingUid, callingPackageName);
if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
}
final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), reqType);
final NetworkRequestInfo nri = getNriToRegister(
asUid, networkRequest, messenger, binder, callbackFlags,
callingAttributionTag);
if (DBG) log("requestNetwork for " + nri);
// For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
// copied from the default request above. (This is necessary to ensure, for example, that
// the callback does not leak sensitive information to unprivileged apps.) Check that the
// changes don't alter request matching.
if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT &&
(!networkCapabilities.equalRequestableCapabilities(defaultNc))) {
throw new IllegalStateException(
"TRACK_SYSTEM_DEFAULT capabilities don't match default request: "
+ networkCapabilities + " vs. " + defaultNc);
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
if (timeoutMs > 0) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
nri), timeoutMs);
}
return networkRequest;
}
/**
* Return the nri to be used when registering a network request. Specifically, this is used with
* requests registered to track the default request. If there is currently a per-app default
* tracking the app requestor, then we need to create a version of this nri that mirrors that of
* the tracking per-app default so that callbacks are sent to the app requestor appropriately.
* @param asUid the uid on behalf of which to file the request. Different from requestorUid
* when a privileged caller is tracking the default network for another uid.
* @param nr the network request for the nri.
* @param msgr the messenger for the nri.
* @param binder the binder for the nri.
* @param callingAttributionTag the calling attribution tag for the nri.
* @return the nri to register.
*/
private NetworkRequestInfo getNriToRegister(final int asUid, @NonNull final NetworkRequest nr,
@Nullable final Messenger msgr, @Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
@Nullable String callingAttributionTag) {
final List<NetworkRequest> requests;
if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
requests = copyDefaultNetworkRequestsForUid(
asUid, nr.getRequestorUid(), nr.getRequestorPackageName());
} else {
requests = Collections.singletonList(nr);
}
return new NetworkRequestInfo(
asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
}
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
String callingPackageName, String callingAttributionTag) {
if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
enforceConnectivityRestrictedNetworksPermission();
}
} else {
enforceChangePermission(callingPackageName, callingAttributionTag);
}
}
private boolean checkConnectivityRestrictedNetworksPermission(int callerPid, int callerUid) {
if (checkAnyPermissionOf(callerPid, callerUid,
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
|| checkAnyPermissionOf(callerPid, callerUid,
android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
return true;
}
return false;
}
@Override
public boolean requestBandwidthUpdate(Network network) {
enforceAccessPermission();
NetworkAgentInfo nai = null;
if (network == null) {
return false;
}
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(network.getNetId());
}
if (nai != null) {
nai.onBandwidthUpdateRequested();
synchronized (mBandwidthRequests) {
final int uid = mDeps.getCallingUid();
Integer uidReqs = mBandwidthRequests.get(uid);
if (uidReqs == null) {
uidReqs = 0;
}
mBandwidthRequests.put(uid, ++uidReqs);
}
return true;
}
return false;
}
private boolean isSystem(int uid) {
return uid < Process.FIRST_APPLICATION_UID;
}
private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
final int uid = mDeps.getCallingUid();
if (isSystem(uid)) {
// Exemption for system uid.
return;
}
if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) {
// Policy already enforced.
return;
}
final long ident = Binder.clearCallingIdentity();
try {
if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) {
// If UID is restricted, don't allow them to bring up metered APNs.
networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities,
PendingIntent operation, @NonNull String callingPackageName,
@Nullable String callingAttributionTag) {
Objects.requireNonNull(operation, "PendingIntent cannot be null.");
final int callingUid = mDeps.getCallingUid();
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
callingAttributionTag);
enforceMeteredApnPolicy(networkCapabilities);
ensureRequestableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
callingUid, callingPackageName);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
callingAttributionTag);
if (DBG) log("pendingRequest for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
nri));
return networkRequest;
}
private void releasePendingNetworkRequestWithDelay(PendingIntent operation) {
mHandler.sendMessageDelayed(
mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT,
mDeps.getCallingUid(), 0, operation), mReleasePendingIntentDelayMs);
}
@Override
public void releasePendingNetworkRequest(PendingIntent operation) {
Objects.requireNonNull(operation, "PendingIntent cannot be null.");
mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT,
mDeps.getCallingUid(), 0, operation));
}
// In order to implement the compatibility measure for pre-M apps that call
// WifiManager.enableNetwork(..., true) without also binding to that network explicitly,
// WifiManager registers a network listen for the purpose of calling setProcessDefaultNetwork.
// This ensures it has permission to do so.
private boolean hasWifiNetworkListenPermission(NetworkCapabilities nc) {
if (nc == null) {
return false;
}
int[] transportTypes = nc.getTransportTypes();
if (transportTypes.length != 1 || transportTypes[0] != NetworkCapabilities.TRANSPORT_WIFI) {
return false;
}
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_WIFI_STATE,
"ConnectivityService");
} catch (SecurityException e) {
return false;
}
return true;
}
@Override
public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, IBinder binder,
@NetworkCallback.Flag int callbackFlags,
@NonNull String callingPackageName, @NonNull String callingAttributionTag) {
final int callingUid = mDeps.getCallingUid();
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName);
// Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so
// make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get
// onLost and onAvailable callbacks when networks move in and out of the background.
// There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
// can't request networks.
restrictBackgroundRequestForCaller(nc);
ensureListenableCapabilities(nc);
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
NetworkRequestInfo nri =
new NetworkRequestInfo(callingUid, networkRequest, messenger, binder, callbackFlags,
callingAttributionTag);
if (VDBG) log("listenForNetwork for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
return networkRequest;
}
@Override
public void pendingListenForNetwork(NetworkCapabilities networkCapabilities,
PendingIntent operation, @NonNull String callingPackageName,
@Nullable String callingAttributionTag) {
Objects.requireNonNull(operation, "PendingIntent cannot be null.");
final int callingUid = mDeps.getCallingUid();
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
ensureListenableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName);
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
callingAttributionTag);
if (VDBG) log("pendingListenForNetwork for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT, nri));
}
/** Returns the next Network provider ID. */
public final int nextNetworkProviderId() {
return mNextNetworkProviderId.getAndIncrement();
}
@Override
public void releaseNetworkRequest(NetworkRequest networkRequest) {
ensureNetworkRequestHasType(networkRequest);
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_RELEASE_NETWORK_REQUEST, mDeps.getCallingUid(), 0, networkRequest));
}
private void releaseNetworkRequestAndCallOnUnavailable(NetworkRequest networkRequest) {
ensureNetworkRequestHasType(networkRequest);
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE, mDeps.getCallingUid(), 0,
networkRequest));
}
private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
if (mNetworkProviderInfos.containsKey(npi.messenger)) {
// Avoid creating duplicates. even if an app makes a direct AIDL call.
// This will never happen if an app calls ConnectivityManager#registerNetworkProvider,
// as that will throw if a duplicate provider is registered.
loge("Attempt to register existing NetworkProviderInfo "
+ mNetworkProviderInfos.get(npi.messenger).name);
return;
}
if (DBG) log("Got NetworkProvider Messenger for " + npi.name);
mNetworkProviderInfos.put(npi.messenger, npi);
npi.connect(mContext, mTrackerHandler);
}
@Override
public int registerNetworkProvider(Messenger messenger, String name) {
enforceNetworkFactoryOrSettingsPermission();
Objects.requireNonNull(messenger, "messenger must be non-null");
NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger,
nextNetworkProviderId(), () -> unregisterNetworkProvider(messenger));
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
return npi.providerId;
}
@Override
public void unregisterNetworkProvider(Messenger messenger) {
enforceNetworkFactoryOrSettingsPermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
}
@Override
public void offerNetwork(final int providerId,
@NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps,
@NonNull final INetworkOfferCallback callback) {
Objects.requireNonNull(score);
Objects.requireNonNull(caps);
Objects.requireNonNull(callback);
final boolean yieldToBadWiFi = caps.hasTransport(TRANSPORT_CELLULAR) && !avoidBadWifi();
final NetworkOffer offer = new NetworkOffer(
FullScore.makeProspectiveScore(score, caps, yieldToBadWiFi),
caps, callback, providerId);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_OFFER, offer));
}
private void updateOfferScore(final NetworkOffer offer) {
final boolean yieldToBadWiFi =
offer.caps.hasTransport(TRANSPORT_CELLULAR) && !avoidBadWifi();
final NetworkOffer newOffer = new NetworkOffer(
offer.score.withYieldToBadWiFi(yieldToBadWiFi),
offer.caps, offer.callback, offer.providerId);
if (offer.equals(newOffer)) return;
handleRegisterNetworkOffer(newOffer);
}
@Override
public void unofferNetwork(@NonNull final INetworkOfferCallback callback) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_OFFER, callback));
}
private void handleUnregisterNetworkProvider(Messenger messenger) {
NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger);
if (npi == null) {
loge("Failed to find Messenger in unregisterNetworkProvider");
return;
}
// Unregister all the offers from this provider
final ArrayList<NetworkOfferInfo> toRemove = new ArrayList<>();
for (final NetworkOfferInfo noi : mNetworkOffers) {
if (noi.offer.providerId == npi.providerId) {
// Can't call handleUnregisterNetworkOffer here because iteration is in progress
toRemove.add(noi);
}
}
for (final NetworkOfferInfo noi : toRemove) {
handleUnregisterNetworkOffer(noi);
}
if (DBG) log("unregisterNetworkProvider for " + npi.name);
}
@Override
public void declareNetworkRequestUnfulfillable(@NonNull final NetworkRequest request) {
if (request.hasTransport(TRANSPORT_TEST)) {
enforceNetworkFactoryOrTestNetworksPermission();
} else {
enforceNetworkFactoryPermission();
}
final NetworkRequestInfo nri = mNetworkRequests.get(request);
if (nri != null) {
// declareNetworkRequestUnfulfillable() paths don't apply to multilayer requests.
ensureNotMultilayerRequest(nri, "declareNetworkRequestUnfulfillable");
mHandler.post(() -> handleReleaseNetworkRequest(
nri.mRequests.get(0), mDeps.getCallingUid(), true));
}
}
// NOTE: Accessed on multiple threads, must be synchronized on itself.
@GuardedBy("mNetworkForNetId")
private final SparseArray<NetworkAgentInfo> mNetworkForNetId = new SparseArray<>();
// NOTE: Accessed on multiple threads, synchronized with mNetworkForNetId.
// An entry is first reserved with NetIdManager, prior to being added to mNetworkForNetId, so
// there may not be a strict 1:1 correlation between the two.
private final NetIdManager mNetIdManager;
// Tracks all NetworkAgents that are currently registered.
// NOTE: Only should be accessed on ConnectivityServiceThread, except dump().
private final ArraySet<NetworkAgentInfo> mNetworkAgentInfos = new ArraySet<>();
// UID ranges for users that are currently blocked by VPNs.
// This array is accessed and iterated on multiple threads without holding locks, so its
// contents must never be mutated. When the ranges change, the array is replaced with a new one
// (on the handler thread).
private volatile List<UidRange> mVpnBlockedUidRanges = new ArrayList<>();
// Must only be accessed on the handler thread
@NonNull
private final ArrayList<NetworkOfferInfo> mNetworkOffers = new ArrayList<>();
@GuardedBy("mBlockedAppUids")
private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
// Current OEM network preferences. This object must only be written to on the handler thread.
// Since it is immutable and always non-null, other threads may read it if they only care
// about seeing a consistent object but not that it is current.
@NonNull
private OemNetworkPreferences mOemNetworkPreferences =
new OemNetworkPreferences.Builder().build();
// Current per-profile network preferences. This object follows the same threading rules as
// the OEM network preferences above.
@NonNull
private ProfileNetworkPreferenceList mProfileNetworkPreferences =
new ProfileNetworkPreferenceList();
// A set of UIDs that should use mobile data preferentially if available. This object follows
// the same threading rules as the OEM network preferences above.
@NonNull
private Set<Integer> mMobileDataPreferredUids = new ArraySet<>();
// OemNetworkPreferences activity String log entries.
private static final int MAX_OEM_NETWORK_PREFERENCE_LOGS = 20;
@NonNull
private final LocalLog mOemNetworkPreferencesLogs =
new LocalLog(MAX_OEM_NETWORK_PREFERENCE_LOGS);
/**
* Determine whether a given package has a mapping in the current OemNetworkPreferences.
* @param packageName the package name to check existence of a mapping for.
* @return true if a mapping exists, false otherwise
*/
private boolean isMappedInOemNetworkPreference(@NonNull final String packageName) {
return mOemNetworkPreferences.getNetworkPreferences().containsKey(packageName);
}
// The always-on request for an Internet-capable network that apps without a specific default
// fall back to.
@VisibleForTesting
@NonNull
final NetworkRequestInfo mDefaultRequest;
// Collection of NetworkRequestInfo's used for default networks.
@VisibleForTesting
@NonNull
final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) {
return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri);
}
/**
* Return the default network request currently tracking the given uid.
* @param uid the uid to check.
* @return the NetworkRequestInfo tracking the given uid.
*/
@NonNull
private NetworkRequestInfo getDefaultRequestTrackingUid(final int uid) {
NetworkRequestInfo highestPriorityNri = mDefaultRequest;
for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
// Checking the first request is sufficient as only multilayer requests will have more
// than one request and for multilayer, all requests will track the same uids.
if (nri.mRequests.get(0).networkCapabilities.appliesToUid(uid)) {
// Find out the highest priority request.
if (nri.hasHigherOrderThan(highestPriorityNri)) {
highestPriorityNri = nri;
}
}
}
return highestPriorityNri;
}
/**
* Get a copy of the network requests of the default request that is currently tracking the
* given uid.
* @param asUid the uid on behalf of which to file the request. Different from requestorUid
* when a privileged caller is tracking the default network for another uid.
* @param requestorUid the uid to check the default for.
* @param requestorPackageName the requestor's package name.
* @return a copy of the default's NetworkRequest that is tracking the given uid.
*/
@NonNull
private List<NetworkRequest> copyDefaultNetworkRequestsForUid(
final int asUid, final int requestorUid, @NonNull final String requestorPackageName) {
return copyNetworkRequestsForUid(
getDefaultRequestTrackingUid(asUid).mRequests,
asUid, requestorUid, requestorPackageName);
}
/**
* Copy the given nri's NetworkRequest collection.
* @param requestsToCopy the NetworkRequest collection to be copied.
* @param asUid the uid on behalf of which to file the request. Different from requestorUid
* when a privileged caller is tracking the default network for another uid.
* @param requestorUid the uid to set on the copied collection.
* @param requestorPackageName the package name to set on the copied collection.
* @return the copied NetworkRequest collection.
*/
@NonNull
private List<NetworkRequest> copyNetworkRequestsForUid(
@NonNull final List<NetworkRequest> requestsToCopy, final int asUid,
final int requestorUid, @NonNull final String requestorPackageName) {
final List<NetworkRequest> requests = new ArrayList<>();
for (final NetworkRequest nr : requestsToCopy) {
requests.add(new NetworkRequest(copyDefaultNetworkCapabilitiesForUid(
nr.networkCapabilities, asUid, requestorUid, requestorPackageName),
nr.legacyType, nextNetworkRequestId(), nr.type));
}
return requests;
}
@NonNull
private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid(
@NonNull final NetworkCapabilities netCapToCopy, final int asUid,
final int requestorUid, @NonNull final String requestorPackageName) {
// These capabilities are for a TRACK_DEFAULT callback, so:
// 1. Remove NET_CAPABILITY_VPN, because it's (currently!) the only difference between
// mDefaultRequest and a per-UID default request.
// TODO: stop depending on the fact that these two unrelated things happen to be the same
// 2. Always set the UIDs to asUid. restrictRequestUidsForCallerAndSetRequestorInfo will
// not do this in the case of a privileged application.
final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
netCap.setSingleUid(asUid);
restrictRequestUidsForCallerAndSetRequestorInfo(
netCap, requestorUid, requestorPackageName);
return netCap;
}
/**
* Get the nri that is currently being tracked for callbacks by per-app defaults.
* @param nr the network request to check for equality against.
* @return the nri if one exists, null otherwise.
*/
@Nullable
private NetworkRequestInfo getNriForAppRequest(@NonNull final NetworkRequest nr) {
for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.getNetworkRequestForCallback().equals(nr)) {
return nri;
}
}
return null;
}
/**
* Check if an nri is currently being managed by per-app default networking.
* @param nri the nri to check.
* @return true if this nri is currently being managed by per-app default networking.
*/
private boolean isPerAppTrackedNri(@NonNull final NetworkRequestInfo nri) {
// nri.mRequests.get(0) is only different from the original request filed in
// nri.getNetworkRequestForCallback() if nri.mRequests was changed by per-app default
// functionality therefore if these two don't match, it means this particular nri is
// currently being managed by a per-app default.
return nri.getNetworkRequestForCallback() != nri.mRequests.get(0);
}
/**
* Determine if an nri is a managed default request that disallows default networking.
* @param nri the request to evaluate
* @return true if device-default networking is disallowed
*/
private boolean isDefaultBlocked(@NonNull final NetworkRequestInfo nri) {
// Check if this nri is a managed default that supports the default network at its
// lowest priority request.
final NetworkRequest defaultNetworkRequest = mDefaultRequest.mRequests.get(0);
final NetworkCapabilities lowestPriorityNetCap =
nri.mRequests.get(nri.mRequests.size() - 1).networkCapabilities;
return isPerAppDefaultRequest(nri)
&& !(defaultNetworkRequest.networkCapabilities.equalRequestableCapabilities(
lowestPriorityNetCap));
}
// Request used to optionally keep mobile data active even when higher
// priority networks like Wi-Fi are active.
private final NetworkRequest mDefaultMobileDataRequest;
// Request used to optionally keep wifi data active even when higher
// priority networks like ethernet are active.
private final NetworkRequest mDefaultWifiRequest;
// Request used to optionally keep vehicle internal network always active
private final NetworkRequest mDefaultVehicleRequest;
// Sentinel NAI used to direct apps with default networks that should have no connectivity to a
// network with no service. This NAI should never be matched against, nor should any public API
// ever return the associated network. For this reason, this NAI is not in the list of available
// NAIs. It is used in computeNetworkReassignment() to be set as the satisfier for non-device
// default requests that don't support using the device default network which will ultimately
// allow ConnectivityService to use this no-service network when calling makeDefaultForApps().
@VisibleForTesting
final NetworkAgentInfo mNoServiceNetwork;
// The NetworkAgentInfo currently satisfying the default request, if any.
private NetworkAgentInfo getDefaultNetwork() {
return mDefaultRequest.mSatisfier;
}
private NetworkAgentInfo getDefaultNetworkForUid(final int uid) {
NetworkRequestInfo highestPriorityNri = mDefaultRequest;
for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
// Currently, all network requests will have the same uids therefore checking the first
// one is sufficient. If/when uids are tracked at the nri level, this can change.
final Set<UidRange> uids = nri.mRequests.get(0).networkCapabilities.getUidRanges();
if (null == uids) {
continue;
}
for (final UidRange range : uids) {
if (range.contains(uid)) {
if (nri.hasHigherOrderThan(highestPriorityNri)) {
highestPriorityNri = nri;
}
}
}
}
return highestPriorityNri.getSatisfier();
}
@Nullable
private Network getNetwork(@Nullable NetworkAgentInfo nai) {
return nai != null ? nai.network : null;
}
private void ensureRunningOnConnectivityServiceThread() {
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException(
"Not running on ConnectivityService thread: "
+ Thread.currentThread().getName());
}
}
@VisibleForTesting
protected boolean isDefaultNetwork(NetworkAgentInfo nai) {
return nai == getDefaultNetwork();
}
/**
* Register a new agent with ConnectivityService to handle a network.
*
* @param na a reference for ConnectivityService to contact the agent asynchronously.
* @param networkInfo the initial info associated with this network. It can be updated later :
* see {@link #updateNetworkInfo}.
* @param linkProperties the initial link properties of this network. They can be updated
* later : see {@link #updateLinkProperties}.
* @param networkCapabilities the initial capabilites of this network. They can be updated
* later : see {@link #updateCapabilities}.
* @param initialScore the initial score of the network. See
* {@link NetworkAgentInfo#getCurrentScore}.
* @param networkAgentConfig metadata about the network. This is never updated.
* @param providerId the ID of the provider owning this NetworkAgent.
* @return the network created for this agent.
*/
public Network registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
@NonNull NetworkScore initialScore, NetworkAgentConfig networkAgentConfig,
int providerId) {
Objects.requireNonNull(networkInfo, "networkInfo must not be null");
Objects.requireNonNull(linkProperties, "linkProperties must not be null");
Objects.requireNonNull(networkCapabilities, "networkCapabilities must not be null");
Objects.requireNonNull(initialScore, "initialScore must not be null");
Objects.requireNonNull(networkAgentConfig, "networkAgentConfig must not be null");
if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
enforceAnyPermissionOf(Manifest.permission.MANAGE_TEST_NETWORKS);
} else {
enforceNetworkFactoryPermission();
}
final int uid = mDeps.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
return registerNetworkAgentInternal(na, networkInfo, linkProperties,
networkCapabilities, initialScore, networkAgentConfig, providerId, uid);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private Network registerNetworkAgentInternal(INetworkAgent na, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId,
int uid) {
// At this point the capabilities/properties are untrusted and unverified, e.g. checks that
// the capabilities' access UID comply with security limitations. They will be sanitized
// as the NAI registration finishes, in handleRegisterNetworkAgent(). This is
// because some of the checks must happen on the handler thread.
final NetworkAgentInfo nai = new NetworkAgentInfo(na,
new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo),
linkProperties, networkCapabilities,
currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs,
mQosCallbackTracker, mDeps);
final String extraInfo = networkInfo.getExtraInfo();
final String name = TextUtils.isEmpty(extraInfo)
? nai.networkCapabilities.getSsid() : extraInfo;
if (DBG) log("registerNetworkAgent " + nai);
mDeps.getNetworkStack().makeNetworkMonitor(
nai.network, name, new NetworkMonitorCallbacks(nai));
// NetworkAgentInfo registration will finish when the NetworkMonitor is created.
// If the network disconnects or sends any other event before that, messages are deferred by
// NetworkAgent until nai.connect(), which will be called when finalizing the
// registration.
return nai.network;
}
private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
if (VDBG) log("Network Monitor created for " + nai);
// nai.nc and nai.lp are the same object that was passed by the network agent if the agent
// lives in the same process as this code (e.g. wifi), so make sure this code doesn't
// mutate their object
final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
final LinkProperties lp = new LinkProperties(nai.linkProperties);
// Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
processCapabilitiesFromAgent(nai, nc);
nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
processLinkPropertiesFromAgent(nai, lp);
nai.linkProperties = lp;
nai.onNetworkMonitorCreated(networkMonitor);
mNetworkAgentInfos.add(nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.put(nai.network.getNetId(), nai);
}
try {
networkMonitor.start();
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
nai.notifyRegistered();
NetworkInfo networkInfo = nai.networkInfo;
updateNetworkInfo(nai, networkInfo);
updateVpnUids(nai, null, nai.networkCapabilities);
}
private class NetworkOfferInfo implements IBinder.DeathRecipient {
@NonNull public final NetworkOffer offer;
NetworkOfferInfo(@NonNull final NetworkOffer offer) {
this.offer = offer;
}
@Override
public void binderDied() {
mHandler.post(() -> handleUnregisterNetworkOffer(this));
}
}
private boolean isNetworkProviderWithIdRegistered(final int providerId) {
for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
if (npi.providerId == providerId) return true;
}
return false;
}
/**
* Register or update a network offer.
* @param newOffer The new offer. If the callback member is the same as an existing
* offer, it is an update of that offer.
*/
// TODO : rename this to handleRegisterOrUpdateNetworkOffer
private void handleRegisterNetworkOffer(@NonNull final NetworkOffer newOffer) {
ensureRunningOnConnectivityServiceThread();
if (!isNetworkProviderWithIdRegistered(newOffer.providerId)) {
// This may actually happen if a provider updates its score or registers and then
// immediately unregisters. The offer would still be in the handler queue, but the
// provider would have been removed.
if (DBG) log("Received offer from an unregistered provider");
return;
}
final NetworkOfferInfo existingOffer = findNetworkOfferInfoByCallback(newOffer.callback);
if (null != existingOffer) {
handleUnregisterNetworkOffer(existingOffer);
newOffer.migrateFrom(existingOffer.offer);
if (DBG) {
// handleUnregisterNetworkOffer has already logged the old offer
log("update offer from providerId " + newOffer.providerId + " new : " + newOffer);
}
} else {
if (DBG) {
log("register offer from providerId " + newOffer.providerId + " : " + newOffer);
}
}
final NetworkOfferInfo noi = new NetworkOfferInfo(newOffer);
try {
noi.offer.callback.asBinder().linkToDeath(noi, 0 /* flags */);
} catch (RemoteException e) {
noi.binderDied();
return;
}
mNetworkOffers.add(noi);
issueNetworkNeeds(noi);
}
private void handleUnregisterNetworkOffer(@NonNull final NetworkOfferInfo noi) {
ensureRunningOnConnectivityServiceThread();
if (DBG) {
log("unregister offer from providerId " + noi.offer.providerId + " : " + noi.offer);
}
// If the provider removes the offer and dies immediately afterwards this
// function may be called twice in a row, but the array will no longer contain
// the offer.
if (!mNetworkOffers.remove(noi)) return;
noi.offer.callback.asBinder().unlinkToDeath(noi, 0 /* flags */);
}
@Nullable private NetworkOfferInfo findNetworkOfferInfoByCallback(
@NonNull final INetworkOfferCallback callback) {
ensureRunningOnConnectivityServiceThread();
for (final NetworkOfferInfo noi : mNetworkOffers) {
if (noi.offer.callback.asBinder().equals(callback.asBinder())) return noi;
}
return null;
}
/**
* Called when receiving LinkProperties directly from a NetworkAgent.
* Stores into |nai| any data coming from the agent that might also be written to the network's
* LinkProperties by ConnectivityService itself. This ensures that the data provided by the
* agent is not lost when updateLinkProperties is called.
* This method should never alter the agent's LinkProperties, only store data in |nai|.
*/
private void processLinkPropertiesFromAgent(NetworkAgentInfo nai, LinkProperties lp) {
lp.ensureDirectlyConnectedRoutes();
nai.clatd.setNat64PrefixFromRa(lp.getNat64Prefix());
nai.networkAgentPortalData = lp.getCaptivePortalData();
}
private void updateLinkProperties(NetworkAgentInfo networkAgent, @NonNull LinkProperties newLp,
@NonNull LinkProperties oldLp) {
int netId = networkAgent.network.getNetId();
// The NetworkAgent does not know whether clatd is running on its network or not, or whether
// a NAT64 prefix was discovered by the DNS resolver. Before we do anything else, make sure
// the LinkProperties for the network are accurate.
networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
// update filtering rules, need to happen after the interface update so netd knows about the
// new interface (the interface name -> index map becomes initialized)
updateVpnFiltering(newLp, oldLp, networkAgent);
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
// updateMtu(lp, null);
// }
if (isDefaultNetwork(networkAgent)) {
updateTcpBufferSizes(newLp.getTcpBufferSizes());
}
updateRoutes(newLp, oldLp, netId);
updateDnses(newLp, oldLp, netId);
// Make sure LinkProperties represents the latest private DNS status.
// This does not need to be done before updateDnses because the
// LinkProperties are not the source of the private DNS configuration.
// updateDnses will fetch the private DNS configuration from DnsManager.
mDnsManager.updatePrivateDnsStatus(netId, newLp);
if (isDefaultNetwork(networkAgent)) {
handleApplyDefaultProxy(newLp.getHttpProxy());
} else {
updateProxy(newLp, oldLp);
}
updateWakeOnLan(newLp);
// Captive portal data is obtained from NetworkMonitor and stored in NetworkAgentInfo.
// It is not always contained in the LinkProperties sent from NetworkAgents, and if it
// does, it needs to be merged here.
newLp.setCaptivePortalData(mergeCaptivePortalData(networkAgent.networkAgentPortalData,
networkAgent.capportApiData));
// TODO - move this check to cover the whole function
if (!Objects.equals(newLp, oldLp)) {
synchronized (networkAgent) {
networkAgent.linkProperties = newLp;
}
// Start or stop DNS64 detection and 464xlat according to network state.
networkAgent.clatd.update();
notifyIfacesChangedForNetworkStats();
networkAgent.networkMonitor().notifyLinkPropertiesChanged(
new LinkProperties(newLp, true /* parcelSensitiveFields */));
if (networkAgent.everConnected) {
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
}
}
mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
}
/**
* @param naData captive portal data from NetworkAgent
* @param apiData captive portal data from capport API
*/
@Nullable
private CaptivePortalData mergeCaptivePortalData(CaptivePortalData naData,
CaptivePortalData apiData) {
if (naData == null || apiData == null) {
return naData == null ? apiData : naData;
}
final CaptivePortalData.Builder captivePortalBuilder =
new CaptivePortalData.Builder(naData);
if (apiData.isCaptive()) {
captivePortalBuilder.setCaptive(true);
}
if (apiData.isSessionExtendable()) {
captivePortalBuilder.setSessionExtendable(true);
}
if (apiData.getExpiryTimeMillis() >= 0 || apiData.getByteLimit() >= 0) {
// Expiry time, bytes remaining, refresh time all need to come from the same source,
// otherwise data would be inconsistent. Prefer the capport API info if present,
// as it can generally be refreshed more often.
captivePortalBuilder.setExpiryTime(apiData.getExpiryTimeMillis());
captivePortalBuilder.setBytesRemaining(apiData.getByteLimit());
captivePortalBuilder.setRefreshTime(apiData.getRefreshTimeMillis());
} else if (naData.getExpiryTimeMillis() < 0 && naData.getByteLimit() < 0) {
// No source has time / bytes remaining information: surface the newest refresh time
// for other fields
captivePortalBuilder.setRefreshTime(
Math.max(naData.getRefreshTimeMillis(), apiData.getRefreshTimeMillis()));
}
// Prioritize the user portal URL from the network agent if the source is authenticated.
if (apiData.getUserPortalUrl() != null && naData.getUserPortalUrlSource()
!= CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl(),
apiData.getUserPortalUrlSource());
}
// Prioritize the venue information URL from the network agent if the source is
// authenticated.
if (apiData.getVenueInfoUrl() != null && naData.getVenueInfoUrlSource()
!= CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl(),
apiData.getVenueInfoUrlSource());
}
return captivePortalBuilder.build();
}
private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
// Marks are only available on WiFi interfaces. Checking for
// marks on unsupported interfaces is harmless.
if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return;
}
int mark = mResources.get().getInteger(R.integer.config_networkWakeupPacketMark);
int mask = mResources.get().getInteger(R.integer.config_networkWakeupPacketMask);
// Mask/mark of zero will not detect anything interesting.
// Don't install rules unless both values are nonzero.
if (mark == 0 || mask == 0) {
return;
}
final String prefix = "iface:" + iface;
try {
if (add) {
mNetd.wakeupAddInterface(iface, prefix, mark, mask);
} else {
mNetd.wakeupDelInterface(iface, prefix, mark, mask);
}
} catch (Exception e) {
loge("Exception modifying wakeup packet monitoring: " + e);
}
}
private void updateInterfaces(final @Nullable LinkProperties newLp,
final @Nullable LinkProperties oldLp, final int netId,
final @NonNull NetworkCapabilities caps) {
final CompareResult<String> interfaceDiff = new CompareResult<>(
oldLp != null ? oldLp.getAllInterfaceNames() : null,
newLp != null ? newLp.getAllInterfaceNames() : null);
if (!interfaceDiff.added.isEmpty()) {
for (final String iface : interfaceDiff.added) {
try {
if (DBG) log("Adding iface " + iface + " to network " + netId);
mNetd.networkAddInterface(netId, iface);
wakeupModifyInterface(iface, caps, true);
mDeps.reportNetworkInterfaceForTransports(mContext, iface,
caps.getTransportTypes());
} catch (Exception e) {
logw("Exception adding interface: " + e);
}
}
}
for (final String iface : interfaceDiff.removed) {
try {
if (DBG) log("Removing iface " + iface + " from network " + netId);
wakeupModifyInterface(iface, caps, false);
mNetd.networkRemoveInterface(netId, iface);
} catch (Exception e) {
loge("Exception removing interface: " + e);
}
}
}
// TODO: move to frameworks/libs/net.
private RouteInfoParcel convertRouteInfo(RouteInfo route) {
final String nextHop;
switch (route.getType()) {
case RouteInfo.RTN_UNICAST:
if (route.hasGateway()) {
nextHop = route.getGateway().getHostAddress();
} else {
nextHop = INetd.NEXTHOP_NONE;
}
break;
case RouteInfo.RTN_UNREACHABLE:
nextHop = INetd.NEXTHOP_UNREACHABLE;
break;
case RouteInfo.RTN_THROW:
nextHop = INetd.NEXTHOP_THROW;
break;
default:
nextHop = INetd.NEXTHOP_NONE;
break;
}
final RouteInfoParcel rip = new RouteInfoParcel();
rip.ifName = route.getInterface();
rip.destination = route.getDestination().toString();
rip.nextHop = nextHop;
rip.mtu = route.getMtu();
return rip;
}
/**
* Have netd update routes from oldLp to newLp.
* @return true if routes changed between oldLp and newLp
*/
private boolean updateRoutes(LinkProperties newLp, LinkProperties oldLp, int netId) {
// compare the route diff to determine which routes have been updated
final CompareOrUpdateResult<RouteInfo.RouteKey, RouteInfo> routeDiff =
new CompareOrUpdateResult<>(
oldLp != null ? oldLp.getAllRoutes() : null,
newLp != null ? newLp.getAllRoutes() : null,
(r) -> r.getRouteKey());
// add routes before removing old in case it helps with continuous connectivity
// do this twice, adding non-next-hop routes first, then routes they are dependent on
for (RouteInfo route : routeDiff.added) {
if (route.hasGateway()) continue;
if (VDBG || DDBG) log("Adding Route [" + route + "] to network " + netId);
try {
mNetd.networkAddRouteParcel(netId, convertRouteInfo(route));
} catch (Exception e) {
if ((route.getDestination().getAddress() instanceof Inet4Address) || VDBG) {
loge("Exception in networkAddRouteParcel for non-gateway: " + e);
}
}
}
for (RouteInfo route : routeDiff.added) {
if (!route.hasGateway()) continue;
if (VDBG || DDBG) log("Adding Route [" + route + "] to network " + netId);
try {
mNetd.networkAddRouteParcel(netId, convertRouteInfo(route));
} catch (Exception e) {
if ((route.getGateway() instanceof Inet4Address) || VDBG) {
loge("Exception in networkAddRouteParcel for gateway: " + e);
}
}
}
for (RouteInfo route : routeDiff.removed) {
if (VDBG || DDBG) log("Removing Route [" + route + "] from network " + netId);
try {
mNetd.networkRemoveRouteParcel(netId, convertRouteInfo(route));
} catch (Exception e) {
loge("Exception in networkRemoveRouteParcel: " + e);
}
}
for (RouteInfo route : routeDiff.updated) {
if (VDBG || DDBG) log("Updating Route [" + route + "] from network " + netId);
try {
mNetd.networkUpdateRouteParcel(netId, convertRouteInfo(route));
} catch (Exception e) {
loge("Exception in networkUpdateRouteParcel: " + e);
}
}
return !routeDiff.added.isEmpty() || !routeDiff.removed.isEmpty()
|| !routeDiff.updated.isEmpty();
}
private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId) {
if (oldLp != null && newLp.isIdenticalDnses(oldLp)) {
return; // no updating necessary
}
if (DBG) {
final Collection<InetAddress> dnses = newLp.getDnsServers();
log("Setting DNS servers for network " + netId + " to " + dnses);
}
try {
mDnsManager.noteDnsServersForNetwork(netId, newLp);
mDnsManager.flushVmDnsCache();
} catch (Exception e) {
loge("Exception in setDnsConfigurationForNetwork: " + e);
}
}
private void updateVpnFiltering(LinkProperties newLp, LinkProperties oldLp,
NetworkAgentInfo nai) {
final String oldIface = oldLp != null ? oldLp.getInterfaceName() : null;
final String newIface = newLp != null ? newLp.getInterfaceName() : null;
final boolean wasFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, oldLp);
final boolean needsFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, newLp);
if (!wasFiltering && !needsFiltering) {
// Nothing to do.
return;
}
if (Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) {
// Nothing changed.
return;
}
final Set<UidRange> ranges = nai.networkCapabilities.getUidRanges();
final int vpnAppUid = nai.networkCapabilities.getOwnerUid();
// TODO: this create a window of opportunity for apps to receive traffic between the time
// when the old rules are removed and the time when new rules are added. To fix this,
// make eBPF support two allowlisted interfaces so here new rules can be added before the
// old rules are being removed.
if (wasFiltering) {
mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid);
}
if (needsFiltering) {
mPermissionMonitor.onVpnUidRangesAdded(newIface, ranges, vpnAppUid);
}
}
private void updateWakeOnLan(@NonNull LinkProperties lp) {
if (mWolSupportedInterfaces == null) {
mWolSupportedInterfaces = new ArraySet<>(mResources.get().getStringArray(
R.array.config_wakeonlan_supported_interfaces));
}
lp.setWakeOnLanSupported(mWolSupportedInterfaces.contains(lp.getInterfaceName()));
}
private int getNetworkPermission(NetworkCapabilities nc) {
if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
return INetd.PERMISSION_SYSTEM;
}
if (!nc.hasCapability(NET_CAPABILITY_FOREGROUND)) {
return INetd.PERMISSION_NETWORK;
}
return INetd.PERMISSION_NONE;
}
private void updateNetworkPermissions(@NonNull final NetworkAgentInfo nai,
@NonNull final NetworkCapabilities newNc) {
final int oldPermission = getNetworkPermission(nai.networkCapabilities);
final int newPermission = getNetworkPermission(newNc);
if (oldPermission != newPermission && nai.created && !nai.isVPN()) {
try {
mNetd.networkSetPermissionForNetwork(nai.network.getNetId(), newPermission);
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception in networkSetPermissionForNetwork: " + e);
}
}
}
/**
* Called when receiving NetworkCapabilities directly from a NetworkAgent.
* Stores into |nai| any data coming from the agent that might also be written to the network's
* NetworkCapabilities by ConnectivityService itself. This ensures that the data provided by the
* agent is not lost when updateCapabilities is called.
*/
private void processCapabilitiesFromAgent(NetworkAgentInfo nai, NetworkCapabilities nc) {
if (nc.hasConnectivityManagedCapability()) {
Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
}
// Note: resetting the owner UID before storing the agent capabilities in NAI means that if
// the agent attempts to change the owner UID, then nai.declaredCapabilities will not
// actually be the same as the capabilities sent by the agent. Still, it is safer to reset
// the owner UID here and behave as if the agent had never tried to change it.
if (nai.networkCapabilities.getOwnerUid() != nc.getOwnerUid()) {
Log.e(TAG, nai.toShortString() + ": ignoring attempt to change owner from "
+ nai.networkCapabilities.getOwnerUid() + " to " + nc.getOwnerUid());
nc.setOwnerUid(nai.networkCapabilities.getOwnerUid());
}
nai.declaredCapabilities = new NetworkCapabilities(nc);
NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
mCarrierPrivilegeAuthenticator);
}
/** Modifies |newNc| based on the capabilities of |underlyingNetworks| and |agentCaps|. */
@VisibleForTesting
void applyUnderlyingCapabilities(@Nullable Network[] underlyingNetworks,
@NonNull NetworkCapabilities agentCaps, @NonNull NetworkCapabilities newNc) {
underlyingNetworks = underlyingNetworksOrDefault(
agentCaps.getOwnerUid(), underlyingNetworks);
long transportTypes = NetworkCapabilitiesUtils.packBits(agentCaps.getTransportTypes());
int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
// metered if any underlying is metered, or originally declared metered by the agent.
boolean metered = !agentCaps.hasCapability(NET_CAPABILITY_NOT_METERED);
boolean roaming = false; // roaming if any underlying is roaming
boolean congested = false; // congested if any underlying is congested
boolean suspended = true; // suspended if all underlying are suspended
boolean hadUnderlyingNetworks = false;
ArrayList<Network> newUnderlyingNetworks = null;
if (null != underlyingNetworks) {
newUnderlyingNetworks = new ArrayList<>();
for (Network underlyingNetwork : underlyingNetworks) {
final NetworkAgentInfo underlying =
getNetworkAgentInfoForNetwork(underlyingNetwork);
if (underlying == null) continue;
final NetworkCapabilities underlyingCaps = underlying.networkCapabilities;
hadUnderlyingNetworks = true;
for (int underlyingType : underlyingCaps.getTransportTypes()) {
transportTypes |= 1L << underlyingType;
}
// Merge capabilities of this underlying network. For bandwidth, assume the
// worst case.
downKbps = NetworkCapabilities.minBandwidth(downKbps,
underlyingCaps.getLinkDownstreamBandwidthKbps());
upKbps = NetworkCapabilities.minBandwidth(upKbps,
underlyingCaps.getLinkUpstreamBandwidthKbps());
// If this underlying network is metered, the VPN is metered (it may cost money
// to send packets on this network).
metered |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_METERED);
// If this underlying network is roaming, the VPN is roaming (the billing structure
// is different than the usual, local one).
roaming |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
// If this underlying network is congested, the VPN is congested (the current
// condition of the network affects the performance of this network).
congested |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_CONGESTED);
// If this network is not suspended, the VPN is not suspended (the VPN
// is able to transfer some data).
suspended &= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
newUnderlyingNetworks.add(underlyingNetwork);
}
}
if (!hadUnderlyingNetworks) {
// No idea what the underlying networks are; assume reasonable defaults
metered = true;
roaming = false;
congested = false;
suspended = false;
}
newNc.setTransportTypes(NetworkCapabilitiesUtils.unpackBits(transportTypes));
newNc.setLinkDownstreamBandwidthKbps(downKbps);
newNc.setLinkUpstreamBandwidthKbps(upKbps);
newNc.setCapability(NET_CAPABILITY_NOT_METERED, !metered);
newNc.setCapability(NET_CAPABILITY_NOT_ROAMING, !roaming);
newNc.setCapability(NET_CAPABILITY_NOT_CONGESTED, !congested);
newNc.setCapability(NET_CAPABILITY_NOT_SUSPENDED, !suspended);
newNc.setUnderlyingNetworks(newUnderlyingNetworks);
}
/**
* Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are
* maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal,
* and foreground status).
*/
@NonNull
private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) {
// Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
// Don't complain for VPNs since they're not driven by requests and there is no risk of
// causing a connect/teardown loop.
// TODO: remove this altogether and make it the responsibility of the NetworkProviders to
// avoid connect/teardown loops.
if (nai.everConnected &&
!nai.isVPN() &&
!nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) {
// TODO: consider not complaining when a network agent degrades its capabilities if this
// does not cause any request (that is not a listen) currently matching that agent to
// stop being matched by the updated agent.
String diff = nai.networkCapabilities.describeImmutableDifferences(nc);
if (!TextUtils.isEmpty(diff)) {
Log.wtf(TAG, "BUG: " + nai + " lost immutable capabilities:" + diff);
}
}
// Don't modify caller's NetworkCapabilities.
final NetworkCapabilities newNc = new NetworkCapabilities(nc);
if (nai.lastValidated) {
newNc.addCapability(NET_CAPABILITY_VALIDATED);
} else {
newNc.removeCapability(NET_CAPABILITY_VALIDATED);
}
if (nai.lastCaptivePortalDetected) {
newNc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
} else {
newNc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
}
if (nai.isBackgroundNetwork()) {
newNc.removeCapability(NET_CAPABILITY_FOREGROUND);
} else {
newNc.addCapability(NET_CAPABILITY_FOREGROUND);
}
if (nai.partialConnectivity) {
newNc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
} else {
newNc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
}
newNc.setPrivateDnsBroken(nai.networkCapabilities.isPrivateDnsBroken());
// TODO : remove this once all factories are updated to send NOT_SUSPENDED and NOT_ROAMING
if (!newNc.hasTransport(TRANSPORT_CELLULAR)) {
newNc.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
newNc.addCapability(NET_CAPABILITY_NOT_ROAMING);
}
if (nai.propagateUnderlyingCapabilities()) {
applyUnderlyingCapabilities(nai.declaredUnderlyingNetworks, nai.declaredCapabilities,
newNc);
}
return newNc;
}
private void updateNetworkInfoForRoamingAndSuspended(NetworkAgentInfo nai,
NetworkCapabilities prevNc, NetworkCapabilities newNc) {
final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
if (prevSuspended != suspended) {
// TODO (b/73132094) : remove this call once the few users of onSuspended and
// onResumed have been removed.
notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED
: ConnectivityManager.CALLBACK_RESUMED);
}
if (prevSuspended != suspended || prevRoaming != roaming) {
// updateNetworkInfo will mix in the suspended info from the capabilities and
// take appropriate action for the network having possibly changed state.
updateNetworkInfo(nai, nai.networkInfo);
}
}
/**
* Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically:
*
* 1. Calls mixInCapabilities to merge the passed-in NetworkCapabilities {@code nc} with the
* capabilities we manage and store in {@code nai}, such as validated status and captive
* portal status)
* 2. Takes action on the result: changes network permissions, sends CAP_CHANGED callbacks, and
* potentially triggers rematches.
* 3. Directly informs other network stack components (NetworkStatsService, VPNs, etc. of the
* change.)
*
* @param oldScore score of the network before any of the changes that prompted us
* to call this function.
* @param nai the network having its capabilities updated.
* @param nc the new network capabilities.
*/
private void updateCapabilities(final int oldScore, @NonNull final NetworkAgentInfo nai,
@NonNull final NetworkCapabilities nc) {
NetworkCapabilities newNc = mixInCapabilities(nai, nc);
if (Objects.equals(nai.networkCapabilities, newNc)) return;
updateNetworkPermissions(nai, newNc);
final NetworkCapabilities prevNc = nai.getAndSetNetworkCapabilities(newNc);
updateVpnUids(nai, prevNc, newNc);
updateAccessUids(nai, prevNc, newNc);
nai.updateScoreForNetworkAgentUpdate();
if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) {
// If the requestable capabilities haven't changed, and the score hasn't changed, then
// the change we're processing can't affect any requests, it can only affect the listens
// on this network. We might have been called by rematchNetworkAndRequests when a
// network changed foreground state.
processListenRequests(nai);
} else {
// If the requestable capabilities have changed or the score changed, we can't have been
// called by rematchNetworkAndRequests, so it's safe to start a rematch.
rematchAllNetworksAndRequests();
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
updateNetworkInfoForRoamingAndSuspended(nai, prevNc, newNc);
final boolean oldMetered = prevNc.isMetered();
final boolean newMetered = newNc.isMetered();
final boolean meteredChanged = oldMetered != newMetered;
if (meteredChanged) {
maybeNotifyNetworkBlocked(nai, oldMetered, newMetered,
mVpnBlockedUidRanges, mVpnBlockedUidRanges);
}
final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING)
!= newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
// Report changes that are interesting for network statistics tracking.
if (meteredChanged || roamingChanged) {
notifyIfacesChangedForNetworkStats();
}
// This network might have been underlying another network. Propagate its capabilities.
propagateUnderlyingNetworkCapabilities(nai.network);
if (!newNc.equalsTransportTypes(prevNc)) {
mDnsManager.updateTransportsForNetwork(
nai.network.getNetId(), newNc.getTransportTypes());
}
maybeSendProxyBroadcast(nai, prevNc, newNc);
}
/** Convenience method to update the capabilities for a given network. */
private void updateCapabilitiesForNetwork(NetworkAgentInfo nai) {
updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
}
/**
* Returns whether VPN isolation (ingress interface filtering) should be applied on the given
* network.
*
* Ingress interface filtering enforces that all apps under the given network can only receive
* packets from the network's interface (and loopback). This is important for VPNs because
* apps that cannot bypass a fully-routed VPN shouldn't be able to receive packets from any
* non-VPN interfaces.
*
* As a result, this method should return true iff
* 1. the network is an app VPN (not legacy VPN)
* 2. the VPN does not allow bypass
* 3. the VPN is fully-routed
* 4. the VPN interface is non-null
*
* @see INetd#firewallAddUidInterfaceRules
* @see INetd#firewallRemoveUidInterfaceRules
*/
private boolean requiresVpnIsolation(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc,
LinkProperties lp) {
if (nc == null || lp == null) return false;
return nai.isVPN()
&& !nai.networkAgentConfig.allowBypass
&& nc.getOwnerUid() != Process.SYSTEM_UID
&& lp.getInterfaceName() != null
&& (lp.hasIpv4DefaultRoute() || lp.hasIpv4UnreachableDefaultRoute())
&& (lp.hasIpv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute());
}
private static UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) {
final UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.size()];
int index = 0;
for (UidRange range : ranges) {
stableRanges[index] = new UidRangeParcel(range.start, range.stop);
index++;
}
return stableRanges;
}
private static UidRangeParcel[] intsToUidRangeStableParcels(
final @NonNull ArraySet<Integer> uids) {
final UidRangeParcel[] stableRanges = new UidRangeParcel[uids.size()];
int index = 0;
for (int uid : uids) {
stableRanges[index] = new UidRangeParcel(uid, uid);
index++;
}
return stableRanges;
}
private static UidRangeParcel[] toUidRangeStableParcels(UidRange[] ranges) {
final UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.length];
for (int i = 0; i < ranges.length; i++) {
stableRanges[i] = new UidRangeParcel(ranges[i].start, ranges[i].stop);
}
return stableRanges;
}
private void maybeCloseSockets(NetworkAgentInfo nai, UidRangeParcel[] ranges,
int[] exemptUids) {
if (nai.isVPN() && !nai.networkAgentConfig.allowBypass) {
try {
mNetd.socketDestroy(ranges, exemptUids);
} catch (Exception e) {
loge("Exception in socket destroy: ", e);
}
}
}
private void updateVpnUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges) {
int[] exemptUids = new int[2];
// TODO: Excluding VPN_UID is necessary in order to not to kill the TCP connection used
// by PPTP. Fix this by making Vpn set the owner UID to VPN_UID instead of system when
// starting a legacy VPN, and remove VPN_UID here. (b/176542831)
exemptUids[0] = VPN_UID;
exemptUids[1] = nai.networkCapabilities.getOwnerUid();
UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges);
maybeCloseSockets(nai, ranges, exemptUids);
try {
if (add) {
mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
nai.network.netId, ranges, PREFERENCE_ORDER_VPN));
} else {
mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
nai.network.netId, ranges, PREFERENCE_ORDER_VPN));
}
} catch (Exception e) {
loge("Exception while " + (add ? "adding" : "removing") + " uid ranges " + uidRanges +
" on netId " + nai.network.netId + ". " + e);
}
maybeCloseSockets(nai, ranges, exemptUids);
}
private boolean isProxySetOnAnyDefaultNetwork() {
ensureRunningOnConnectivityServiceThread();
for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
final NetworkAgentInfo nai = nri.getSatisfier();
if (nai != null && nai.linkProperties.getHttpProxy() != null) {
return true;
}
}
return false;
}
private void maybeSendProxyBroadcast(NetworkAgentInfo nai, NetworkCapabilities prevNc,
NetworkCapabilities newNc) {
// When the apps moved from/to a VPN, a proxy broadcast is needed to inform the apps that
// the proxy might be changed since the default network satisfied by the apps might also
// changed.
// TODO: Try to track the default network that apps use and only send a proxy broadcast when
// that happens to prevent false alarms.
final Set<UidRange> prevUids = prevNc == null ? null : prevNc.getUidRanges();
final Set<UidRange> newUids = newNc == null ? null : newNc.getUidRanges();
if (nai.isVPN() && nai.everConnected && !UidRange.hasSameUids(prevUids, newUids)
&& (nai.linkProperties.getHttpProxy() != null || isProxySetOnAnyDefaultNetwork())) {
mProxyTracker.sendProxyBroadcast();
}
}
private void updateVpnUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
@Nullable NetworkCapabilities newNc) {
Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUidRanges();
Set<UidRange> newRanges = null == newNc ? null : newNc.getUidRanges();
if (null == prevRanges) prevRanges = new ArraySet<>();
if (null == newRanges) newRanges = new ArraySet<>();
final Set<UidRange> prevRangesCopy = new ArraySet<>(prevRanges);
prevRanges.removeAll(newRanges);
newRanges.removeAll(prevRangesCopy);
try {
// When updating the VPN uid routing rules, add the new range first then remove the old
// range. If old range were removed first, there would be a window between the old
// range being removed and the new range being added, during which UIDs contained
// in both ranges are not subject to any VPN routing rules. Adding new range before
// removing old range works because, unlike the filtering rules below, it's possible to
// add duplicate UID routing rules.
// TODO: calculate the intersection of add & remove. Imagining that we are trying to
// remove uid 3 from a set containing 1-5. Intersection of the prev and new sets is:
// [1-5] & [1-2],[4-5] == [3]
// Then we can do:
// maybeCloseSockets([3])
// mNetd.networkAddUidRanges([1-2],[4-5])
// mNetd.networkRemoveUidRanges([1-5])
// maybeCloseSockets([3])
// This can prevent the sockets of uid 1-2, 4-5 from being closed. It also reduce the
// number of binder calls from 6 to 4.
if (!newRanges.isEmpty()) {
updateVpnUidRanges(true, nai, newRanges);
}
if (!prevRanges.isEmpty()) {
updateVpnUidRanges(false, nai, prevRanges);
}
final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties);
final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties);
final String iface = nai.linkProperties.getInterfaceName();
// For VPN uid interface filtering, old ranges need to be removed before new ranges can
// be added, due to the range being expanded and stored as individual UIDs. For example
// the UIDs might be updated from [0, 99999] to ([0, 10012], [10014, 99999]) which means
// prevRanges = [0, 99999] while newRanges = [0, 10012], [10014, 99999]. If prevRanges
// were added first and then newRanges got removed later, there would be only one uid
// 10013 left. A consequence of removing old ranges before adding new ranges is that
// there is now a window of opportunity when the UIDs are not subject to any filtering.
// Note that this is in contrast with the (more robust) update of VPN routing rules
// above, where the addition of new ranges happens before the removal of old ranges.
// TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range
// to be removed will never overlap with the new range to be added.
if (wasFiltering && !prevRanges.isEmpty()) {
mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges, prevNc.getOwnerUid());
}
if (shouldFilter && !newRanges.isEmpty()) {
mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges, newNc.getOwnerUid());
}
} catch (Exception e) {
// Never crash!
loge("Exception in updateVpnUids: ", e);
}
}
private void updateAccessUids(@NonNull NetworkAgentInfo nai,
@Nullable NetworkCapabilities prevNc, @Nullable NetworkCapabilities newNc) {
// In almost all cases both NC code for empty access UIDs. return as fast as possible.
final boolean prevEmpty = null == prevNc || prevNc.getAccessUidsNoCopy().isEmpty();
final boolean newEmpty = null == newNc || newNc.getAccessUidsNoCopy().isEmpty();
if (prevEmpty && newEmpty) return;
final ArraySet<Integer> prevUids =
null == prevNc ? new ArraySet<>() : prevNc.getAccessUidsNoCopy();
final ArraySet<Integer> newUids =
null == newNc ? new ArraySet<>() : newNc.getAccessUidsNoCopy();
if (prevUids.equals(newUids)) return;
// This implementation is very simple and vastly faster for sets of Integers than
// CompareOrUpdateResult, which is tuned for sets that need to be compared based on
// a key computed from the value and has storage for that.
final ArraySet<Integer> toRemove = new ArraySet<>(prevUids);
final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
toRemove.removeAll(newUids);
toAdd.removeAll(prevUids);
try {
if (!toAdd.isEmpty()) {
mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
nai.network.netId,
intsToUidRangeStableParcels(toAdd),
PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
}
if (!toRemove.isEmpty()) {
mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
nai.network.netId,
intsToUidRangeStableParcels(toRemove),
PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
}
} catch (ServiceSpecificException e) {
// Has the interface disappeared since the network was built ?
Log.i(TAG, "Can't set access UIDs for network " + nai.network, e);
} catch (RemoteException e) {
// Netd died. This usually causes a runtime restart anyway.
}
}
public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
ensureRunningOnConnectivityServiceThread();
if (!mNetworkAgentInfos.contains(nai)) {
// Ignore updates for disconnected networks
return;
}
if (VDBG || DDBG) {
log("Update of LinkProperties for " + nai.toShortString()
+ "; created=" + nai.created
+ "; everConnected=" + nai.everConnected);
}
// TODO: eliminate this defensive copy after confirming that updateLinkProperties does not
// modify its oldLp parameter.
updateLinkProperties(nai, newLp, new LinkProperties(nai.linkProperties));
}
private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent,
int notificationType) {
if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
Intent intent = new Intent();
intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network);
// If apps could file multi-layer requests with PendingIntents, they'd need to know
// which of the layer is satisfied alongside with some ID for the request. Hence, if
// such an API is ever implemented, there is no doubt the right request to send in
// EXTRA_NETWORK_REQUEST is the active request, and whatever ID would be added would
// need to be sent as a separate extra.
final NetworkRequest req = nri.isMultilayerRequest()
? nri.getActiveRequest()
// Non-multilayer listen requests do not have an active request
: nri.mRequests.get(0);
if (req == null) {
Log.wtf(TAG, "No request in NRI " + nri);
}
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, req);
nri.mPendingIntentSent = true;
sendIntent(nri.mPendingIntent, intent);
}
// else not handled
}
private void sendIntent(PendingIntent pendingIntent, Intent intent) {
mPendingIntentWakeLock.acquire();
try {
if (DBG) log("Sending " + pendingIntent);
pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */);
} catch (PendingIntent.CanceledException e) {
if (DBG) log(pendingIntent + " was not sent, it had been canceled.");
mPendingIntentWakeLock.release();
releasePendingNetworkRequest(pendingIntent);
}
// ...otherwise, mPendingIntentWakeLock.release() gets called by onSendFinished()
}
@Override
public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
String resultData, Bundle resultExtras) {
if (DBG) log("Finished sending " + pendingIntent);
mPendingIntentWakeLock.release();
// Release with a delay so the receiving client has an opportunity to put in its
// own request.
releasePendingNetworkRequestWithDelay(pendingIntent);
}
private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri,
@NonNull final NetworkAgentInfo networkAgent, final int notificationType,
final int arg1) {
if (nri.mMessenger == null) {
// Default request has no msgr. Also prevents callbacks from being invoked for
// NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
Bundle bundle = new Bundle();
// TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
// TODO: check if defensive copies of data is needed.
final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
putParcelable(bundle, nrForCallback);
Message msg = Message.obtain();
if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
putParcelable(bundle, networkAgent.network);
}
final boolean includeLocationSensitiveInfo =
(nri.mCallbackFlags & NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) != 0;
switch (notificationType) {
case ConnectivityManager.CALLBACK_AVAILABLE: {
final NetworkCapabilities nc =
networkCapabilitiesRestrictedForCallerPermissions(
networkAgent.networkCapabilities, nri.mPid, nri.mUid);
putParcelable(
bundle,
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
nc, includeLocationSensitiveInfo, nri.mPid, nri.mUid,
nrForCallback.getRequestorPackageName(),
nri.mCallingAttributionTag));
putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
networkAgent.linkProperties, nri.mPid, nri.mUid));
// For this notification, arg1 contains the blocked status.
msg.arg1 = arg1;
break;
}
case ConnectivityManager.CALLBACK_LOSING: {
msg.arg1 = arg1;
break;
}
case ConnectivityManager.CALLBACK_CAP_CHANGED: {
// networkAgent can't be null as it has been accessed a few lines above.
final NetworkCapabilities netCap =
networkCapabilitiesRestrictedForCallerPermissions(
networkAgent.networkCapabilities, nri.mPid, nri.mUid);
putParcelable(
bundle,
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
netCap, includeLocationSensitiveInfo, nri.mPid, nri.mUid,
nrForCallback.getRequestorPackageName(),
nri.mCallingAttributionTag));
break;
}
case ConnectivityManager.CALLBACK_IP_CHANGED: {
putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
networkAgent.linkProperties, nri.mPid, nri.mUid));
break;
}
case ConnectivityManager.CALLBACK_BLK_CHANGED: {
maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1);
msg.arg1 = arg1;
break;
}
}
msg.what = notificationType;
msg.setData(bundle);
try {
if (VDBG) {
String notification = ConnectivityManager.getCallbackName(notificationType);
log("sending notification " + notification + " for " + nrForCallback);
}
nri.mMessenger.send(msg);
} catch (RemoteException e) {
// may occur naturally in the race of binder death.
loge("RemoteException caught trying to send a callback msg for " + nrForCallback);
}
}
private static <T extends Parcelable> void putParcelable(Bundle bundle, T t) {
bundle.putParcelable(t.getClass().getSimpleName(), t);
}
/**
* Returns whether reassigning a request from an NAI to another can be done gracefully.
*
* When a request should be assigned to a new network, it is normally lingered to give
* time for apps to gracefully migrate their connections. When both networks are on the same
* radio, but that radio can't do time-sharing efficiently, this may end up being
* counter-productive because any traffic on the old network may drastically reduce the
* performance of the new network.
* The stack supports a configuration to let modem vendors state that their radio can't
* do time-sharing efficiently. If this configuration is set, the stack assumes moving
* from one cell network to another can't be done gracefully.
*
* @param oldNai the old network serving the request
* @param newNai the new network serving the request
* @return whether the switch can be graceful
*/
private boolean canSupportGracefulNetworkSwitch(@NonNull final NetworkAgentInfo oldSatisfier,
@NonNull final NetworkAgentInfo newSatisfier) {
if (mCellularRadioTimesharingCapable) return true;
return !oldSatisfier.networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)
|| !newSatisfier.networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)
|| !newSatisfier.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY);
}
private void teardownUnneededNetwork(NetworkAgentInfo nai) {
if (nai.numRequestNetworkRequests() != 0) {
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
// Ignore listening and track default requests.
if (!nr.isRequest()) continue;
loge("Dead network still had at least " + nr);
break;
}
}
nai.disconnect();
}
private void handleLingerComplete(NetworkAgentInfo oldNetwork) {
if (oldNetwork == null) {
loge("Unknown NetworkAgentInfo in handleLingerComplete");
return;
}
if (DBG) log("handleLingerComplete for " + oldNetwork.toShortString());
// If we get here it means that the last linger timeout for this network expired. So there
// must be no other active linger timers, and we must stop lingering.
oldNetwork.clearInactivityState();
if (unneeded(oldNetwork, UnneededFor.TEARDOWN)) {
// Tear the network down.
teardownUnneededNetwork(oldNetwork);
} else {
// Put the network in the background if it doesn't satisfy any foreground request.
updateCapabilitiesForNetwork(oldNetwork);
}
}
private void processDefaultNetworkChanges(@NonNull final NetworkReassignment changes) {
boolean isDefaultChanged = false;
for (final NetworkRequestInfo defaultRequestInfo : mDefaultNetworkRequests) {
final NetworkReassignment.RequestReassignment reassignment =
changes.getReassignment(defaultRequestInfo);
if (null == reassignment) {
continue;
}
// reassignment only contains those instances where the satisfying network changed.
isDefaultChanged = true;
// Notify system services of the new default.
makeDefault(defaultRequestInfo, reassignment.mOldNetwork, reassignment.mNewNetwork);
}
if (isDefaultChanged) {
// Hold a wakelock for a short time to help apps in migrating to a new default.
scheduleReleaseNetworkTransitionWakelock();
}
}
private void makeDefault(@NonNull final NetworkRequestInfo nri,
@Nullable final NetworkAgentInfo oldDefaultNetwork,
@Nullable final NetworkAgentInfo newDefaultNetwork) {
if (DBG) {
log("Switching to new default network for: " + nri + " using " + newDefaultNetwork);
}
// Fix up the NetworkCapabilities of any networks that have this network as underlying.
if (newDefaultNetwork != null) {
propagateUnderlyingNetworkCapabilities(newDefaultNetwork.network);
}
// Set an app level managed default and return since further processing only applies to the
// default network.
if (mDefaultRequest != nri) {
makeDefaultForApps(nri, oldDefaultNetwork, newDefaultNetwork);
return;
}
makeDefaultNetwork(newDefaultNetwork);
if (oldDefaultNetwork != null) {
mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
}
mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
handleApplyDefaultProxy(null != newDefaultNetwork
? newDefaultNetwork.linkProperties.getHttpProxy() : null);
updateTcpBufferSizes(null != newDefaultNetwork
? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null);
notifyIfacesChangedForNetworkStats();
}
private void makeDefaultForApps(@NonNull final NetworkRequestInfo nri,
@Nullable final NetworkAgentInfo oldDefaultNetwork,
@Nullable final NetworkAgentInfo newDefaultNetwork) {
try {
if (VDBG) {
log("Setting default network for " + nri
+ " using UIDs " + nri.getUids()
+ " with old network " + (oldDefaultNetwork != null
? oldDefaultNetwork.network().getNetId() : "null")
+ " and new network " + (newDefaultNetwork != null
? newDefaultNetwork.network().getNetId() : "null"));
}
if (nri.getUids().isEmpty()) {
throw new IllegalStateException("makeDefaultForApps called without specifying"
+ " any applications to set as the default." + nri);
}
if (null != newDefaultNetwork) {
mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
newDefaultNetwork.network.getNetId(),
toUidRangeStableParcels(nri.getUids()),
nri.getPreferenceOrderForNetd()));
}
if (null != oldDefaultNetwork) {
mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
oldDefaultNetwork.network.getNetId(),
toUidRangeStableParcels(nri.getUids()),
nri.getPreferenceOrderForNetd()));
}
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception setting app default network", e);
}
}
private void makeDefaultNetwork(@Nullable final NetworkAgentInfo newDefaultNetwork) {
try {
if (null != newDefaultNetwork) {
mNetd.networkSetDefault(newDefaultNetwork.network.getNetId());
} else {
mNetd.networkClearDefault();
}
} catch (RemoteException | ServiceSpecificException e) {
loge("Exception setting default network :" + e);
}
}
private void processListenRequests(@NonNull final NetworkAgentInfo nai) {
// For consistency with previous behaviour, send onLost callbacks before onAvailable.
processNewlyLostListenRequests(nai);
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
processNewlySatisfiedListenRequests(nai);
}
private void processNewlyLostListenRequests(@NonNull final NetworkAgentInfo nai) {
for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.isMultilayerRequest()) {
continue;
}
final NetworkRequest nr = nri.mRequests.get(0);
if (!nr.isListen()) continue;
if (nai.isSatisfyingRequest(nr.requestId) && !nai.satisfies(nr)) {
nai.removeRequest(nr.requestId);
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);
}
}
}
private void processNewlySatisfiedListenRequests(@NonNull final NetworkAgentInfo nai) {
for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.isMultilayerRequest()) {
continue;
}
final NetworkRequest nr = nri.mRequests.get(0);
if (!nr.isListen()) continue;
if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) {
nai.addRequest(nr);
notifyNetworkAvailable(nai, nri);
}
}
}
// An accumulator class to gather the list of changes that result from a rematch.
private static class NetworkReassignment {
static class RequestReassignment {
@NonNull public final NetworkRequestInfo mNetworkRequestInfo;
@Nullable public final NetworkRequest mOldNetworkRequest;
@Nullable public final NetworkRequest mNewNetworkRequest;
@Nullable public final NetworkAgentInfo mOldNetwork;
@Nullable public final NetworkAgentInfo mNewNetwork;
RequestReassignment(@NonNull final NetworkRequestInfo networkRequestInfo,
@Nullable final NetworkRequest oldNetworkRequest,
@Nullable final NetworkRequest newNetworkRequest,
@Nullable final NetworkAgentInfo oldNetwork,
@Nullable final NetworkAgentInfo newNetwork) {
mNetworkRequestInfo = networkRequestInfo;
mOldNetworkRequest = oldNetworkRequest;
mNewNetworkRequest = newNetworkRequest;
mOldNetwork = oldNetwork;
mNewNetwork = newNetwork;
}
public String toString() {
final NetworkRequest requestToShow = null != mNewNetworkRequest
? mNewNetworkRequest : mNetworkRequestInfo.mRequests.get(0);
return requestToShow.requestId + " : "
+ (null != mOldNetwork ? mOldNetwork.network.getNetId() : "null")
+ "" + (null != mNewNetwork ? mNewNetwork.network.getNetId() : "null");
}
}
@NonNull private final ArrayList<RequestReassignment> mReassignments = new ArrayList<>();
@NonNull Iterable<RequestReassignment> getRequestReassignments() {
return mReassignments;
}
void addRequestReassignment(@NonNull final RequestReassignment reassignment) {
if (Build.isDebuggable()) {
// The code is never supposed to add two reassignments of the same request. Make
// sure this stays true, but without imposing this expensive check on all
// reassignments on all user devices.
for (final RequestReassignment existing : mReassignments) {
if (existing.mNetworkRequestInfo.equals(reassignment.mNetworkRequestInfo)) {
throw new IllegalStateException("Trying to reassign ["
+ reassignment + "] but already have ["
+ existing + "]");
}
}
}
mReassignments.add(reassignment);
}
// Will return null if this reassignment does not change the network assigned to
// the passed request.
@Nullable
private RequestReassignment getReassignment(@NonNull final NetworkRequestInfo nri) {
for (final RequestReassignment event : getRequestReassignments()) {
if (nri == event.mNetworkRequestInfo) return event;
}
return null;
}
public String toString() {
final StringJoiner sj = new StringJoiner(", " /* delimiter */,
"NetReassign [" /* prefix */, "]" /* suffix */);
if (mReassignments.isEmpty()) return sj.add("no changes").toString();
for (final RequestReassignment rr : getRequestReassignments()) {
sj.add(rr.toString());
}
return sj.toString();
}
public String debugString() {
final StringBuilder sb = new StringBuilder();
sb.append("NetworkReassignment :");
if (mReassignments.isEmpty()) return sb.append(" no changes").toString();
for (final RequestReassignment rr : getRequestReassignments()) {
sb.append("\n ").append(rr);
}
return sb.append("\n").toString();
}
}
private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri,
@Nullable final NetworkRequest previousRequest,
@Nullable final NetworkRequest newRequest,
@Nullable final NetworkAgentInfo previousSatisfier,
@Nullable final NetworkAgentInfo newSatisfier,
final long now) {
if (null != newSatisfier && mNoServiceNetwork != newSatisfier) {
if (VDBG) log("rematch for " + newSatisfier.toShortString());
if (null != previousRequest && null != previousSatisfier) {
if (VDBG || DDBG) {
log(" accepting network in place of " + previousSatisfier.toShortString());
}
previousSatisfier.removeRequest(previousRequest.requestId);
if (canSupportGracefulNetworkSwitch(previousSatisfier, newSatisfier)) {
// If this network switch can't be supported gracefully, the request is not
// lingered. This allows letting go of the network sooner to reclaim some
// performance on the new network, since the radio can't do both at the same
// time while preserving good performance.
previousSatisfier.lingerRequest(previousRequest.requestId, now);
}
} else {
if (VDBG || DDBG) log(" accepting network in place of null");
}
// To prevent constantly CPU wake up for nascent timer, if a network comes up
// and immediately satisfies a request then remove the timer. This will happen for
// all networks except in the case of an underlying network for a VCN.
if (newSatisfier.isNascent()) {
newSatisfier.unlingerRequest(NetworkRequest.REQUEST_ID_NONE);
newSatisfier.unsetInactive();
}
// if newSatisfier is not null, then newRequest may not be null.
newSatisfier.unlingerRequest(newRequest.requestId);
if (!newSatisfier.addRequest(newRequest)) {
Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has "
+ newRequest);
}
} else if (null != previousRequest && null != previousSatisfier) {
if (DBG) {
log("Network " + previousSatisfier.toShortString() + " stopped satisfying"
+ " request " + previousRequest.requestId);
}
previousSatisfier.removeRequest(previousRequest.requestId);
}
nri.setSatisfier(newSatisfier, newRequest);
}
/**
* This function is triggered when something can affect what network should satisfy what
* request, and it computes the network reassignment from the passed collection of requests to
* network match to the one that the system should now have. That data is encoded in an
* object that is a list of changes, each of them having an NRI, and old satisfier, and a new
* satisfier.
*
* After the reassignment is computed, it is applied to the state objects.
*
* @param networkRequests the nri objects to evaluate for possible network reassignment
* @return NetworkReassignment listing of proposed network assignment changes
*/
@NonNull
private NetworkReassignment computeNetworkReassignment(
@NonNull final Collection<NetworkRequestInfo> networkRequests) {
final NetworkReassignment changes = new NetworkReassignment();
// Gather the list of all relevant agents.
final ArrayList<NetworkAgentInfo> nais = new ArrayList<>();
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
if (!nai.everConnected) {
continue;
}
nais.add(nai);
}
for (final NetworkRequestInfo nri : networkRequests) {
// Non-multilayer listen requests can be ignored.
if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) {
continue;
}
NetworkAgentInfo bestNetwork = null;
NetworkRequest bestRequest = null;
for (final NetworkRequest req : nri.mRequests) {
bestNetwork = mNetworkRanker.getBestNetwork(req, nais, nri.getSatisfier());
// Stop evaluating as the highest possible priority request is satisfied.
if (null != bestNetwork) {
bestRequest = req;
break;
}
}
if (null == bestNetwork && isDefaultBlocked(nri)) {
// Remove default networking if disallowed for managed default requests.
bestNetwork = mNoServiceNetwork;
}
if (nri.getSatisfier() != bestNetwork) {
// bestNetwork may be null if no network can satisfy this request.
changes.addRequestReassignment(new NetworkReassignment.RequestReassignment(
nri, nri.mActiveRequest, bestRequest, nri.getSatisfier(), bestNetwork));
}
}
return changes;
}
private Set<NetworkRequestInfo> getNrisFromGlobalRequests() {
return new HashSet<>(mNetworkRequests.values());
}
/**
* Attempt to rematch all Networks with all NetworkRequests. This may result in Networks
* being disconnected.
*/
private void rematchAllNetworksAndRequests() {
rematchNetworksAndRequests(getNrisFromGlobalRequests());
}
/**
* Attempt to rematch all Networks with given NetworkRequests. This may result in Networks
* being disconnected.
*/
private void rematchNetworksAndRequests(
@NonNull final Set<NetworkRequestInfo> networkRequests) {
ensureRunningOnConnectivityServiceThread();
// TODO: This may be slow, and should be optimized.
final long now = SystemClock.elapsedRealtime();
final NetworkReassignment changes = computeNetworkReassignment(networkRequests);
if (VDBG || DDBG) {
log(changes.debugString());
} else if (DBG) {
log(changes.toString()); // Shorter form, only one line of log
}
applyNetworkReassignment(changes, now);
issueNetworkNeeds();
}
private void applyNetworkReassignment(@NonNull final NetworkReassignment changes,
final long now) {
final Collection<NetworkAgentInfo> nais = mNetworkAgentInfos;
// Since most of the time there are only 0 or 1 background networks, it would probably
// be more efficient to just use an ArrayList here. TODO : measure performance
final ArraySet<NetworkAgentInfo> oldBgNetworks = new ArraySet<>();
for (final NetworkAgentInfo nai : nais) {
if (nai.isBackgroundNetwork()) oldBgNetworks.add(nai);
}
// First, update the lists of satisfied requests in the network agents. This is necessary
// because some code later depends on this state to be correct, most prominently computing
// the linger status.
for (final NetworkReassignment.RequestReassignment event :
changes.getRequestReassignments()) {
updateSatisfiersForRematchRequest(event.mNetworkRequestInfo,
event.mOldNetworkRequest, event.mNewNetworkRequest,
event.mOldNetwork, event.mNewNetwork,
now);
}
// Process default network changes if applicable.
processDefaultNetworkChanges(changes);
// Notify requested networks are available after the default net is switched, but
// before LegacyTypeTracker sends legacy broadcasts
for (final NetworkReassignment.RequestReassignment event :
changes.getRequestReassignments()) {
if (null != event.mNewNetwork) {
notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo);
} else {
callCallbackForRequest(event.mNetworkRequestInfo, event.mOldNetwork,
ConnectivityManager.CALLBACK_LOST, 0);
}
}
// Update the inactivity state before processing listen callbacks, because the background
// computation depends on whether the network is inactive. Don't send the LOSING callbacks
// just yet though, because they have to be sent after the listens are processed to keep
// backward compatibility.
final ArrayList<NetworkAgentInfo> inactiveNetworks = new ArrayList<>();
for (final NetworkAgentInfo nai : nais) {
// Rematching may have altered the inactivity state of some networks, so update all
// inactivity timers. updateInactivityState reads the state from the network agent
// and does nothing if the state has not changed : the source of truth is controlled
// with NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which
// have been called while rematching the individual networks above.
if (updateInactivityState(nai, now)) {
inactiveNetworks.add(nai);
}
}
for (final NetworkAgentInfo nai : nais) {
if (!nai.everConnected) continue;
final boolean oldBackground = oldBgNetworks.contains(nai);
// Process listen requests and update capabilities if the background state has
// changed for this network. For consistency with previous behavior, send onLost
// callbacks before onAvailable.
processNewlyLostListenRequests(nai);
if (oldBackground != nai.isBackgroundNetwork()) {
applyBackgroundChangeForRematch(nai);
}
processNewlySatisfiedListenRequests(nai);
}
for (final NetworkAgentInfo nai : inactiveNetworks) {
// For nascent networks, if connecting with no foreground request, skip broadcasting
// LOSING for backward compatibility. This is typical when mobile data connected while
// wifi connected with mobile data always-on enabled.
if (nai.isNascent()) continue;
notifyNetworkLosing(nai, now);
}
updateLegacyTypeTrackerAndVpnLockdownForRematch(changes, nais);
// Tear down all unneeded networks.
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
if (unneeded(nai, UnneededFor.TEARDOWN)) {
if (nai.getInactivityExpiry() > 0) {
// This network has active linger timers and no requests, but is not
// lingering. Linger it.
//
// One way (the only way?) this can happen if this network is unvalidated
// and became unneeded due to another network improving its score to the
// point where this network will no longer be able to satisfy any requests
// even if it validates.
if (updateInactivityState(nai, now)) {
notifyNetworkLosing(nai, now);
}
} else {
if (DBG) log("Reaping " + nai.toShortString());
teardownUnneededNetwork(nai);
}
}
}
}
/**
* Apply a change in background state resulting from rematching networks with requests.
*
* During rematch, a network may change background states by starting to satisfy or stopping
* to satisfy a foreground request. Listens don't count for this. When a network changes
* background states, its capabilities need to be updated and callbacks fired for the
* capability change.
*
* @param nai The network that changed background states
*/
private void applyBackgroundChangeForRematch(@NonNull final NetworkAgentInfo nai) {
final NetworkCapabilities newNc = mixInCapabilities(nai, nai.networkCapabilities);
if (Objects.equals(nai.networkCapabilities, newNc)) return;
updateNetworkPermissions(nai, newNc);
nai.getAndSetNetworkCapabilities(newNc);
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
@NonNull final NetworkReassignment changes,
@NonNull final Collection<NetworkAgentInfo> nais) {
final NetworkReassignment.RequestReassignment reassignmentOfDefault =
changes.getReassignment(mDefaultRequest);
final NetworkAgentInfo oldDefaultNetwork =
null != reassignmentOfDefault ? reassignmentOfDefault.mOldNetwork : null;
final NetworkAgentInfo newDefaultNetwork =
null != reassignmentOfDefault ? reassignmentOfDefault.mNewNetwork : null;
if (oldDefaultNetwork != newDefaultNetwork) {
// Maintain the illusion : since the legacy API only understands one network at a time,
// if the default network changed, apps should see a disconnected broadcast for the
// old default network before they see a connected broadcast for the new one.
if (oldDefaultNetwork != null) {
mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
oldDefaultNetwork, true);
}
if (newDefaultNetwork != null) {
// The new default network can be newly null if and only if the old default
// network doesn't satisfy the default request any more because it lost a
// capability.
mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
mLegacyTypeTracker.add(
newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
}
}
// Now that all the callbacks have been sent, send the legacy network broadcasts
// as needed. This is necessary so that legacy requests correctly bind dns
// requests to this network. The legacy users are listening for this broadcast
// and will generally do a dns request so they can ensureRouteToHost and if
// they do that before the callbacks happen they'll use the default network.
//
// TODO: Is there still a race here? The legacy broadcast will be sent after sending
// callbacks, but if apps can receive the broadcast before the callback, they still might
// have an inconsistent view of networking.
//
// This *does* introduce a race where if the user uses the new api
// (notification callbacks) and then uses the old api (getNetworkInfo(type))
// they may get old info. Reverse this after the old startUsing api is removed.
// This is on top of the multiple intent sequencing referenced in the todo above.
for (NetworkAgentInfo nai : nais) {
if (nai.everConnected) {
addNetworkToLegacyTypeTracker(nai);
}
}
}
private void issueNetworkNeeds() {
ensureRunningOnConnectivityServiceThread();
for (final NetworkOfferInfo noi : mNetworkOffers) {
issueNetworkNeeds(noi);
}
}
private void issueNetworkNeeds(@NonNull final NetworkOfferInfo noi) {
ensureRunningOnConnectivityServiceThread();
for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
informOffer(nri, noi.offer, mNetworkRanker);
}
}
/**
* Inform a NetworkOffer about any new situation of a request.
*
* This function handles updates to offers. A number of events may happen that require
* updating the registrant for this offer about the situation :
* • The offer itself was updated. This may lead the offer to no longer being able
* to satisfy a request or beat a satisfier (and therefore be no longer needed),
* or conversely being strengthened enough to beat the satisfier (and therefore
* start being needed)
* • The network satisfying a request changed (including cases where the request
* starts or stops being satisfied). The new network may be a stronger or weaker
* match than the old one, possibly affecting whether the offer is needed.
* • The network satisfying a request updated their score. This may lead the offer
* to no longer be able to beat it if the current satisfier got better, or
* conversely start being a good choice if the current satisfier got weaker.
*
* @param nri The request
* @param offer The offer. This may be an updated offer.
*/
private static void informOffer(@NonNull NetworkRequestInfo nri,
@NonNull final NetworkOffer offer, @NonNull final NetworkRanker networkRanker) {
final NetworkRequest activeRequest = nri.isBeingSatisfied() ? nri.getActiveRequest() : null;
final NetworkAgentInfo satisfier = null != activeRequest ? nri.getSatisfier() : null;
// Multi-layer requests have a currently active request, the one being satisfied.
// Since the system will try to bring up a better network than is currently satisfying
// the request, NetworkProviders need to be told the offers matching the requests *above*
// the currently satisfied one are needed, that the ones *below* the satisfied one are
// not needed, and the offer is needed for the active request iff the offer can beat
// the satisfier.
// For non-multilayer requests, the logic above gracefully degenerates to only the
// last case.
// To achieve this, the loop below will proceed in three steps. In a first phase, inform
// providers that the offer is needed for this request, until the active request is found.
// In a second phase, deal with the currently active request. In a third phase, inform
// the providers that offer is unneeded for the remaining requests.
// First phase : inform providers of all requests above the active request.
int i;
for (i = 0; nri.mRequests.size() > i; ++i) {
final NetworkRequest request = nri.mRequests.get(i);
if (activeRequest == request) break; // Found the active request : go to phase 2
if (!request.isRequest()) continue; // Listens/track defaults are never sent to offers
// Since this request is higher-priority than the one currently satisfied, if the
// offer can satisfy it, the provider should try and bring up the network for sure ;
// no need to even ask the ranker an offer that can satisfy is always better than
// no network. Hence tell the provider so unless it already knew.
if (request.canBeSatisfiedBy(offer.caps) && !offer.neededFor(request)) {
offer.onNetworkNeeded(request);
}
}
// Second phase : deal with the active request (if any)
if (null != activeRequest && activeRequest.isRequest()) {
final boolean oldNeeded = offer.neededFor(activeRequest);
// If an offer can satisfy the request, it is considered needed if it is currently
// served by this provider or if this offer can beat the current satisfier.
final boolean currentlyServing = satisfier != null
&& satisfier.factorySerialNumber == offer.providerId
&& activeRequest.canBeSatisfiedBy(offer.caps);
final boolean newNeeded = currentlyServing
|| networkRanker.mightBeat(activeRequest, satisfier, offer);
if (newNeeded != oldNeeded) {
if (newNeeded) {
offer.onNetworkNeeded(activeRequest);
} else {
// The offer used to be able to beat the satisfier. Now it can't.
offer.onNetworkUnneeded(activeRequest);
}
}
}
// Third phase : inform the providers that the offer isn't needed for any request
// below the active one.
for (++i /* skip the active request */; nri.mRequests.size() > i; ++i) {
final NetworkRequest request = nri.mRequests.get(i);
if (!request.isRequest()) continue; // Listens/track defaults are never sent to offers
// Since this request is lower-priority than the one currently satisfied, if the
// offer can satisfy it, the provider should not try and bring up the network.
// Hence tell the provider so unless it already knew.
if (offer.neededFor(request)) {
offer.onNetworkUnneeded(request);
}
}
}
private void addNetworkToLegacyTypeTracker(@NonNull final NetworkAgentInfo nai) {
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
if (nr.legacyType != TYPE_NONE && nr.isRequest()) {
// legacy type tracker filters out repeat adds
mLegacyTypeTracker.add(nr.legacyType, nai);
}
}
// A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above,
// because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest
// wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the
// newNetwork to the tracker explicitly (it's a no-op if it has already been added).
if (nai.isVPN()) {
mLegacyTypeTracker.add(TYPE_VPN, nai);
}
}
private void updateInetCondition(NetworkAgentInfo nai) {
// Don't bother updating until we've graduated to validated at least once.
if (!nai.everValidated) return;
// For now only update icons for the default connection.
// TODO: Update WiFi and cellular icons separately. b/17237507
if (!isDefaultNetwork(nai)) return;
int newInetCondition = nai.lastValidated ? 100 : 0;
// Don't repeat publish.
if (newInetCondition == mDefaultInetConditionPublished) return;
mDefaultInetConditionPublished = newInetCondition;
sendInetConditionBroadcast(nai.networkInfo);
}
@NonNull
private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) {
final NetworkInfo newInfo = new NetworkInfo(info);
// The suspended and roaming bits are managed in NetworkCapabilities.
final boolean suspended =
!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
if (suspended && info.getDetailedState() == NetworkInfo.DetailedState.CONNECTED) {
// Only override the state with SUSPENDED if the network is currently in CONNECTED
// state. This is because the network could have been suspended before connecting,
// or it could be disconnecting while being suspended, and in both these cases
// the state should not be overridden. Note that the only detailed state that
// maps to State.CONNECTED is DetailedState.CONNECTED, so there is also no need to
// worry about multiple different substates of CONNECTED.
newInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, info.getReason(),
info.getExtraInfo());
} else if (!suspended && info.getDetailedState() == NetworkInfo.DetailedState.SUSPENDED) {
// SUSPENDED state is currently only overridden from CONNECTED state. In the case the
// network agent is created, then goes to suspended, then goes out of suspended without
// ever setting connected. Check if network agent is ever connected to update the state.
newInfo.setDetailedState(nai.everConnected
? NetworkInfo.DetailedState.CONNECTED
: NetworkInfo.DetailedState.CONNECTING,
info.getReason(),
info.getExtraInfo());
}
newInfo.setRoaming(!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING));
return newInfo;
}
private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo info) {
final NetworkInfo newInfo = mixInInfo(networkAgent, info);
final NetworkInfo.State state = newInfo.getState();
NetworkInfo oldInfo = null;
synchronized (networkAgent) {
oldInfo = networkAgent.networkInfo;
networkAgent.networkInfo = newInfo;
}
if (DBG) {
log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from "
+ oldInfo.getState() + " to " + state);
}
if (!networkAgent.created
&& (state == NetworkInfo.State.CONNECTED
|| (state == NetworkInfo.State.CONNECTING && networkAgent.isVPN()))) {
// A network that has just connected has zero requests and is thus a foreground network.
networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
// If a rate limit has been configured and is applicable to this network (network
// provides internet connectivity), apply it.
// Note: in case of a system server crash, there is a very small chance that this
// leaves some interfaces rate limited (i.e. if the rate limit had been changed just
// before the crash and was never applied). One solution would be to delete all
// potential tc police filters every time this is called. Since this is an unlikely
// scenario in the first place (and worst case, the interface stays rate limited until
// the device is rebooted), this seems a little overkill.
if (canNetworkBeRateLimited(networkAgent) && mIngressRateLimit >= 0) {
mDeps.enableIngressRateLimit(networkAgent.linkProperties.getInterfaceName(),
mIngressRateLimit);
}
if (!createNativeNetwork(networkAgent)) return;
if (networkAgent.propagateUnderlyingCapabilities()) {
// Initialize the network's capabilities to their starting values according to the
// underlying networks. This ensures that the capabilities are correct before
// anything happens to the network.
updateCapabilitiesForNetwork(networkAgent);
}
networkAgent.created = true;
networkAgent.onNetworkCreated();
updateAccessUids(networkAgent, null, networkAgent.networkCapabilities);
}
if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
networkAgent.everConnected = true;
// NetworkCapabilities need to be set before sending the private DNS config to
// NetworkMonitor, otherwise NetworkMonitor cannot determine if validation is required.
networkAgent.getAndSetNetworkCapabilities(networkAgent.networkCapabilities);
handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig());
updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
null);
// Until parceled LinkProperties are sent directly to NetworkMonitor, the connect
// command must be sent after updating LinkProperties to maximize chances of
// NetworkMonitor seeing the correct LinkProperties when starting.
// TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
if (networkAgent.networkAgentConfig.acceptPartialConnectivity) {
networkAgent.networkMonitor().setAcceptPartialConnectivity();
}
final NetworkMonitorParameters params = new NetworkMonitorParameters();
params.networkAgentConfig = networkAgent.networkAgentConfig;
params.networkCapabilities = networkAgent.networkCapabilities;
params.linkProperties = new LinkProperties(networkAgent.linkProperties,
true /* parcelSensitiveFields */);
networkAgent.networkMonitor().notifyNetworkConnected(params);
scheduleUnvalidatedPrompt(networkAgent);
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable,
// capabilities, so it only needs to be done once on initial connect, not every time the
// network's capabilities change. Note that we do this before rematching the network,
// so we could decide to tear it down immediately afterwards. That's fine though - on
// disconnection NetworkAgents should stop any signal strength monitoring they have been
// doing.
updateSignalStrengthThresholds(networkAgent, "CONNECT", null);
// Before first rematching networks, put an inactivity timer without any request, this
// allows {@code updateInactivityState} to update the state accordingly and prevent
// tearing down for any {@code unneeded} evaluation in this period.
// Note that the timer will not be rescheduled since the expiry time is
// fixed after connection regardless of the network satisfying other requests or not.
// But it will be removed as soon as the network satisfies a request for the first time.
networkAgent.lingerRequest(NetworkRequest.REQUEST_ID_NONE,
SystemClock.elapsedRealtime(), mNascentDelayMs);
networkAgent.setInactive();
// Consider network even though it is not yet validated.
rematchAllNetworksAndRequests();
// This has to happen after matching the requests, because callbacks are just requests.
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
} else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.disconnect();
if (networkAgent.isVPN()) {
updateVpnUids(networkAgent, networkAgent.networkCapabilities, null);
}
disconnectAndDestroyNetwork(networkAgent);
if (networkAgent.isVPN()) {
// As the active or bound network changes for apps, broadcast the default proxy, as
// apps may need to update their proxy data. This is called after disconnecting from
// VPN to make sure we do not broadcast the old proxy data.
// TODO(b/122649188): send the broadcast only to VPN users.
mProxyTracker.sendProxyBroadcast();
}
} else if (networkAgent.created && (oldInfo.getState() == NetworkInfo.State.SUSPENDED ||
state == NetworkInfo.State.SUSPENDED)) {
mLegacyTypeTracker.update(networkAgent);
}
}
private void updateNetworkScore(@NonNull final NetworkAgentInfo nai, final NetworkScore score) {
if (VDBG || DDBG) log("updateNetworkScore for " + nai.toShortString() + " to " + score);
nai.setScore(score);
rematchAllNetworksAndRequests();
}
// Notify only this one new request of the current state. Transfer all the
// current state by calling NetworkCapabilities and LinkProperties callbacks
// so that callers can be guaranteed to have as close to atomicity in state
// transfer as can be supported by this current API.
protected void notifyNetworkAvailable(NetworkAgentInfo nai, NetworkRequestInfo nri) {
mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
if (nri.mPendingIntent != null) {
sendPendingIntentForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE);
// Attempt no subsequent state pushes where intents are involved.
return;
}
final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
final boolean metered = nai.networkCapabilities.isMetered();
final boolean vpnBlocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE,
getBlockedState(blockedReasons, metered, vpnBlocked));
}
// Notify the requests on this NAI that the network is now lingered.
private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) {
final int lingerTime = (int) (nai.getInactivityExpiry() - now);
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
}
private static int getBlockedState(int reasons, boolean metered, boolean vpnBlocked) {
if (!metered) reasons &= ~BLOCKED_METERED_REASON_MASK;
return vpnBlocked
? reasons | BLOCKED_REASON_LOCKDOWN_VPN
: reasons & ~BLOCKED_REASON_LOCKDOWN_VPN;
}
private void setUidBlockedReasons(int uid, @BlockedReason int blockedReasons) {
if (blockedReasons == BLOCKED_REASON_NONE) {
mUidBlockedReasons.delete(uid);
} else {
mUidBlockedReasons.put(uid, blockedReasons);
}
}
/**
* Notify of the blocked state apps with a registered callback matching a given NAI.
*
* Unlike other callbacks, blocked status is different between each individual uid. So for
* any given nai, all requests need to be considered according to the uid who filed it.
*
* @param nai The target NetworkAgentInfo.
* @param oldMetered True if the previous network capabilities were metered.
* @param newMetered True if the current network capabilities are metered.
* @param oldBlockedUidRanges list of UID ranges previously blocked by lockdown VPN.
* @param newBlockedUidRanges list of UID ranges blocked by lockdown VPN.
*/
private void maybeNotifyNetworkBlocked(NetworkAgentInfo nai, boolean oldMetered,
boolean newMetered, List<UidRange> oldBlockedUidRanges,
List<UidRange> newBlockedUidRanges) {
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
final boolean oldVpnBlocked = isUidBlockedByVpn(nri.mAsUid, oldBlockedUidRanges);
final boolean newVpnBlocked = (oldBlockedUidRanges != newBlockedUidRanges)
? isUidBlockedByVpn(nri.mAsUid, newBlockedUidRanges)
: oldVpnBlocked;
final int oldBlockedState = getBlockedState(blockedReasons, oldMetered, oldVpnBlocked);
final int newBlockedState = getBlockedState(blockedReasons, newMetered, newVpnBlocked);
if (oldBlockedState != newBlockedState) {
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
newBlockedState);
}
}
}
/**
* Notify apps with a given UID of the new blocked state according to new uid state.
* @param uid The uid for which the rules changed.
* @param blockedReasons The reasons for why an uid is blocked.
*/
private void maybeNotifyNetworkBlockedForNewState(int uid, @BlockedReason int blockedReasons) {
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
final boolean metered = nai.networkCapabilities.isMetered();
final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
final int oldBlockedState = getBlockedState(
mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered, vpnBlocked);
final int newBlockedState = getBlockedState(blockedReasons, metered, vpnBlocked);
if (oldBlockedState == newBlockedState) {
continue;
}
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (nri != null && nri.mAsUid == uid) {
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
newBlockedState);
}
}
}
}
@VisibleForTesting
protected void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
// The NetworkInfo we actually send out has no bearing on the real
// state of affairs. For example, if the default connection is mobile,
// and a request for HIPRI has just gone away, we need to pretend that
// HIPRI has just disconnected. So we need to set the type to HIPRI and
// the state to DISCONNECTED, even though the network is of type MOBILE
// and is still connected.
NetworkInfo info = new NetworkInfo(nai.networkInfo);
info.setType(type);
filterForLegacyLockdown(info);
if (state != DetailedState.DISCONNECTED) {
info.setDetailedState(state, null, info.getExtraInfo());
sendConnectedBroadcast(info);
} else {
info.setDetailedState(state, info.getReason(), info.getExtraInfo());
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
if (info.isFailover()) {
intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
nai.networkInfo.setFailover(false);
}
if (info.getReason() != null) {
intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
}
if (info.getExtraInfo() != null) {
intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
}
NetworkAgentInfo newDefaultAgent = null;
if (nai.isSatisfyingRequest(mDefaultRequest.mRequests.get(0).requestId)) {
newDefaultAgent = mDefaultRequest.getSatisfier();
if (newDefaultAgent != null) {
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO,
newDefaultAgent.networkInfo);
} else {
intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
}
}
intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION,
mDefaultInetConditionPublished);
sendStickyBroadcast(intent);
if (newDefaultAgent != null) {
sendConnectedBroadcast(newDefaultAgent.networkInfo);
}
}
}
protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType, int arg1) {
if (VDBG || DDBG) {
String notification = ConnectivityManager.getCallbackName(notifyType);
log("notifyType " + notification + " for " + networkAgent.toShortString());
}
for (int i = 0; i < networkAgent.numNetworkRequests(); i++) {
NetworkRequest nr = networkAgent.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (VDBG) log(" sending notification for " + nr);
if (nri.mPendingIntent == null) {
callCallbackForRequest(nri, networkAgent, notifyType, arg1);
} else {
sendPendingIntentForRequest(nri, networkAgent, notifyType);
}
}
}
protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) {
notifyNetworkCallbacks(networkAgent, notifyType, 0);
}
/**
* Returns the list of all interfaces that could be used by network traffic that does not
* explicitly specify a network. This includes the default network, but also all VPNs that are
* currently connected.
*
* Must be called on the handler thread.
*/
@NonNull
private ArrayList<Network> getDefaultNetworks() {
ensureRunningOnConnectivityServiceThread();
final ArrayList<Network> defaultNetworks = new ArrayList<>();
final Set<Integer> activeNetIds = new ArraySet<>();
for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
if (nri.isBeingSatisfied()) {
activeNetIds.add(nri.getSatisfier().network().netId);
}
}
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
if (nai.everConnected && (activeNetIds.contains(nai.network().netId) || nai.isVPN())) {
defaultNetworks.add(nai.network);
}
}
return defaultNetworks;
}
/**
* Notify NetworkStatsService that the set of active ifaces has changed, or that one of the
* active iface's tracked properties has changed.
*/
private void notifyIfacesChangedForNetworkStats() {
ensureRunningOnConnectivityServiceThread();
String activeIface = null;
LinkProperties activeLinkProperties = getActiveLinkProperties();
if (activeLinkProperties != null) {
activeIface = activeLinkProperties.getInterfaceName();
}
final UnderlyingNetworkInfo[] underlyingNetworkInfos = getAllVpnInfo();
try {
final ArrayList<NetworkStateSnapshot> snapshots = new ArrayList<>();
for (final NetworkStateSnapshot snapshot : getAllNetworkStateSnapshots()) {
snapshots.add(snapshot);
}
mStatsManager.notifyNetworkStatus(getDefaultNetworks(),
snapshots, activeIface, Arrays.asList(underlyingNetworkInfos));
} catch (Exception ignored) {
}
}
@Override
public String getCaptivePortalServerUrl() {
enforceNetworkStackOrSettingsPermission();
String settingUrl = mResources.get().getString(
R.string.config_networkCaptivePortalServerUrl);
if (!TextUtils.isEmpty(settingUrl)) {
return settingUrl;
}
settingUrl = Settings.Global.getString(mContext.getContentResolver(),
ConnectivitySettingsManager.CAPTIVE_PORTAL_HTTP_URL);
if (!TextUtils.isEmpty(settingUrl)) {
return settingUrl;
}
return DEFAULT_CAPTIVE_PORTAL_HTTP_URL;
}
@Override
public void startNattKeepalive(Network network, int intervalSeconds,
ISocketKeepaliveCallback cb, String srcAddr, int srcPort, String dstAddr) {
enforceKeepalivePermission();
mKeepaliveTracker.startNattKeepalive(
getNetworkAgentInfoForNetwork(network), null /* fd */,
intervalSeconds, cb,
srcAddr, srcPort, dstAddr, NattSocketKeepalive.NATT_PORT);
}
@Override
public void startNattKeepaliveWithFd(Network network, ParcelFileDescriptor pfd, int resourceId,
int intervalSeconds, ISocketKeepaliveCallback cb, String srcAddr,
String dstAddr) {
try {
final FileDescriptor fd = pfd.getFileDescriptor();
mKeepaliveTracker.startNattKeepalive(
getNetworkAgentInfoForNetwork(network), fd, resourceId,
intervalSeconds, cb,
srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
} finally {
// FileDescriptors coming from AIDL calls must be manually closed to prevent leaks.
// startNattKeepalive calls Os.dup(fd) before returning, so we can close immediately.
if (pfd != null && Binder.getCallingPid() != Process.myPid()) {
IoUtils.closeQuietly(pfd);
}
}
}
@Override
public void startTcpKeepalive(Network network, ParcelFileDescriptor pfd, int intervalSeconds,
ISocketKeepaliveCallback cb) {
try {
enforceKeepalivePermission();
final FileDescriptor fd = pfd.getFileDescriptor();
mKeepaliveTracker.startTcpKeepalive(
getNetworkAgentInfoForNetwork(network), fd, intervalSeconds, cb);
} finally {
// FileDescriptors coming from AIDL calls must be manually closed to prevent leaks.
// startTcpKeepalive calls Os.dup(fd) before returning, so we can close immediately.
if (pfd != null && Binder.getCallingPid() != Process.myPid()) {
IoUtils.closeQuietly(pfd);
}
}
}
@Override
public void stopKeepalive(Network network, int slot) {
mHandler.sendMessage(mHandler.obtainMessage(
NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE, slot, SocketKeepalive.SUCCESS, network));
}
@Override
public void factoryReset() {
enforceSettingsPermission();
final int uid = mDeps.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
if (mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_NETWORK_RESET,
UserHandle.getUserHandleForUid(uid))) {
return;
}
final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
ipMemoryStore.factoryReset();
// Turn airplane mode off
setAirplaneMode(false);
// restore private DNS settings to default mode (opportunistic)
if (!mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
UserHandle.getUserHandleForUid(uid))) {
ConnectivitySettingsManager.setPrivateDnsMode(mContext,
PRIVATE_DNS_MODE_OPPORTUNISTIC);
}
Settings.Global.putString(mContext.getContentResolver(),
ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public byte[] getNetworkWatchlistConfigHash() {
NetworkWatchlistManager nwm = mContext.getSystemService(NetworkWatchlistManager.class);
if (nwm == null) {
loge("Unable to get NetworkWatchlistManager");
return null;
}
// Redirect it to network watchlist service to access watchlist file and calculate hash.
return nwm.getWatchlistConfigHash();
}
private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
int[] transports = nai.networkCapabilities.getTransportTypes();
mMetricsLog.log(nai.network.getNetId(), transports, new NetworkEvent(evtype));
}
private static boolean toBool(int encodedBoolean) {
return encodedBoolean != 0; // Only 0 means false.
}
private static int encodeBool(boolean b) {
return b ? 1 : 0;
}
@Override
public int handleShellCommand(@NonNull ParcelFileDescriptor in,
@NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
@NonNull String[] args) {
return new ShellCmd().exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
err.getFileDescriptor(), args);
}
private class ShellCmd extends BasicShellCommandHandler {
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
final PrintWriter pw = getOutPrintWriter();
try {
switch (cmd) {
case "airplane-mode":
final String action = getNextArg();
if ("enable".equals(action)) {
setAirplaneMode(true);
return 0;
} else if ("disable".equals(action)) {
setAirplaneMode(false);
return 0;
} else if (action == null) {
final ContentResolver cr = mContext.getContentResolver();
final int enabled = Settings.Global.getInt(cr,
Settings.Global.AIRPLANE_MODE_ON);
pw.println(enabled == 0 ? "disabled" : "enabled");
return 0;
} else {
onHelp();
return -1;
}
default:
return handleDefaultCommands(cmd);
}
} catch (Exception e) {
pw.println(e);
}
return -1;
}
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
pw.println("Connectivity service commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" airplane-mode [enable|disable]");
pw.println(" Turn airplane mode on or off.");
pw.println(" airplane-mode");
pw.println(" Get airplane mode.");
}
}
private int getVpnType(@Nullable NetworkAgentInfo vpn) {
if (vpn == null) return VpnManager.TYPE_VPN_NONE;
final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
return ((VpnTransportInfo) ti).getType();
}
/**
* @param connectionInfo the connection to resolve.
* @return {@code uid} if the connection is found and the app has permission to observe it
* (e.g., if it is associated with the calling VPN app's tunnel) or {@code INVALID_UID} if the
* connection is not found.
*/
public int getConnectionOwnerUid(ConnectionInfo connectionInfo) {
if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) {
throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
}
final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol,
connectionInfo.local, connectionInfo.remote);
if (uid == INVALID_UID) return uid; // Not found.
// Connection owner UIDs are visible only to the network stack and to the VpnService-based
// VPN, if any, that applies to the UID that owns the connection.
if (checkNetworkStackPermission()) return uid;
final NetworkAgentInfo vpn = getVpnForUid(uid);
if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE
|| vpn.networkCapabilities.getOwnerUid() != mDeps.getCallingUid()) {
return INVALID_UID;
}
return uid;
}
/**
* Returns a IBinder to a TestNetworkService. Will be lazily created as needed.
*
* <p>The TestNetworkService must be run in the system server due to TUN creation.
*/
@Override
public IBinder startOrGetTestNetworkService() {
synchronized (mTNSLock) {
TestNetworkService.enforceTestNetworkPermissions(mContext);
if (mTNS == null) {
mTNS = new TestNetworkService(mContext);
}
return mTNS;
}
}
/**
* Handler used for managing all Connectivity Diagnostics related functions.
*
* @see android.net.ConnectivityDiagnosticsManager
*
* TODO(b/147816404): Explore moving ConnectivityDiagnosticsHandler to a separate file
*/
@VisibleForTesting
class ConnectivityDiagnosticsHandler extends Handler {
private final String mTag = ConnectivityDiagnosticsHandler.class.getSimpleName();
/**
* Used to handle ConnectivityDiagnosticsCallback registration events from {@link
* android.net.ConnectivityDiagnosticsManager}.
* obj = ConnectivityDiagnosticsCallbackInfo with IConnectivityDiagnosticsCallback and
* NetworkRequestInfo to be registered
*/
private static final int EVENT_REGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 1;
/**
* Used to handle ConnectivityDiagnosticsCallback unregister events from {@link
* android.net.ConnectivityDiagnosticsManager}.
* obj = the IConnectivityDiagnosticsCallback to be unregistered
* arg1 = the uid of the caller
*/
private static final int EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 2;
/**
* Event for {@link NetworkStateTrackerHandler} to trigger ConnectivityReport callbacks
* after processing {@link #EVENT_NETWORK_TESTED} events.
* obj = {@link ConnectivityReportEvent} representing ConnectivityReport info reported from
* NetworkMonitor.
* data = PersistableBundle of extras passed from NetworkMonitor.
*
* <p>See {@link ConnectivityService#EVENT_NETWORK_TESTED}.
*/
private static final int EVENT_NETWORK_TESTED = ConnectivityService.EVENT_NETWORK_TESTED;
/**
* Event for NetworkMonitor to inform ConnectivityService that a potential data stall has
* been detected on the network.
* obj = Long the timestamp (in millis) for when the suspected data stall was detected.
* arg1 = {@link DataStallReport#DetectionMethod} indicating the detection method.
* arg2 = NetID.
* data = PersistableBundle of extras passed from NetworkMonitor.
*/
private static final int EVENT_DATA_STALL_SUSPECTED = 4;
/**
* Event for ConnectivityDiagnosticsHandler to handle network connectivity being reported to
* the platform. This event will invoke {@link
* IConnectivityDiagnosticsCallback#onNetworkConnectivityReported} for permissioned
* callbacks.
* obj = ReportedNetworkConnectivityInfo with info on reported Network connectivity.
*/
private static final int EVENT_NETWORK_CONNECTIVITY_REPORTED = 5;
private ConnectivityDiagnosticsHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_REGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK: {
handleRegisterConnectivityDiagnosticsCallback(
(ConnectivityDiagnosticsCallbackInfo) msg.obj);
break;
}
case EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK: {
handleUnregisterConnectivityDiagnosticsCallback(
(IConnectivityDiagnosticsCallback) msg.obj, msg.arg1);
break;
}
case EVENT_NETWORK_TESTED: {
final ConnectivityReportEvent reportEvent =
(ConnectivityReportEvent) msg.obj;
handleNetworkTestedWithExtras(reportEvent, reportEvent.mExtras);
break;
}
case EVENT_DATA_STALL_SUSPECTED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
final Pair<Long, PersistableBundle> arg =
(Pair<Long, PersistableBundle>) msg.obj;
if (nai == null) break;
handleDataStallSuspected(nai, arg.first, msg.arg1, arg.second);
break;
}
case EVENT_NETWORK_CONNECTIVITY_REPORTED: {
handleNetworkConnectivityReported((ReportedNetworkConnectivityInfo) msg.obj);
break;
}
default: {
Log.e(mTag, "Unrecognized event in ConnectivityDiagnostics: " + msg.what);
}
}
}
}
/** Class used for cleaning up IConnectivityDiagnosticsCallback instances after their death. */
@VisibleForTesting
class ConnectivityDiagnosticsCallbackInfo implements Binder.DeathRecipient {
@NonNull private final IConnectivityDiagnosticsCallback mCb;
@NonNull private final NetworkRequestInfo mRequestInfo;
@NonNull private final String mCallingPackageName;
@VisibleForTesting
ConnectivityDiagnosticsCallbackInfo(
@NonNull IConnectivityDiagnosticsCallback cb,
@NonNull NetworkRequestInfo nri,
@NonNull String callingPackageName) {
mCb = cb;
mRequestInfo = nri;
mCallingPackageName = callingPackageName;
}
@Override
public void binderDied() {
log("ConnectivityDiagnosticsCallback IBinder died.");
unregisterConnectivityDiagnosticsCallback(mCb);
}
}
/**
* Class used for sending information from {@link
* NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} to the handler for processing it.
*/
private static class NetworkTestedResults {
private final int mNetId;
private final int mTestResult;
private final long mTimestampMillis;
@Nullable private final String mRedirectUrl;
private NetworkTestedResults(
int netId, int testResult, long timestampMillis, @Nullable String redirectUrl) {
mNetId = netId;
mTestResult = testResult;
mTimestampMillis = timestampMillis;
mRedirectUrl = redirectUrl;
}
}
/**
* Class used for sending information from {@link NetworkStateTrackerHandler} to {@link
* ConnectivityDiagnosticsHandler}.
*/
private static class ConnectivityReportEvent {
private final long mTimestampMillis;
@NonNull private final NetworkAgentInfo mNai;
private final PersistableBundle mExtras;
private ConnectivityReportEvent(long timestampMillis, @NonNull NetworkAgentInfo nai,
PersistableBundle p) {
mTimestampMillis = timestampMillis;
mNai = nai;
mExtras = p;
}
}
/**
* Class used for sending info for a call to {@link #reportNetworkConnectivity()} to {@link
* ConnectivityDiagnosticsHandler}.
*/
private static class ReportedNetworkConnectivityInfo {
public final boolean hasConnectivity;
public final boolean isNetworkRevalidating;
public final int reporterUid;
@NonNull public final NetworkAgentInfo nai;
private ReportedNetworkConnectivityInfo(
boolean hasConnectivity,
boolean isNetworkRevalidating,
int reporterUid,
@NonNull NetworkAgentInfo nai) {
this.hasConnectivity = hasConnectivity;
this.isNetworkRevalidating = isNetworkRevalidating;
this.reporterUid = reporterUid;
this.nai = nai;
}
}
private void handleRegisterConnectivityDiagnosticsCallback(
@NonNull ConnectivityDiagnosticsCallbackInfo cbInfo) {
ensureRunningOnConnectivityServiceThread();
final IConnectivityDiagnosticsCallback cb = cbInfo.mCb;
final IBinder iCb = cb.asBinder();
final NetworkRequestInfo nri = cbInfo.mRequestInfo;
// Connectivity Diagnostics are meant to be used with a single network request. It would be
// confusing for these networks to change when an NRI is satisfied in another layer.
if (nri.isMultilayerRequest()) {
throw new IllegalArgumentException("Connectivity Diagnostics do not support multilayer "
+ "network requests.");
}
// This means that the client registered the same callback multiple times. Do
// not override the previous entry, and exit silently.
if (mConnectivityDiagnosticsCallbacks.containsKey(iCb)) {
if (VDBG) log("Diagnostics callback is already registered");
// Decrement the reference count for this NetworkRequestInfo. The reference count is
// incremented when the NetworkRequestInfo is created as part of
// enforceRequestCountLimit().
nri.decrementRequestCount();
return;
}
mConnectivityDiagnosticsCallbacks.put(iCb, cbInfo);
try {
iCb.linkToDeath(cbInfo, 0);
} catch (RemoteException e) {
cbInfo.binderDied();
return;
}
// Once registered, provide ConnectivityReports for matching Networks
final List<NetworkAgentInfo> matchingNetworks = new ArrayList<>();
synchronized (mNetworkForNetId) {
for (int i = 0; i < mNetworkForNetId.size(); i++) {
final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
// Connectivity Diagnostics rejects multilayer requests at registration hence get(0)
if (nai.satisfies(nri.mRequests.get(0))) {
matchingNetworks.add(nai);
}
}
}
for (final NetworkAgentInfo nai : matchingNetworks) {
final ConnectivityReport report = nai.getConnectivityReport();
if (report == null) {
continue;
}
if (!checkConnectivityDiagnosticsPermissions(
nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
continue;
}
try {
cb.onConnectivityReportAvailable(report);
} catch (RemoteException e) {
// Exception while sending the ConnectivityReport. Move on to the next network.
}
}
}
private void handleUnregisterConnectivityDiagnosticsCallback(
@NonNull IConnectivityDiagnosticsCallback cb, int uid) {
ensureRunningOnConnectivityServiceThread();
final IBinder iCb = cb.asBinder();
final ConnectivityDiagnosticsCallbackInfo cbInfo =
mConnectivityDiagnosticsCallbacks.remove(iCb);
if (cbInfo == null) {
if (VDBG) log("Removing diagnostics callback that is not currently registered");
return;
}
final NetworkRequestInfo nri = cbInfo.mRequestInfo;
// Caller's UID must either be the registrants (if they are unregistering) or the System's
// (if the Binder died)
if (uid != nri.mUid && uid != Process.SYSTEM_UID) {
if (DBG) loge("Uid(" + uid + ") not registrant's (" + nri.mUid + ") or System's");
return;
}
// Decrement the reference count for this NetworkRequestInfo. The reference count is
// incremented when the NetworkRequestInfo is created as part of
// enforceRequestCountLimit().
nri.decrementRequestCount();
iCb.unlinkToDeath(cbInfo, 0);
}
private void handleNetworkTestedWithExtras(
@NonNull ConnectivityReportEvent reportEvent, @NonNull PersistableBundle extras) {
final NetworkAgentInfo nai = reportEvent.mNai;
final NetworkCapabilities networkCapabilities =
getNetworkCapabilitiesWithoutUids(nai.networkCapabilities);
final ConnectivityReport report =
new ConnectivityReport(
reportEvent.mNai.network,
reportEvent.mTimestampMillis,
nai.linkProperties,
networkCapabilities,
extras);
nai.setConnectivityReport(report);
final List<IConnectivityDiagnosticsCallback> results =
getMatchingPermissionedCallbacks(nai, Process.INVALID_UID);
for (final IConnectivityDiagnosticsCallback cb : results) {
try {
cb.onConnectivityReportAvailable(report);
} catch (RemoteException ex) {
loge("Error invoking onConnectivityReportAvailable", ex);
}
}
}
private void handleDataStallSuspected(
@NonNull NetworkAgentInfo nai, long timestampMillis, int detectionMethod,
@NonNull PersistableBundle extras) {
final NetworkCapabilities networkCapabilities =
getNetworkCapabilitiesWithoutUids(nai.networkCapabilities);
final DataStallReport report =
new DataStallReport(
nai.network,
timestampMillis,
detectionMethod,
nai.linkProperties,
networkCapabilities,
extras);
final List<IConnectivityDiagnosticsCallback> results =
getMatchingPermissionedCallbacks(nai, Process.INVALID_UID);
for (final IConnectivityDiagnosticsCallback cb : results) {
try {
cb.onDataStallSuspected(report);
} catch (RemoteException ex) {
loge("Error invoking onDataStallSuspected", ex);
}
}
}
private void handleNetworkConnectivityReported(
@NonNull ReportedNetworkConnectivityInfo reportedNetworkConnectivityInfo) {
final NetworkAgentInfo nai = reportedNetworkConnectivityInfo.nai;
final ConnectivityReport cachedReport = nai.getConnectivityReport();
// If the Network is being re-validated as a result of this call to
// reportNetworkConnectivity(), notify all permissioned callbacks. Otherwise, only notify
// permissioned callbacks registered by the reporter.
final List<IConnectivityDiagnosticsCallback> results =
getMatchingPermissionedCallbacks(
nai,
reportedNetworkConnectivityInfo.isNetworkRevalidating
? Process.INVALID_UID
: reportedNetworkConnectivityInfo.reporterUid);
for (final IConnectivityDiagnosticsCallback cb : results) {
try {
cb.onNetworkConnectivityReported(
nai.network, reportedNetworkConnectivityInfo.hasConnectivity);
} catch (RemoteException ex) {
loge("Error invoking onNetworkConnectivityReported", ex);
}
// If the Network isn't re-validating, also provide the cached report. If there is no
// cached report, the Network is still being validated and a report will be sent once
// validation is complete. Note that networks which never undergo validation will still
// have a cached ConnectivityReport with RESULT_SKIPPED.
if (!reportedNetworkConnectivityInfo.isNetworkRevalidating && cachedReport != null) {
try {
cb.onConnectivityReportAvailable(cachedReport);
} catch (RemoteException ex) {
loge("Error invoking onConnectivityReportAvailable", ex);
}
}
}
}
private NetworkCapabilities getNetworkCapabilitiesWithoutUids(@NonNull NetworkCapabilities nc) {
final NetworkCapabilities sanitized = new NetworkCapabilities(nc,
NetworkCapabilities.REDACT_ALL);
sanitized.setUids(null);
sanitized.setAdministratorUids(new int[0]);
sanitized.setOwnerUid(Process.INVALID_UID);
return sanitized;
}
/**
* Gets a list of ConnectivityDiagnostics callbacks that match the specified Network and uid.
*
* <p>If Process.INVALID_UID is specified, all matching callbacks will be returned.
*/
private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks(
@NonNull NetworkAgentInfo nai, int uid) {
final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>();
for (Entry<IBinder, ConnectivityDiagnosticsCallbackInfo> entry :
mConnectivityDiagnosticsCallbacks.entrySet()) {
final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue();
final NetworkRequestInfo nri = cbInfo.mRequestInfo;
// Connectivity Diagnostics rejects multilayer requests at registration hence get(0).
if (!nai.satisfies(nri.mRequests.get(0))) {
continue;
}
// UID for this callback must either be:
// - INVALID_UID (which sends callbacks to all UIDs), or
// - The callback's owner (the owner called reportNetworkConnectivity() and is being
// notified as a result)
if (uid != Process.INVALID_UID && uid != nri.mUid) {
continue;
}
if (!checkConnectivityDiagnosticsPermissions(
nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
continue;
}
results.add(entry.getValue().mCb);
}
return results;
}
private boolean isLocationPermissionRequiredForConnectivityDiagnostics(
@NonNull NetworkAgentInfo nai) {
// TODO(b/188483916): replace with a transport-agnostic location-aware check
return nai.networkCapabilities.hasTransport(TRANSPORT_WIFI);
}
private boolean hasLocationPermission(String packageName, int uid) {
// LocationPermissionChecker#checkLocationPermission can throw SecurityException if the uid
// and package name don't match. Throwing on the CS thread is not acceptable, so wrap the
// call in a try-catch.
try {
if (!mLocationPermissionChecker.checkLocationPermission(
packageName, null /* featureId */, uid, null /* message */)) {
return false;
}
} catch (SecurityException e) {
return false;
}
return true;
}
private boolean ownsVpnRunningOverNetwork(int uid, Network network) {
for (NetworkAgentInfo virtual : mNetworkAgentInfos) {
if (virtual.propagateUnderlyingCapabilities()
&& virtual.networkCapabilities.getOwnerUid() == uid
&& CollectionUtils.contains(virtual.declaredUnderlyingNetworks, network)) {
return true;
}
}
return false;
}
@VisibleForTesting
boolean checkConnectivityDiagnosticsPermissions(
int callbackPid, int callbackUid, NetworkAgentInfo nai, String callbackPackageName) {
if (checkNetworkStackPermission(callbackPid, callbackUid)) {
return true;
}
// Administrator UIDs also contains the Owner UID
final int[] administratorUids = nai.networkCapabilities.getAdministratorUids();
if (!CollectionUtils.contains(administratorUids, callbackUid)
&& !ownsVpnRunningOverNetwork(callbackUid, nai.network)) {
return false;
}
return !isLocationPermissionRequiredForConnectivityDiagnostics(nai)
|| hasLocationPermission(callbackPackageName, callbackUid);
}
@Override
public void registerConnectivityDiagnosticsCallback(
@NonNull IConnectivityDiagnosticsCallback callback,
@NonNull NetworkRequest request,
@NonNull String callingPackageName) {
Objects.requireNonNull(callback, "callback must not be null");
Objects.requireNonNull(request, "request must not be null");
Objects.requireNonNull(callingPackageName, "callingPackageName must not be null");
if (request.legacyType != TYPE_NONE) {
throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated."
+ " Please use NetworkCapabilities instead.");
}
final int callingUid = mDeps.getCallingUid();
mAppOpsManager.checkPackage(callingUid, callingPackageName);
// This NetworkCapabilities is only used for matching to Networks. Clear out its owner uid
// and administrator uids to be safe.
final NetworkCapabilities nc = new NetworkCapabilities(request.networkCapabilities);
restrictRequestUidsForCallerAndSetRequestorInfo(nc, callingUid, callingPackageName);
final NetworkRequest requestWithId =
new NetworkRequest(
nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN);
// NetworkRequestInfos created here count towards MAX_NETWORK_REQUESTS_PER_UID limit.
//
// nri is not bound to the death of callback. Instead, callback.bindToDeath() is set in
// handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the
// callback's binder death.
final NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, requestWithId);
final ConnectivityDiagnosticsCallbackInfo cbInfo =
new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName);
mConnectivityDiagnosticsHandler.sendMessage(
mConnectivityDiagnosticsHandler.obtainMessage(
ConnectivityDiagnosticsHandler
.EVENT_REGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK,
cbInfo));
}
@Override
public void unregisterConnectivityDiagnosticsCallback(
@NonNull IConnectivityDiagnosticsCallback callback) {
Objects.requireNonNull(callback, "callback must be non-null");
mConnectivityDiagnosticsHandler.sendMessage(
mConnectivityDiagnosticsHandler.obtainMessage(
ConnectivityDiagnosticsHandler
.EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK,
mDeps.getCallingUid(),
0,
callback));
}
@Override
public void simulateDataStall(int detectionMethod, long timestampMillis,
@NonNull Network network, @NonNull PersistableBundle extras) {
Objects.requireNonNull(network, "network must not be null");
Objects.requireNonNull(extras, "extras must not be null");
enforceAnyPermissionOf(android.Manifest.permission.MANAGE_TEST_NETWORKS,
android.Manifest.permission.NETWORK_STACK);
final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
if (!nc.hasTransport(TRANSPORT_TEST)) {
throw new SecurityException("Data Stall simulation is only possible for test networks");
}
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null || nai.creatorUid != mDeps.getCallingUid()) {
throw new SecurityException("Data Stall simulation is only possible for network "
+ "creators");
}
// Instead of passing the data stall directly to the ConnectivityDiagnostics handler, treat
// this as a Data Stall received directly from NetworkMonitor. This requires wrapping the
// Data Stall information as a DataStallReportParcelable and passing to
// #notifyDataStallSuspected. This ensures that unknown Data Stall detection methods are
// still passed to ConnectivityDiagnostics (with new detection methods masked).
final DataStallReportParcelable p = new DataStallReportParcelable();
p.timestampMillis = timestampMillis;
p.detectionMethod = detectionMethod;
if (hasDataStallDetectionMethod(p, DETECTION_METHOD_DNS_EVENTS)) {
p.dnsConsecutiveTimeouts = extras.getInt(KEY_DNS_CONSECUTIVE_TIMEOUTS);
}
if (hasDataStallDetectionMethod(p, DETECTION_METHOD_TCP_METRICS)) {
p.tcpPacketFailRate = extras.getInt(KEY_TCP_PACKET_FAIL_RATE);
p.tcpMetricsCollectionPeriodMillis = extras.getInt(
KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS);
}
notifyDataStallSuspected(p, network.getNetId());
}
private class NetdCallback extends BaseNetdUnsolicitedEventListener {
@Override
public void onInterfaceClassActivityChanged(boolean isActive, int transportType,
long timestampNs, int uid) {
mNetworkActivityTracker.setAndReportNetworkActive(isActive, transportType, timestampNs);
}
@Override
public void onInterfaceLinkStateChanged(String iface, boolean up) {
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
nai.clatd.interfaceLinkStateChanged(iface, up);
}
}
@Override
public void onInterfaceRemoved(String iface) {
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
nai.clatd.interfaceRemoved(iface);
}
}
}
private final LegacyNetworkActivityTracker mNetworkActivityTracker;
/**
* Class used for updating network activity tracking with netd and notify network activity
* changes.
*/
private static final class LegacyNetworkActivityTracker {
private static final int NO_UID = -1;
private final Context mContext;
private final INetd mNetd;
private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners =
new RemoteCallbackList<>();
// Indicate the current system default network activity is active or not.
@GuardedBy("mActiveIdleTimers")
private boolean mNetworkActive;
@GuardedBy("mActiveIdleTimers")
private final ArrayMap<String, IdleTimerParams> mActiveIdleTimers = new ArrayMap();
private final Handler mHandler;
private class IdleTimerParams {
public final int timeout;
public final int transportType;
IdleTimerParams(int timeout, int transport) {
this.timeout = timeout;
this.transportType = transport;
}
}
LegacyNetworkActivityTracker(@NonNull Context context, @NonNull Handler handler,
@NonNull INetd netd) {
mContext = context;
mNetd = netd;
mHandler = handler;
}
public void setAndReportNetworkActive(boolean active, int transportType, long tsNanos) {
sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active, tsNanos);
synchronized (mActiveIdleTimers) {
mNetworkActive = active;
// If there are no idle timers, it means that system is not monitoring
// activity, so the system default network for those default network
// unspecified apps is always considered active.
//
// TODO: If the mActiveIdleTimers is empty, netd will actually not send
// any network activity change event. Whenever this event is received,
// the mActiveIdleTimers should be always not empty. The legacy behavior
// is no-op. Remove to refer to mNetworkActive only.
if (mNetworkActive || mActiveIdleTimers.isEmpty()) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REPORT_NETWORK_ACTIVITY));
}
}
}
// The network activity should only be updated from ConnectivityService handler thread
// when mActiveIdleTimers lock is held.
@GuardedBy("mActiveIdleTimers")
private void reportNetworkActive() {
final int length = mNetworkActivityListeners.beginBroadcast();
if (DDBG) log("reportNetworkActive, notify " + length + " listeners");
try {
for (int i = 0; i < length; i++) {
try {
mNetworkActivityListeners.getBroadcastItem(i).onNetworkActive();
} catch (RemoteException | RuntimeException e) {
loge("Fail to send network activie to listener " + e);
}
}
} finally {
mNetworkActivityListeners.finishBroadcast();
}
}
@GuardedBy("mActiveIdleTimers")
public void handleReportNetworkActivity() {
synchronized (mActiveIdleTimers) {
reportNetworkActive();
}
}
// This is deprecated and only to support legacy use cases.
private int transportTypeToLegacyType(int type) {
switch (type) {
case NetworkCapabilities.TRANSPORT_CELLULAR:
return TYPE_MOBILE;
case NetworkCapabilities.TRANSPORT_WIFI:
return TYPE_WIFI;
case NetworkCapabilities.TRANSPORT_BLUETOOTH:
return TYPE_BLUETOOTH;
case NetworkCapabilities.TRANSPORT_ETHERNET:
return TYPE_ETHERNET;
default:
loge("Unexpected transport in transportTypeToLegacyType: " + type);
}
return ConnectivityManager.TYPE_NONE;
}
public void sendDataActivityBroadcast(int deviceType, boolean active, long tsNanos) {
final Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE);
intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType);
intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active);
intent.putExtra(ConnectivityManager.EXTRA_REALTIME_NS, tsNanos);
final long ident = Binder.clearCallingIdentity();
try {
mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL,
RECEIVE_DATA_ACTIVITY_CHANGE,
null /* resultReceiver */,
null /* scheduler */,
0 /* initialCode */,
null /* initialData */,
null /* initialExtra */);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
/**
* Setup data activity tracking for the given network.
*
* Every {@code setupDataActivityTracking} should be paired with a
* {@link #removeDataActivityTracking} for cleanup.
*/
private void setupDataActivityTracking(NetworkAgentInfo networkAgent) {
final String iface = networkAgent.linkProperties.getInterfaceName();
final int timeout;
final int type;
if (networkAgent.networkCapabilities.hasTransport(
NetworkCapabilities.TRANSPORT_CELLULAR)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_MOBILE,
10);
type = NetworkCapabilities.TRANSPORT_CELLULAR;
} else if (networkAgent.networkCapabilities.hasTransport(
NetworkCapabilities.TRANSPORT_WIFI)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_WIFI,
15);
type = NetworkCapabilities.TRANSPORT_WIFI;
} else {
return; // do not track any other networks
}
updateRadioPowerState(true /* isActive */, type);
if (timeout > 0 && iface != null) {
try {
synchronized (mActiveIdleTimers) {
// Networks start up.
mNetworkActive = true;
mActiveIdleTimers.put(iface, new IdleTimerParams(timeout, type));
mNetd.idletimerAddInterface(iface, timeout, Integer.toString(type));
reportNetworkActive();
}
} catch (Exception e) {
// You shall not crash!
loge("Exception in setupDataActivityTracking " + e);
}
}
}
/**
* Remove data activity tracking when network disconnects.
*/
private void removeDataActivityTracking(NetworkAgentInfo networkAgent) {
final String iface = networkAgent.linkProperties.getInterfaceName();
final NetworkCapabilities caps = networkAgent.networkCapabilities;
if (iface == null) return;
final int type;
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
type = NetworkCapabilities.TRANSPORT_CELLULAR;
} else if (caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
type = NetworkCapabilities.TRANSPORT_WIFI;
} else {
return; // do not track any other networks
}
try {
updateRadioPowerState(false /* isActive */, type);
synchronized (mActiveIdleTimers) {
final IdleTimerParams params = mActiveIdleTimers.remove(iface);
// The call fails silently if no idle timer setup for this interface
mNetd.idletimerRemoveInterface(iface, params.timeout,
Integer.toString(params.transportType));
}
} catch (Exception e) {
// You shall not crash!
loge("Exception in removeDataActivityTracking " + e);
}
}
/**
* Update data activity tracking when network state is updated.
*/
public void updateDataActivityTracking(NetworkAgentInfo newNetwork,
NetworkAgentInfo oldNetwork) {
if (newNetwork != null) {
setupDataActivityTracking(newNetwork);
}
if (oldNetwork != null) {
removeDataActivityTracking(oldNetwork);
}
}
private void updateRadioPowerState(boolean isActive, int transportType) {
final BatteryStatsManager bs = mContext.getSystemService(BatteryStatsManager.class);
switch (transportType) {
case NetworkCapabilities.TRANSPORT_CELLULAR:
bs.reportMobileRadioPowerState(isActive, NO_UID);
break;
case NetworkCapabilities.TRANSPORT_WIFI:
bs.reportWifiRadioPowerState(isActive, NO_UID);
break;
default:
logw("Untracked transport type:" + transportType);
}
}
public boolean isDefaultNetworkActive() {
synchronized (mActiveIdleTimers) {
// If there are no idle timers, it means that system is not monitoring activity,
// so the default network is always considered active.
//
// TODO : Distinguish between the cases where mActiveIdleTimers is empty because
// tracking is disabled (negative idle timer value configured), or no active default
// network. In the latter case, this reports active but it should report inactive.
return mNetworkActive || mActiveIdleTimers.isEmpty();
}
}
public void registerNetworkActivityListener(@NonNull INetworkActivityListener l) {
mNetworkActivityListeners.register(l);
}
public void unregisterNetworkActivityListener(@NonNull INetworkActivityListener l) {
mNetworkActivityListeners.unregister(l);
}
public void dump(IndentingPrintWriter pw) {
synchronized (mActiveIdleTimers) {
pw.print("mNetworkActive="); pw.println(mNetworkActive);
pw.println("Idle timers:");
for (HashMap.Entry<String, IdleTimerParams> ent : mActiveIdleTimers.entrySet()) {
pw.print(" "); pw.print(ent.getKey()); pw.println(":");
final IdleTimerParams params = ent.getValue();
pw.print(" timeout="); pw.print(params.timeout);
pw.print(" type="); pw.println(params.transportType);
}
}
}
}
/**
* Registers {@link QosSocketFilter} with {@link IQosCallback}.
*
* @param socketInfo the socket information
* @param callback the callback to register
*/
@Override
public void registerQosSocketCallback(@NonNull final QosSocketInfo socketInfo,
@NonNull final IQosCallback callback) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(socketInfo.getNetwork());
if (nai == null || nai.networkCapabilities == null) {
try {
callback.onError(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED);
} catch (final RemoteException ex) {
loge("registerQosCallbackInternal: RemoteException", ex);
}
return;
}
registerQosCallbackInternal(new QosSocketFilter(socketInfo), callback, nai);
}
/**
* Register a {@link IQosCallback} with base {@link QosFilter}.
*
* @param filter the filter to register
* @param callback the callback to register
* @param nai the agent information related to the filter's network
*/
@VisibleForTesting
public void registerQosCallbackInternal(@NonNull final QosFilter filter,
@NonNull final IQosCallback callback, @NonNull final NetworkAgentInfo nai) {
if (filter == null) throw new IllegalArgumentException("filter must be non-null");
if (callback == null) throw new IllegalArgumentException("callback must be non-null");
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
enforceConnectivityRestrictedNetworksPermission();
}
mQosCallbackTracker.registerCallback(callback, filter, nai);
}
/**
* Unregisters the given callback.
*
* @param callback the callback to unregister
*/
@Override
public void unregisterQosCallback(@NonNull final IQosCallback callback) {
Objects.requireNonNull(callback, "callback must be non-null");
mQosCallbackTracker.unregisterCallback(callback);
}
/**
* Request that a user profile is put by default on a network matching a given preference.
*
* See the documentation for the individual preferences for a description of the supported
* behaviors.
*
* @param profile the user profile for whih the preference is being set.
* @param preferences the list of profile network preferences for the
* provided profile.
* @param listener an optional listener to listen for completion of the operation.
*/
@Override
public void setProfileNetworkPreferences(
@NonNull final UserHandle profile,
@NonNull List<ProfileNetworkPreference> preferences,
@Nullable final IOnCompleteListener listener) {
Objects.requireNonNull(preferences);
Objects.requireNonNull(profile);
if (preferences.size() == 0) {
preferences.add((new ProfileNetworkPreference.Builder()).build());
}
PermissionUtils.enforceNetworkStackPermission(mContext);
if (DBG) {
log("setProfileNetworkPreferences " + profile + " to " + preferences);
}
if (profile.getIdentifier() < 0) {
throw new IllegalArgumentException("Must explicitly specify a user handle ("
+ "UserHandle.CURRENT not supported)");
}
final UserManager um = mContext.getSystemService(UserManager.class);
if (!um.isManagedProfile(profile.getIdentifier())) {
throw new IllegalArgumentException("Profile must be a managed profile");
}
final List<ProfileNetworkPreferenceList.Preference> preferenceList =
new ArrayList<ProfileNetworkPreferenceList.Preference>();
boolean allowFallback = true;
for (final ProfileNetworkPreference preference : preferences) {
final NetworkCapabilities nc;
switch (preference.getPreference()) {
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
nc = null;
if (preference.getPreferenceEnterpriseId() != 0) {
throw new IllegalArgumentException(
"Invalid enterprise identifier in setProfileNetworkPreferences");
}
break;
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK:
allowFallback = false;
// continue to process the enterprise preference.
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
if (!isEnterpriseIdentifierValid(preference.getPreferenceEnterpriseId())) {
throw new IllegalArgumentException(
"Invalid enterprise identifier in setProfileNetworkPreferences");
}
final Set<UidRange> uidRangeSet =
getUidListToBeAppliedForNetworkPreference(profile, preference);
if (!isRangeAlreadyInPreferenceList(preferenceList, uidRangeSet)) {
nc = createDefaultNetworkCapabilitiesForUidRangeSet(uidRangeSet);
} else {
throw new IllegalArgumentException(
"Overlapping uid range in setProfileNetworkPreferences");
}
nc.addCapability(NET_CAPABILITY_ENTERPRISE);
nc.addEnterpriseId(
preference.getPreferenceEnterpriseId());
nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
break;
default:
throw new IllegalArgumentException(
"Invalid preference in setProfileNetworkPreferences");
}
preferenceList.add(new ProfileNetworkPreferenceList.Preference(
profile, nc, allowFallback));
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE,
new Pair<>(preferenceList, listener)));
}
private Set<UidRange> getUidListToBeAppliedForNetworkPreference(
@NonNull final UserHandle profile,
@NonNull final ProfileNetworkPreference profileNetworkPreference) {
final UidRange profileUids = UidRange.createForUser(profile);
Set<UidRange> uidRangeSet = UidRangeUtils.convertListToUidRange(
profileNetworkPreference.getIncludedUids());
if (uidRangeSet.size() > 0) {
if (!UidRangeUtils.isRangeSetInUidRange(profileUids, uidRangeSet)) {
throw new IllegalArgumentException(
"Allow uid range is outside the uid range of profile.");
}
} else {
ArraySet<UidRange> disallowUidRangeSet = UidRangeUtils.convertListToUidRange(
profileNetworkPreference.getExcludedUids());
if (disallowUidRangeSet.size() > 0) {
if (!UidRangeUtils.isRangeSetInUidRange(profileUids, disallowUidRangeSet)) {
throw new IllegalArgumentException(
"disallow uid range is outside the uid range of profile.");
}
uidRangeSet = UidRangeUtils.removeRangeSetFromUidRange(profileUids,
disallowUidRangeSet);
} else {
uidRangeSet = new ArraySet<UidRange>();
uidRangeSet.add(profileUids);
}
}
return uidRangeSet;
}
private boolean isEnterpriseIdentifierValid(
@NetworkCapabilities.EnterpriseId int identifier) {
if ((identifier >= NET_ENTERPRISE_ID_1)
&& (identifier <= NET_ENTERPRISE_ID_5)) {
return true;
}
return false;
}
private void validateNetworkCapabilitiesOfProfileNetworkPreference(
@Nullable final NetworkCapabilities nc) {
if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
ensureRequestableCapabilities(nc);
}
private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
@NonNull final ProfileNetworkPreferenceList prefs) {
final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
for (final ProfileNetworkPreferenceList.Preference pref : prefs.preferences) {
// The NRI for a user should contain the request for capabilities.
// If fallback to default network is needed then NRI should include
// the request for the default network. Create an image of it to
// have the correct UIDs in it (also a request can only be part of one NRI, because
// of lookups in 1:1 associations like mNetworkRequests).
final ArrayList<NetworkRequest> nrs = new ArrayList<>();
nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities));
if (pref.allowFallback) {
nrs.add(createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
}
if (VDBG) {
loge("pref.capabilities.getUids():" + UidRange.fromIntRanges(
pref.capabilities.getUids()));
}
setNetworkRequestUids(nrs, UidRange.fromIntRanges(pref.capabilities.getUids()));
final NetworkRequestInfo nri = new NetworkRequestInfo(Process.myUid(), nrs,
PREFERENCE_ORDER_PROFILE);
result.add(nri);
}
return result;
}
/**
* Compare if the given UID range sets have the same UIDs.
*
*/
private boolean isRangeAlreadyInPreferenceList(
@NonNull List<ProfileNetworkPreferenceList.Preference> preferenceList,
@NonNull Set<UidRange> uidRangeSet) {
if (uidRangeSet.size() == 0 || preferenceList.size() == 0) {
return false;
}
for (ProfileNetworkPreferenceList.Preference pref : preferenceList) {
if (UidRangeUtils.doesRangeSetOverlap(
UidRange.fromIntRanges(pref.capabilities.getUids()), uidRangeSet)) {
return true;
}
}
return false;
}
private void handleSetProfileNetworkPreference(
@NonNull final List<ProfileNetworkPreferenceList.Preference> preferenceList,
@Nullable final IOnCompleteListener listener) {
for (final ProfileNetworkPreferenceList.Preference preference : preferenceList) {
validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
}
removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_PROFILE);
addPerAppDefaultNetworkRequests(
createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences));
// Finally, rematch.
rematchAllNetworksAndRequests();
if (null != listener) {
try {
listener.onComplete();
} catch (RemoteException e) {
loge("Listener for setProfileNetworkPreference has died");
}
}
}
@VisibleForTesting
@NonNull
ArraySet<NetworkRequestInfo> createNrisFromMobileDataPreferredUids(
@NonNull final Set<Integer> uids) {
final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
if (uids.size() == 0) {
// Should not create NetworkRequestInfo if no preferences. Without uid range in
// NetworkRequestInfo, makeDefaultForApps() would treat it as a illegal NRI.
if (DBG) log("Don't create NetworkRequestInfo because no preferences");
return nris;
}
final List<NetworkRequest> requests = new ArrayList<>();
// The NRI should be comprised of two layers:
// - The request for the mobile network preferred.
// - The request for the default network, for fallback.
requests.add(createDefaultInternetRequestForTransport(
TRANSPORT_CELLULAR, NetworkRequest.Type.REQUEST));
requests.add(createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
final Set<UidRange> ranges = new ArraySet<>();
for (final int uid : uids) {
ranges.add(new UidRange(uid, uid));
}
setNetworkRequestUids(requests, ranges);
nris.add(new NetworkRequestInfo(Process.myUid(), requests,
PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED));
return nris;
}
private void handleMobileDataPreferredUidsChanged() {
mMobileDataPreferredUids = ConnectivitySettingsManager.getMobileDataPreferredUids(mContext);
removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED);
addPerAppDefaultNetworkRequests(
createNrisFromMobileDataPreferredUids(mMobileDataPreferredUids));
// Finally, rematch.
rematchAllNetworksAndRequests();
}
private void handleIngressRateLimitChanged() {
final long oldIngressRateLimit = mIngressRateLimit;
mIngressRateLimit = ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
mContext);
for (final NetworkAgentInfo networkAgent : mNetworkAgentInfos) {
if (canNetworkBeRateLimited(networkAgent)) {
// If rate limit has previously been enabled, remove the old limit first.
if (oldIngressRateLimit >= 0) {
mDeps.disableIngressRateLimit(networkAgent.linkProperties.getInterfaceName());
}
if (mIngressRateLimit >= 0) {
mDeps.enableIngressRateLimit(networkAgent.linkProperties.getInterfaceName(),
mIngressRateLimit);
}
}
}
}
private boolean canNetworkBeRateLimited(@NonNull final NetworkAgentInfo networkAgent) {
final NetworkCapabilities agentCaps = networkAgent.networkCapabilities;
// Only test networks (they cannot hold NET_CAPABILITY_INTERNET) and networks that provide
// internet connectivity can be rate limited.
if (!agentCaps.hasCapability(NET_CAPABILITY_INTERNET) && !agentCaps.hasTransport(
TRANSPORT_TEST)) {
return false;
}
final String iface = networkAgent.linkProperties.getInterfaceName();
if (iface == null) {
// This may happen in tests, but if there is no interface then there is nothing that
// can be rate limited.
loge("canNetworkBeRateLimited: LinkProperties#getInterfaceName returns null");
return false;
}
return true;
}
private void enforceAutomotiveDevice() {
PermissionUtils.enforceSystemFeature(mContext, PackageManager.FEATURE_AUTOMOTIVE,
"setOemNetworkPreference() is only available on automotive devices.");
}
/**
* Used by automotive devices to set the network preferences used to direct traffic at an
* application level as per the given OemNetworkPreferences. An example use-case would be an
* automotive OEM wanting to provide connectivity for applications critical to the usage of a
* vehicle via a particular network.
*
* Calling this will overwrite the existing preference.
*
* @param preference {@link OemNetworkPreferences} The application network preference to be set.
* @param listener {@link ConnectivityManager.OnCompleteListener} Listener used
* to communicate completion of setOemNetworkPreference();
*/
@Override
public void setOemNetworkPreference(
@NonNull final OemNetworkPreferences preference,
@Nullable final IOnCompleteListener listener) {
Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
// Only bypass the permission/device checks if this is a valid test request.
if (isValidTestOemNetworkPreference(preference)) {
enforceManageTestNetworksPermission();
} else {
enforceAutomotiveDevice();
enforceOemNetworkPreferencesPermission();
validateOemNetworkPreferences(preference);
}
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE,
new Pair<>(preference, listener)));
}
/**
* Check the validity of an OEM network preference to be used for testing purposes.
* @param preference the preference to validate
* @return true if this is a valid OEM network preference test request.
*/
private boolean isValidTestOemNetworkPreference(
@NonNull final OemNetworkPreferences preference) {
// Allow for clearing of an existing OemNetworkPreference used for testing.
// This isn't called on the handler thread so it is possible that mOemNetworkPreferences
// changes after this check is complete. This is an unlikely scenario as calling of this API
// is controlled by the OEM therefore the added complexity is not worth adding given those
// circumstances. That said, it is an edge case to be aware of hence this comment.
final boolean isValidTestClearPref = preference.getNetworkPreferences().size() == 0
&& isTestOemNetworkPreference(mOemNetworkPreferences);
return isTestOemNetworkPreference(preference) || isValidTestClearPref;
}
private boolean isTestOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
final Map<String, Integer> prefMap = preference.getNetworkPreferences();
return prefMap.size() == 1
&& (prefMap.containsValue(OEM_NETWORK_PREFERENCE_TEST)
|| prefMap.containsValue(OEM_NETWORK_PREFERENCE_TEST_ONLY));
}
private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) {
for (@OemNetworkPreferences.OemNetworkPreference final int pref
: preference.getNetworkPreferences().values()) {
if (pref <= 0 || OemNetworkPreferences.OEM_NETWORK_PREFERENCE_MAX < pref) {
throw new IllegalArgumentException(
OemNetworkPreferences.oemNetworkPreferenceToString(pref)
+ " is an invalid value.");
}
}
}
private void handleSetOemNetworkPreference(
@NonNull final OemNetworkPreferences preference,
@Nullable final IOnCompleteListener listener) {
Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
if (DBG) {
log("set OEM network preferences :" + preference.toString());
}
mOemNetworkPreferencesLogs.log("UPDATE INITIATED: " + preference);
removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_OEM);
addPerAppDefaultNetworkRequests(new OemNetworkRequestFactory()
.createNrisFromOemNetworkPreferences(preference));
mOemNetworkPreferences = preference;
if (null != listener) {
try {
listener.onComplete();
} catch (RemoteException e) {
loge("Can't send onComplete in handleSetOemNetworkPreference", e);
}
}
}
private void removeDefaultNetworkRequestsForPreference(final int preferenceOrder) {
// Skip the requests which are set by other network preference. Because the uid range rules
// should stay in netd.
final Set<NetworkRequestInfo> requests = new ArraySet<>(mDefaultNetworkRequests);
requests.removeIf(request -> request.mPreferenceOrder != preferenceOrder);
handleRemoveNetworkRequests(requests);
}
private void addPerAppDefaultNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
ensureRunningOnConnectivityServiceThread();
mDefaultNetworkRequests.addAll(nris);
final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate =
getPerAppCallbackRequestsToUpdate();
final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris);
handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
nrisToRegister.addAll(
createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate));
handleRegisterNetworkRequests(nrisToRegister);
}
/**
* All current requests that are tracking the default network need to be assessed as to whether
* or not the current set of per-application default requests will be changing their default
* network. If so, those requests will need to be updated so that they will send callbacks for
* default network changes at the appropriate time. Additionally, those requests tracking the
* default that were previously updated by this flow will need to be reassessed.
* @return the nris which will need to be updated.
*/
private ArraySet<NetworkRequestInfo> getPerAppCallbackRequestsToUpdate() {
final ArraySet<NetworkRequestInfo> defaultCallbackRequests = new ArraySet<>();
// Get the distinct nris to check since for multilayer requests, it is possible to have the
// same nri in the map's values for each of its NetworkRequest objects.
final ArraySet<NetworkRequestInfo> nris = new ArraySet<>(mNetworkRequests.values());
for (final NetworkRequestInfo nri : nris) {
// Include this nri if it is currently being tracked.
if (isPerAppTrackedNri(nri)) {
defaultCallbackRequests.add(nri);
continue;
}
// We only track callbacks for requests tracking the default.
if (NetworkRequest.Type.TRACK_DEFAULT != nri.mRequests.get(0).type) {
continue;
}
// Include this nri if it will be tracked by the new per-app default requests.
final boolean isNriGoingToBeTracked =
getDefaultRequestTrackingUid(nri.mAsUid) != mDefaultRequest;
if (isNriGoingToBeTracked) {
defaultCallbackRequests.add(nri);
}
}
return defaultCallbackRequests;
}
/**
* Create nris for those network requests that are currently tracking the default network that
* are being controlled by a per-application default.
* @param perAppCallbackRequestsForUpdate the baseline network requests to be used as the
* foundation when creating the nri. Important items include the calling uid's original
* NetworkRequest to be used when mapping callbacks as well as the caller's uid and name. These
* requests are assumed to have already been validated as needing to be updated.
* @return the Set of nris to use when registering network requests.
*/
private ArraySet<NetworkRequestInfo> createPerAppCallbackRequestsToRegister(
@NonNull final ArraySet<NetworkRequestInfo> perAppCallbackRequestsForUpdate) {
final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>();
for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) {
final NetworkRequestInfo trackingNri =
getDefaultRequestTrackingUid(callbackRequest.mAsUid);
// If this nri is not being tracked, then change it back to an untracked nri.
if (trackingNri == mDefaultRequest) {
callbackRequestsToRegister.add(new NetworkRequestInfo(
callbackRequest,
Collections.singletonList(callbackRequest.getNetworkRequestForCallback())));
continue;
}
final NetworkRequest request = callbackRequest.mRequests.get(0);
callbackRequestsToRegister.add(new NetworkRequestInfo(
callbackRequest,
copyNetworkRequestsForUid(
trackingNri.mRequests, callbackRequest.mAsUid,
callbackRequest.mUid, request.getRequestorPackageName())));
}
return callbackRequestsToRegister;
}
private static void setNetworkRequestUids(@NonNull final List<NetworkRequest> requests,
@NonNull final Set<UidRange> uids) {
for (final NetworkRequest req : requests) {
req.networkCapabilities.setUids(UidRange.toIntRanges(uids));
}
}
/**
* Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}.
*/
@VisibleForTesting
final class OemNetworkRequestFactory {
ArraySet<NetworkRequestInfo> createNrisFromOemNetworkPreferences(
@NonNull final OemNetworkPreferences preference) {
final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
final SparseArray<Set<Integer>> uids =
createUidsFromOemNetworkPreferences(preference);
for (int i = 0; i < uids.size(); i++) {
final int key = uids.keyAt(i);
final Set<Integer> value = uids.valueAt(i);
final NetworkRequestInfo nri = createNriFromOemNetworkPreferences(key, value);
// No need to add an nri without any requests.
if (0 == nri.mRequests.size()) {
continue;
}
nris.add(nri);
}
return nris;
}
private SparseArray<Set<Integer>> createUidsFromOemNetworkPreferences(
@NonNull final OemNetworkPreferences preference) {
final SparseArray<Set<Integer>> prefToUids = new SparseArray<>();
final PackageManager pm = mContext.getPackageManager();
final List<UserHandle> users =
mContext.getSystemService(UserManager.class).getUserHandles(true);
if (null == users || users.size() == 0) {
if (VDBG || DDBG) {
log("No users currently available for setting the OEM network preference.");
}
return prefToUids;
}
for (final Map.Entry<String, Integer> entry :
preference.getNetworkPreferences().entrySet()) {
@OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue();
// Add the rules for all users as this policy is device wide.
for (final UserHandle user : users) {
try {
final int uid = pm.getApplicationInfoAsUser(entry.getKey(), 0, user).uid;
if (!prefToUids.contains(pref)) {
prefToUids.put(pref, new ArraySet<>());
}
prefToUids.get(pref).add(uid);
} catch (PackageManager.NameNotFoundException e) {
// Although this may seem like an error scenario, it is ok that uninstalled
// packages are sent on a network preference as the system will watch for
// package installations associated with this network preference and update
// accordingly. This is done to minimize race conditions on app install.
continue;
}
}
}
return prefToUids;
}
private NetworkRequestInfo createNriFromOemNetworkPreferences(
@OemNetworkPreferences.OemNetworkPreference final int preference,
@NonNull final Set<Integer> uids) {
final List<NetworkRequest> requests = new ArrayList<>();
// Requests will ultimately be evaluated by order of insertion therefore it matters.
switch (preference) {
case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID:
requests.add(createUnmeteredNetworkRequest());
requests.add(createOemPaidNetworkRequest());
requests.add(createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
break;
case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK:
requests.add(createUnmeteredNetworkRequest());
requests.add(createOemPaidNetworkRequest());
break;
case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY:
requests.add(createOemPaidNetworkRequest());
break;
case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY:
requests.add(createOemPrivateNetworkRequest());
break;
case OEM_NETWORK_PREFERENCE_TEST:
requests.add(createUnmeteredNetworkRequest());
requests.add(createTestNetworkRequest());
requests.add(createDefaultRequest());
break;
case OEM_NETWORK_PREFERENCE_TEST_ONLY:
requests.add(createTestNetworkRequest());
break;
default:
// This should never happen.
throw new IllegalArgumentException("createNriFromOemNetworkPreferences()"
+ " called with invalid preference of " + preference);
}
final ArraySet<UidRange> ranges = new ArraySet<>();
for (final int uid : uids) {
ranges.add(new UidRange(uid, uid));
}
setNetworkRequestUids(requests, ranges);
return new NetworkRequestInfo(Process.myUid(), requests, PREFERENCE_ORDER_OEM);
}
private NetworkRequest createUnmeteredNetworkRequest() {
final NetworkCapabilities netcap = createDefaultPerAppNetCap()
.addCapability(NET_CAPABILITY_NOT_METERED)
.addCapability(NET_CAPABILITY_VALIDATED);
return createNetworkRequest(NetworkRequest.Type.LISTEN, netcap);
}
private NetworkRequest createOemPaidNetworkRequest() {
// NET_CAPABILITY_OEM_PAID is a restricted capability.
final NetworkCapabilities netcap = createDefaultPerAppNetCap()
.addCapability(NET_CAPABILITY_OEM_PAID)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
}
private NetworkRequest createOemPrivateNetworkRequest() {
// NET_CAPABILITY_OEM_PRIVATE is a restricted capability.
final NetworkCapabilities netcap = createDefaultPerAppNetCap()
.addCapability(NET_CAPABILITY_OEM_PRIVATE)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
}
private NetworkCapabilities createDefaultPerAppNetCap() {
final NetworkCapabilities netcap = new NetworkCapabilities();
netcap.addCapability(NET_CAPABILITY_INTERNET);
netcap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
return netcap;
}
private NetworkRequest createTestNetworkRequest() {
final NetworkCapabilities netcap = new NetworkCapabilities();
netcap.clearAll();
netcap.addTransportType(TRANSPORT_TEST);
return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
}
}
@Override
public void updateMeteredNetworkAllowList(final int uid, final boolean add) {
enforceNetworkStackOrSettingsPermission();
try {
if (add) {
mBpfNetMaps.addNiceApp(uid);
} else {
mBpfNetMaps.removeNiceApp(uid);
}
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@Override
public void updateMeteredNetworkDenyList(final int uid, final boolean add) {
enforceNetworkStackOrSettingsPermission();
try {
if (add) {
mBpfNetMaps.addNaughtyApp(uid);
} else {
mBpfNetMaps.removeNaughtyApp(uid);
}
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@Override
public void updateFirewallRule(final int chain, final int uid, final boolean allow) {
enforceNetworkStackOrSettingsPermission();
try {
mBpfNetMaps.setUidRule(chain, uid,
allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@Override
public void setFirewallChainEnabled(final int chain, final boolean enable) {
enforceNetworkStackOrSettingsPermission();
try {
mBpfNetMaps.setChildChain(chain, enable);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@Override
public void replaceFirewallChain(final int chain, final int[] uids) {
enforceNetworkStackOrSettingsPermission();
try {
switch (chain) {
case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
mBpfNetMaps.replaceUidChain("fw_dozable", true /* isAllowList */, uids);
break;
case ConnectivityManager.FIREWALL_CHAIN_STANDBY:
mBpfNetMaps.replaceUidChain("fw_standby", false /* isAllowList */, uids);
break;
case ConnectivityManager.FIREWALL_CHAIN_POWERSAVE:
mBpfNetMaps.replaceUidChain("fw_powersave", true /* isAllowList */, uids);
break;
case ConnectivityManager.FIREWALL_CHAIN_RESTRICTED:
mBpfNetMaps.replaceUidChain("fw_restricted", true /* isAllowList */, uids);
break;
case ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY:
mBpfNetMaps.replaceUidChain("fw_low_power_standby", true /* isAllowList */,
uids);
break;
default:
throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
+ chain);
}
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
@Override
public void swapActiveStatsMap() {
enforceNetworkStackOrSettingsPermission();
try {
mBpfNetMaps.swapActiveStatsMap();
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
}
}