/*
|
* Copyright (C) 2007 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.CONNECTIVITY_INTERNAL;
|
import static android.Manifest.permission.NETWORK_SETTINGS;
|
import static android.Manifest.permission.NETWORK_STACK;
|
import static android.Manifest.permission.SHUTDOWN;
|
import static android.net.INetd.FIREWALL_BLACKLIST;
|
import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
|
import static android.net.INetd.FIREWALL_CHAIN_NONE;
|
import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
|
import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
|
import static android.net.INetd.FIREWALL_RULE_ALLOW;
|
import static android.net.INetd.FIREWALL_RULE_DENY;
|
import static android.net.INetd.FIREWALL_WHITELIST;
|
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
|
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
|
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
|
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
|
import static android.net.NetworkStats.SET_DEFAULT;
|
import static android.net.NetworkStats.STATS_PER_UID;
|
import static android.net.NetworkStats.TAG_ALL;
|
import static android.net.NetworkStats.TAG_NONE;
|
import static android.net.NetworkStats.UID_ALL;
|
import static android.net.TrafficStats.UID_TETHERING;
|
|
import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
|
|
import android.annotation.NonNull;
|
import android.app.ActivityManager;
|
import android.content.Context;
|
import android.net.ConnectivityManager;
|
import android.net.INetd;
|
import android.net.INetdUnsolicitedEventListener;
|
import android.net.INetworkManagementEventObserver;
|
import android.net.ITetheringStatsProvider;
|
import android.net.InetAddresses;
|
import android.net.InterfaceConfiguration;
|
import android.net.InterfaceConfigurationParcel;
|
import android.net.IpPrefix;
|
import android.net.LinkAddress;
|
import android.net.Network;
|
import android.net.NetworkPolicyManager;
|
import android.net.NetworkStack;
|
import android.net.NetworkStats;
|
import android.net.NetworkUtils;
|
import android.net.RouteInfo;
|
import android.net.TetherStatsParcel;
|
import android.net.UidRange;
|
import android.net.UidRangeParcel;
|
import android.net.util.NetdService;
|
import android.os.BatteryStats;
|
import android.os.Binder;
|
import android.os.Handler;
|
import android.os.IBinder;
|
import android.os.INetworkActivityListener;
|
import android.os.INetworkManagementService;
|
import android.os.Process;
|
import android.os.RemoteCallbackList;
|
import android.os.RemoteException;
|
import android.os.ServiceManager;
|
import android.os.ServiceSpecificException;
|
import android.os.StrictMode;
|
import android.os.SystemClock;
|
import android.os.SystemProperties;
|
import android.os.Trace;
|
import android.telephony.DataConnectionRealTimeInfo;
|
import android.text.TextUtils;
|
import android.util.Log;
|
import android.util.Slog;
|
import android.util.SparseBooleanArray;
|
import android.util.SparseIntArray;
|
import android.util.StatsLog;
|
|
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.app.IBatteryStats;
|
import com.android.internal.util.DumpUtils;
|
import com.android.internal.util.HexDump;
|
import com.android.internal.util.Preconditions;
|
import com.android.server.net.NetworkStatsFactory;
|
|
import com.google.android.collect.Maps;
|
|
import java.io.BufferedReader;
|
import java.io.DataInputStream;
|
import java.io.FileDescriptor;
|
import java.io.FileInputStream;
|
import java.io.IOException;
|
import java.io.InputStreamReader;
|
import java.io.PrintWriter;
|
import java.net.InetAddress;
|
import java.net.InterfaceAddress;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* @hide
|
*/
|
public class NetworkManagementService extends INetworkManagementService.Stub {
|
|
/**
|
* Helper class that encapsulates NetworkManagementService dependencies and makes them
|
* easier to mock in unit tests.
|
*/
|
static class SystemServices {
|
public IBinder getService(String name) {
|
return ServiceManager.getService(name);
|
}
|
public void registerLocalService(NetworkManagementInternal nmi) {
|
LocalServices.addService(NetworkManagementInternal.class, nmi);
|
}
|
public INetd getNetd() {
|
return NetdService.get();
|
}
|
}
|
|
private static final String TAG = "NetworkManagement";
|
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
|
|
private static final int MAX_UID_RANGES_PER_COMMAND = 10;
|
|
/**
|
* Name representing {@link #setGlobalAlert(long)} limit when delivered to
|
* {@link INetworkManagementEventObserver#limitReached(String, String)}.
|
*/
|
public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
|
|
static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1;
|
|
static final boolean MODIFY_OPERATION_ADD = true;
|
static final boolean MODIFY_OPERATION_REMOVE = false;
|
|
/**
|
* Binder context for this service
|
*/
|
private final Context mContext;
|
|
private final Handler mDaemonHandler;
|
|
private final SystemServices mServices;
|
|
private INetd mNetdService;
|
|
private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener;
|
|
private IBatteryStats mBatteryStats;
|
|
private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
|
new RemoteCallbackList<>();
|
|
private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
|
|
@GuardedBy("mTetheringStatsProviders")
|
private final HashMap<ITetheringStatsProvider, String>
|
mTetheringStatsProviders = Maps.newHashMap();
|
|
/**
|
* If both locks need to be held, then they should be obtained in the order:
|
* first {@link #mQuotaLock} and then {@link #mRulesLock}.
|
*/
|
private final Object mQuotaLock = new Object();
|
private final Object mRulesLock = new Object();
|
|
/** Set of interfaces with active quotas. */
|
@GuardedBy("mQuotaLock")
|
private HashMap<String, Long> mActiveQuotas = Maps.newHashMap();
|
/** Set of interfaces with active alerts. */
|
@GuardedBy("mQuotaLock")
|
private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
|
/** Set of UIDs blacklisted on metered networks. */
|
@GuardedBy("mRulesLock")
|
private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray();
|
/** Set of UIDs whitelisted on metered networks. */
|
@GuardedBy("mRulesLock")
|
private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray();
|
/** Set of UIDs with cleartext penalties. */
|
@GuardedBy("mQuotaLock")
|
private SparseIntArray mUidCleartextPolicy = new SparseIntArray();
|
/** Set of UIDs that are to be blocked/allowed by firewall controller. */
|
@GuardedBy("mRulesLock")
|
private SparseIntArray mUidFirewallRules = new SparseIntArray();
|
/**
|
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
|
* to application idles.
|
*/
|
@GuardedBy("mRulesLock")
|
private SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
|
/**
|
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
|
* to device idles.
|
*/
|
@GuardedBy("mRulesLock")
|
private SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
|
/**
|
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
|
* to device on power-save mode.
|
*/
|
@GuardedBy("mRulesLock")
|
private SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
|
/** Set of states for the child firewall chains. True if the chain is active. */
|
@GuardedBy("mRulesLock")
|
final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
|
|
@GuardedBy("mQuotaLock")
|
private volatile boolean mDataSaverMode;
|
|
private final Object mIdleTimerLock = new Object();
|
/** Set of interfaces with active idle timers. */
|
private static class IdleTimerParams {
|
public final int timeout;
|
public final int type;
|
public int networkCount;
|
|
IdleTimerParams(int timeout, int type) {
|
this.timeout = timeout;
|
this.type = type;
|
this.networkCount = 1;
|
}
|
}
|
private HashMap<String, IdleTimerParams> mActiveIdleTimers = Maps.newHashMap();
|
|
private volatile boolean mFirewallEnabled;
|
private volatile boolean mStrictEnabled;
|
|
private boolean mMobileActivityFromRadio = false;
|
private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
|
private int mLastPowerStateFromWifi = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
|
|
private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners =
|
new RemoteCallbackList<>();
|
private boolean mNetworkActive;
|
|
/**
|
* Constructs a new NetworkManagementService instance
|
*
|
* @param context Binder context for this service
|
*/
|
private NetworkManagementService(
|
Context context, SystemServices services) {
|
mContext = context;
|
mServices = services;
|
|
mDaemonHandler = new Handler(FgThread.get().getLooper());
|
|
mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
|
|
mServices.registerLocalService(new LocalService());
|
|
synchronized (mTetheringStatsProviders) {
|
mTetheringStatsProviders.put(new NetdTetheringStatsProvider(), "netd");
|
}
|
}
|
|
@VisibleForTesting
|
NetworkManagementService() {
|
mContext = null;
|
mDaemonHandler = null;
|
mServices = null;
|
mNetdUnsolicitedEventListener = null;
|
}
|
|
static NetworkManagementService create(Context context, SystemServices services)
|
throws InterruptedException {
|
final NetworkManagementService service =
|
new NetworkManagementService(context, services);
|
if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
|
if (DBG) Slog.d(TAG, "Connecting native netd service");
|
service.connectNativeNetdService();
|
if (DBG) Slog.d(TAG, "Connected");
|
return service;
|
}
|
|
public static NetworkManagementService create(Context context) throws InterruptedException {
|
return create(context, new SystemServices());
|
}
|
|
public void systemReady() {
|
if (DBG) {
|
final long start = System.currentTimeMillis();
|
prepareNativeDaemon();
|
final long delta = System.currentTimeMillis() - start;
|
Slog.d(TAG, "Prepared in " + delta + "ms");
|
return;
|
} else {
|
prepareNativeDaemon();
|
}
|
}
|
|
private IBatteryStats getBatteryStats() {
|
synchronized (this) {
|
if (mBatteryStats != null) {
|
return mBatteryStats;
|
}
|
mBatteryStats =
|
IBatteryStats.Stub.asInterface(mServices.getService(BatteryStats.SERVICE_NAME));
|
return mBatteryStats;
|
}
|
}
|
|
@Override
|
public void registerObserver(INetworkManagementEventObserver observer) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
mObservers.register(observer);
|
}
|
|
@Override
|
public void unregisterObserver(INetworkManagementEventObserver observer) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
mObservers.unregister(observer);
|
}
|
|
@FunctionalInterface
|
private interface NetworkManagementEventCallback {
|
public void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
|
}
|
|
private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) {
|
final int length = mObservers.beginBroadcast();
|
try {
|
for (int i = 0; i < length; i++) {
|
try {
|
eventCallback.sendCallback(mObservers.getBroadcastItem(i));
|
} catch (RemoteException | RuntimeException e) {
|
}
|
}
|
} finally {
|
mObservers.finishBroadcast();
|
}
|
}
|
|
/**
|
* Notify our observers of an interface status change
|
*/
|
private void notifyInterfaceStatusChanged(String iface, boolean up) {
|
invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up));
|
}
|
|
/**
|
* Notify our observers of an interface link state change
|
* (typically, an Ethernet cable has been plugged-in or unplugged).
|
*/
|
private void notifyInterfaceLinkStateChanged(String iface, boolean up) {
|
invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up));
|
}
|
|
/**
|
* Notify our observers of an interface addition.
|
*/
|
private void notifyInterfaceAdded(String iface) {
|
invokeForAllObservers(o -> o.interfaceAdded(iface));
|
}
|
|
/**
|
* Notify our observers of an interface removal.
|
*/
|
private void notifyInterfaceRemoved(String iface) {
|
// netd already clears out quota and alerts for removed ifaces; update
|
// our sanity-checking state.
|
mActiveAlerts.remove(iface);
|
mActiveQuotas.remove(iface);
|
invokeForAllObservers(o -> o.interfaceRemoved(iface));
|
}
|
|
/**
|
* Notify our observers of a limit reached.
|
*/
|
private void notifyLimitReached(String limitName, String iface) {
|
invokeForAllObservers(o -> o.limitReached(limitName, iface));
|
}
|
|
/**
|
* Notify our observers of a change in the data activity state of the interface
|
*/
|
private void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos,
|
int uid, boolean fromRadio) {
|
final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type);
|
int powerState = isActive
|
? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
|
: DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
|
if (isMobile) {
|
if (!fromRadio) {
|
if (mMobileActivityFromRadio) {
|
// If this call is not coming from a report from the radio itself, but we
|
// have previously received reports from the radio, then we will take the
|
// power state to just be whatever the radio last reported.
|
powerState = mLastPowerStateFromRadio;
|
}
|
} else {
|
mMobileActivityFromRadio = true;
|
}
|
if (mLastPowerStateFromRadio != powerState) {
|
mLastPowerStateFromRadio = powerState;
|
try {
|
getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid);
|
} catch (RemoteException e) {
|
}
|
StatsLog.write_non_chained(StatsLog.MOBILE_RADIO_POWER_STATE_CHANGED, uid, null,
|
powerState);
|
}
|
}
|
|
if (ConnectivityManager.isNetworkTypeWifi(type)) {
|
if (mLastPowerStateFromWifi != powerState) {
|
mLastPowerStateFromWifi = powerState;
|
try {
|
getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid);
|
} catch (RemoteException e) {
|
}
|
StatsLog.write_non_chained(StatsLog.WIFI_RADIO_POWER_STATE_CHANGED, uid, null,
|
powerState);
|
}
|
}
|
|
if (!isMobile || fromRadio || !mMobileActivityFromRadio) {
|
// Report the change in data activity. We don't do this if this is a change
|
// on the mobile network, that is not coming from the radio itself, and we
|
// have previously seen change reports from the radio. In that case only
|
// the radio is the authority for the current state.
|
final boolean active = isActive;
|
invokeForAllObservers(o -> o.interfaceClassDataActivityChanged(
|
Integer.toString(type), active, tsNanos));
|
}
|
|
boolean report = false;
|
synchronized (mIdleTimerLock) {
|
if (mActiveIdleTimers.isEmpty()) {
|
// If there are no idle timers, we are not monitoring activity, so we
|
// are always considered active.
|
isActive = true;
|
}
|
if (mNetworkActive != isActive) {
|
mNetworkActive = isActive;
|
report = isActive;
|
}
|
}
|
if (report) {
|
reportNetworkActive();
|
}
|
}
|
|
@Override
|
public void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name) {
|
mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
|
Preconditions.checkNotNull(provider);
|
synchronized(mTetheringStatsProviders) {
|
mTetheringStatsProviders.put(provider, name);
|
}
|
}
|
|
@Override
|
public void unregisterTetheringStatsProvider(ITetheringStatsProvider provider) {
|
mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
|
synchronized(mTetheringStatsProviders) {
|
mTetheringStatsProviders.remove(provider);
|
}
|
}
|
|
@Override
|
public void tetherLimitReached(ITetheringStatsProvider provider) {
|
mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
|
synchronized(mTetheringStatsProviders) {
|
if (!mTetheringStatsProviders.containsKey(provider)) {
|
return;
|
}
|
// No current code examines the interface parameter in a global alert. Just pass null.
|
mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null));
|
}
|
}
|
|
// Sync the state of the given chain with the native daemon.
|
private void syncFirewallChainLocked(int chain, String name) {
|
SparseIntArray rules;
|
synchronized (mRulesLock) {
|
final SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
|
// Make a copy of the current rules, and then clear them. This is because
|
// setFirewallUidRuleInternal only pushes down rules to the native daemon if they
|
// are different from the current rules stored in the mUidFirewall*Rules array for
|
// the specified chain. If we don't clear the rules, setFirewallUidRuleInternal
|
// will do nothing.
|
rules = uidFirewallRules.clone();
|
uidFirewallRules.clear();
|
}
|
if (rules.size() > 0) {
|
// Now push the rules. setFirewallUidRuleInternal will push each of these down to the
|
// native daemon, and also add them to the mUidFirewall*Rules array for the specified
|
// chain.
|
if (DBG) Slog.d(TAG, "Pushing " + rules.size() + " active firewall "
|
+ name + "UID rules");
|
for (int i = 0; i < rules.size(); i++) {
|
setFirewallUidRuleLocked(chain, rules.keyAt(i), rules.valueAt(i));
|
}
|
}
|
}
|
|
private void connectNativeNetdService() {
|
mNetdService = mServices.getNetd();
|
try {
|
mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener);
|
if (DBG) Slog.d(TAG, "Register unsolicited event listener");
|
} catch (RemoteException | ServiceSpecificException e) {
|
Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e);
|
}
|
}
|
|
/**
|
* Prepare native daemon once connected, enabling modules and pushing any
|
* existing in-memory rules.
|
*/
|
private void prepareNativeDaemon() {
|
|
// push any existing quota or UID rules
|
synchronized (mQuotaLock) {
|
|
// Netd unconditionally enable bandwidth control
|
SystemProperties.set(PROP_QTAGUID_ENABLED, "1");
|
|
mStrictEnabled = true;
|
|
setDataSaverModeEnabled(mDataSaverMode);
|
|
int size = mActiveQuotas.size();
|
if (size > 0) {
|
if (DBG) Slog.d(TAG, "Pushing " + size + " active quota rules");
|
final HashMap<String, Long> activeQuotas = mActiveQuotas;
|
mActiveQuotas = Maps.newHashMap();
|
for (Map.Entry<String, Long> entry : activeQuotas.entrySet()) {
|
setInterfaceQuota(entry.getKey(), entry.getValue());
|
}
|
}
|
|
size = mActiveAlerts.size();
|
if (size > 0) {
|
if (DBG) Slog.d(TAG, "Pushing " + size + " active alert rules");
|
final HashMap<String, Long> activeAlerts = mActiveAlerts;
|
mActiveAlerts = Maps.newHashMap();
|
for (Map.Entry<String, Long> entry : activeAlerts.entrySet()) {
|
setInterfaceAlert(entry.getKey(), entry.getValue());
|
}
|
}
|
|
SparseBooleanArray uidRejectOnQuota = null;
|
SparseBooleanArray uidAcceptOnQuota = null;
|
synchronized (mRulesLock) {
|
size = mUidRejectOnMetered.size();
|
if (size > 0) {
|
if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
|
uidRejectOnQuota = mUidRejectOnMetered;
|
mUidRejectOnMetered = new SparseBooleanArray();
|
}
|
|
size = mUidAllowOnMetered.size();
|
if (size > 0) {
|
if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
|
uidAcceptOnQuota = mUidAllowOnMetered;
|
mUidAllowOnMetered = new SparseBooleanArray();
|
}
|
}
|
if (uidRejectOnQuota != null) {
|
for (int i = 0; i < uidRejectOnQuota.size(); i++) {
|
setUidMeteredNetworkBlacklist(uidRejectOnQuota.keyAt(i),
|
uidRejectOnQuota.valueAt(i));
|
}
|
}
|
if (uidAcceptOnQuota != null) {
|
for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
|
setUidMeteredNetworkWhitelist(uidAcceptOnQuota.keyAt(i),
|
uidAcceptOnQuota.valueAt(i));
|
}
|
}
|
|
size = mUidCleartextPolicy.size();
|
if (size > 0) {
|
if (DBG) Slog.d(TAG, "Pushing " + size + " active UID cleartext policies");
|
final SparseIntArray local = mUidCleartextPolicy;
|
mUidCleartextPolicy = new SparseIntArray();
|
for (int i = 0; i < local.size(); i++) {
|
setUidCleartextNetworkPolicy(local.keyAt(i), local.valueAt(i));
|
}
|
}
|
|
setFirewallEnabled(mFirewallEnabled);
|
|
syncFirewallChainLocked(FIREWALL_CHAIN_NONE, "");
|
syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, "standby ");
|
syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable ");
|
syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
|
|
final int[] chains =
|
{FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE};
|
for (int chain : chains) {
|
if (getFirewallChainState(chain)) {
|
setFirewallChainEnabled(chain, true);
|
}
|
}
|
}
|
|
|
try {
|
getBatteryStats().noteNetworkStatsEnabled();
|
} catch (RemoteException e) {
|
}
|
|
}
|
|
/**
|
* Notify our observers of a new or updated interface address.
|
*/
|
private void notifyAddressUpdated(String iface, LinkAddress address) {
|
invokeForAllObservers(o -> o.addressUpdated(iface, address));
|
}
|
|
/**
|
* Notify our observers of a deleted interface address.
|
*/
|
private void notifyAddressRemoved(String iface, LinkAddress address) {
|
invokeForAllObservers(o -> o.addressRemoved(iface, address));
|
}
|
|
/**
|
* Notify our observers of DNS server information received.
|
*/
|
private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
|
invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses));
|
}
|
|
/**
|
* Notify our observers of a route change.
|
*/
|
private void notifyRouteChange(boolean updated, RouteInfo route) {
|
if (updated) {
|
invokeForAllObservers(o -> o.routeUpdated(route));
|
} else {
|
invokeForAllObservers(o -> o.routeRemoved(route));
|
}
|
}
|
|
private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
|
@Override
|
public void onInterfaceClassActivityChanged(boolean isActive,
|
int label, long timestamp, int uid) throws RemoteException {
|
final long timestampNanos;
|
if (timestamp <= 0) {
|
timestampNanos = SystemClock.elapsedRealtimeNanos();
|
} else {
|
timestampNanos = timestamp;
|
}
|
mDaemonHandler.post(() ->
|
notifyInterfaceClassActivity(label, isActive, timestampNanos, uid, false));
|
}
|
|
@Override
|
public void onQuotaLimitReached(String alertName, String ifName)
|
throws RemoteException {
|
mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
|
}
|
|
@Override
|
public void onInterfaceDnsServerInfo(String ifName,
|
long lifetime, String[] servers) throws RemoteException {
|
mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
|
}
|
|
@Override
|
public void onInterfaceAddressUpdated(String addr,
|
String ifName, int flags, int scope) throws RemoteException {
|
final LinkAddress address = new LinkAddress(addr, flags, scope);
|
mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
|
}
|
|
@Override
|
public void onInterfaceAddressRemoved(String addr,
|
String ifName, int flags, int scope) throws RemoteException {
|
final LinkAddress address = new LinkAddress(addr, flags, scope);
|
mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
|
}
|
|
@Override
|
public void onInterfaceAdded(String ifName) throws RemoteException {
|
mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
|
}
|
|
@Override
|
public void onInterfaceRemoved(String ifName) throws RemoteException {
|
mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
|
}
|
|
@Override
|
public void onInterfaceChanged(String ifName, boolean up)
|
throws RemoteException {
|
mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
|
}
|
|
@Override
|
public void onInterfaceLinkStateChanged(String ifName, boolean up)
|
throws RemoteException {
|
mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
|
}
|
|
@Override
|
public void onRouteChanged(boolean updated,
|
String route, String gateway, String ifName) throws RemoteException {
|
final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
|
("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
|
ifName);
|
mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
|
}
|
|
@Override
|
public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
|
// Don't need to post to mDaemonHandler because the only thing
|
// that notifyCleartextNetwork does is post to a handler
|
ActivityManager.getService().notifyCleartextNetwork(uid,
|
HexDump.hexStringToByteArray(hex));
|
}
|
|
@Override
|
public int getInterfaceVersion() {
|
return INetdUnsolicitedEventListener.VERSION;
|
}
|
}
|
|
//
|
// INetworkManagementService members
|
//
|
@Override
|
public String[] listInterfaces() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
return mNetdService.interfaceGetList();
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
/**
|
* Convert InterfaceConfiguration to InterfaceConfigurationParcel with given ifname.
|
*/
|
private static InterfaceConfigurationParcel toStableParcel(InterfaceConfiguration cfg,
|
String iface) {
|
InterfaceConfigurationParcel cfgParcel = new InterfaceConfigurationParcel();
|
cfgParcel.ifName = iface;
|
String hwAddr = cfg.getHardwareAddress();
|
if (!TextUtils.isEmpty(hwAddr)) {
|
cfgParcel.hwAddr = hwAddr;
|
} else {
|
cfgParcel.hwAddr = "";
|
}
|
cfgParcel.ipv4Addr = cfg.getLinkAddress().getAddress().getHostAddress();
|
cfgParcel.prefixLength = cfg.getLinkAddress().getPrefixLength();
|
ArrayList<String> flags = new ArrayList<>();
|
for (String flag : cfg.getFlags()) {
|
flags.add(flag);
|
}
|
cfgParcel.flags = flags.toArray(new String[0]);
|
|
return cfgParcel;
|
}
|
|
/**
|
* Construct InterfaceConfiguration from InterfaceConfigurationParcel.
|
*/
|
public static InterfaceConfiguration fromStableParcel(InterfaceConfigurationParcel p) {
|
InterfaceConfiguration cfg = new InterfaceConfiguration();
|
cfg.setHardwareAddress(p.hwAddr);
|
|
final InetAddress addr = NetworkUtils.numericToInetAddress(p.ipv4Addr);
|
cfg.setLinkAddress(new LinkAddress(addr, p.prefixLength));
|
for (String flag : p.flags) {
|
cfg.setFlag(flag);
|
}
|
|
return cfg;
|
}
|
|
@Override
|
public InterfaceConfiguration getInterfaceConfig(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
final InterfaceConfigurationParcel result;
|
try {
|
result = mNetdService.interfaceGetCfg(iface);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
|
try {
|
final InterfaceConfiguration cfg = fromStableParcel(result);
|
return cfg;
|
} catch (IllegalArgumentException iae) {
|
throw new IllegalStateException("Invalid InterfaceConfigurationParcel", iae);
|
}
|
}
|
|
@Override
|
public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
LinkAddress linkAddr = cfg.getLinkAddress();
|
if (linkAddr == null || linkAddr.getAddress() == null) {
|
throw new IllegalStateException("Null LinkAddress given");
|
}
|
|
final InterfaceConfigurationParcel cfgParcel = toStableParcel(cfg, iface);
|
|
try {
|
mNetdService.interfaceSetCfg(cfgParcel);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void setInterfaceDown(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
|
ifcg.setInterfaceDown();
|
setInterfaceConfig(iface, ifcg);
|
}
|
|
@Override
|
public void setInterfaceUp(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
|
ifcg.setInterfaceUp();
|
setInterfaceConfig(iface, ifcg);
|
}
|
|
@Override
|
public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.interfaceSetIPv6PrivacyExtensions(iface, enable);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
/* TODO: This is right now a IPv4 only function. Works for wifi which loses its
|
IPv6 addresses on interface down, but we need to do full clean up here */
|
@Override
|
public void clearInterfaceAddresses(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.interfaceClearAddrs(iface);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void enableIpv6(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.interfaceSetEnableIPv6(iface, true);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void setIPv6AddrGenMode(String iface, int mode) throws ServiceSpecificException {
|
NetworkStack.checkNetworkStackPermission(mContext);
|
try {
|
mNetdService.setIPv6AddrGenMode(iface, mode);
|
} catch (RemoteException e) {
|
throw e.rethrowAsRuntimeException();
|
}
|
}
|
|
@Override
|
public void disableIpv6(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.interfaceSetEnableIPv6(iface, false);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void addRoute(int netId, RouteInfo route) {
|
modifyRoute(MODIFY_OPERATION_ADD, netId, route);
|
}
|
|
@Override
|
public void removeRoute(int netId, RouteInfo route) {
|
modifyRoute(MODIFY_OPERATION_REMOVE, netId, route);
|
}
|
|
private void modifyRoute(boolean add, int netId, RouteInfo route) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
final String ifName = route.getInterface();
|
final String dst = route.getDestination().toString();
|
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;
|
}
|
try {
|
if (add) {
|
mNetdService.networkAddRoute(netId, ifName, dst, nextHop);
|
} else {
|
mNetdService.networkRemoveRoute(netId, ifName, dst, nextHop);
|
}
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
private ArrayList<String> readRouteList(String filename) {
|
FileInputStream fstream = null;
|
ArrayList<String> list = new ArrayList<>();
|
|
try {
|
fstream = new FileInputStream(filename);
|
DataInputStream in = new DataInputStream(fstream);
|
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
String s;
|
|
// throw away the title line
|
|
while (((s = br.readLine()) != null) && (s.length() != 0)) {
|
list.add(s);
|
}
|
} catch (IOException ex) {
|
// return current list, possibly empty
|
} finally {
|
if (fstream != null) {
|
try {
|
fstream.close();
|
} catch (IOException ex) {}
|
}
|
}
|
|
return list;
|
}
|
|
@Override
|
public void setMtu(String iface, int mtu) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
mNetdService.interfaceSetMtu(iface, mtu);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void shutdown() {
|
// TODO: remove from aidl if nobody calls externally
|
mContext.enforceCallingOrSelfPermission(SHUTDOWN, TAG);
|
|
Slog.i(TAG, "Shutting down");
|
}
|
|
@Override
|
public boolean getIpForwardingEnabled() throws IllegalStateException{
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
final boolean isEnabled = mNetdService.ipfwdEnabled();
|
return isEnabled;
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void setIpForwardingEnabled(boolean enable) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
if (enable) {
|
mNetdService.ipfwdEnableForwarding("tethering");
|
} else {
|
mNetdService.ipfwdDisableForwarding("tethering");
|
}
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void startTethering(String[] dhcpRange) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
// an odd number of addrs will fail
|
|
try {
|
mNetdService.tetherStart(dhcpRange);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void stopTethering() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.tetherStop();
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public boolean isTetheringStarted() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
final boolean isEnabled = mNetdService.tetherIsEnabled();
|
return isEnabled;
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void tetherInterface(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.tetherInterfaceAdd(iface);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
List<RouteInfo> routes = new ArrayList<>();
|
// The RouteInfo constructor truncates the LinkAddress to a network prefix, thus making it
|
// suitable to use as a route destination.
|
routes.add(new RouteInfo(getInterfaceConfig(iface).getLinkAddress(), null, iface));
|
addInterfaceToLocalNetwork(iface, routes);
|
}
|
|
@Override
|
public void untetherInterface(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.tetherInterfaceRemove(iface);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
} finally {
|
removeInterfaceFromLocalNetwork(iface);
|
}
|
}
|
|
@Override
|
public String[] listTetheredInterfaces() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
return mNetdService.tetherInterfaceList();
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void setDnsForwarders(Network network, String[] dns) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
int netId = (network != null) ? network.netId : ConnectivityManager.NETID_UNSET;
|
|
try {
|
mNetdService.tetherDnsSet(netId, dns);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public String[] getDnsForwarders() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
return mNetdService.tetherDnsList();
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
private List<InterfaceAddress> excludeLinkLocal(List<InterfaceAddress> addresses) {
|
ArrayList<InterfaceAddress> filtered = new ArrayList<>(addresses.size());
|
for (InterfaceAddress ia : addresses) {
|
if (!ia.getAddress().isLinkLocalAddress())
|
filtered.add(ia);
|
}
|
return filtered;
|
}
|
|
private void modifyInterfaceForward(boolean add, String fromIface, String toIface) {
|
try {
|
if (add) {
|
mNetdService.ipfwdAddInterfaceForward(fromIface, toIface);
|
} else {
|
mNetdService.ipfwdRemoveInterfaceForward(fromIface, toIface);
|
}
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void startInterfaceForwarding(String fromIface, String toIface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
modifyInterfaceForward(true, fromIface, toIface);
|
}
|
|
@Override
|
public void stopInterfaceForwarding(String fromIface, String toIface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
modifyInterfaceForward(false, fromIface, toIface);
|
}
|
|
@Override
|
public void enableNat(String internalInterface, String externalInterface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.tetherAddForward(internalInterface, externalInterface);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void disableNat(String internalInterface, String externalInterface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.tetherRemoveForward(internalInterface, externalInterface);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void addIdleTimer(String iface, int timeout, final int type) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
if (DBG) Slog.d(TAG, "Adding idletimer");
|
|
synchronized (mIdleTimerLock) {
|
IdleTimerParams params = mActiveIdleTimers.get(iface);
|
if (params != null) {
|
// the interface already has idletimer, update network count
|
params.networkCount++;
|
return;
|
}
|
|
try {
|
mNetdService.idletimerAddInterface(iface, timeout, Integer.toString(type));
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
mActiveIdleTimers.put(iface, new IdleTimerParams(timeout, type));
|
|
// Networks start up.
|
if (ConnectivityManager.isNetworkTypeMobile(type)) {
|
mNetworkActive = false;
|
}
|
mDaemonHandler.post(() -> notifyInterfaceClassActivity(type, true,
|
SystemClock.elapsedRealtimeNanos(), -1, false));
|
}
|
}
|
|
@Override
|
public void removeIdleTimer(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
if (DBG) Slog.d(TAG, "Removing idletimer");
|
|
synchronized (mIdleTimerLock) {
|
final IdleTimerParams params = mActiveIdleTimers.get(iface);
|
if (params == null || --(params.networkCount) > 0) {
|
return;
|
}
|
|
try {
|
mNetdService.idletimerRemoveInterface(iface,
|
params.timeout, Integer.toString(params.type));
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
mActiveIdleTimers.remove(iface);
|
mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type, false,
|
SystemClock.elapsedRealtimeNanos(), -1, false));
|
}
|
}
|
|
@Override
|
public NetworkStats getNetworkStatsSummaryDev() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
return mStatsFactory.readNetworkStatsSummaryDev();
|
} catch (IOException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public NetworkStats getNetworkStatsSummaryXt() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
return mStatsFactory.readNetworkStatsSummaryXt();
|
} catch (IOException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public NetworkStats getNetworkStatsDetail() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
return mStatsFactory.readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
|
} catch (IOException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void setInterfaceQuota(String iface, long quotaBytes) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
synchronized (mQuotaLock) {
|
if (mActiveQuotas.containsKey(iface)) {
|
throw new IllegalStateException("iface " + iface + " already has quota");
|
}
|
|
try {
|
// TODO: support quota shared across interfaces
|
mNetdService.bandwidthSetInterfaceQuota(iface, quotaBytes);
|
|
mActiveQuotas.put(iface, quotaBytes);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
|
synchronized (mTetheringStatsProviders) {
|
for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) {
|
try {
|
provider.setInterfaceQuota(iface, quotaBytes);
|
} catch (RemoteException e) {
|
Log.e(TAG, "Problem setting tethering data limit on provider " +
|
mTetheringStatsProviders.get(provider) + ": " + e);
|
}
|
}
|
}
|
}
|
}
|
|
@Override
|
public void removeInterfaceQuota(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
synchronized (mQuotaLock) {
|
if (!mActiveQuotas.containsKey(iface)) {
|
// TODO: eventually consider throwing
|
return;
|
}
|
|
mActiveQuotas.remove(iface);
|
mActiveAlerts.remove(iface);
|
|
try {
|
// TODO: support quota shared across interfaces
|
mNetdService.bandwidthRemoveInterfaceQuota(iface);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
|
synchronized (mTetheringStatsProviders) {
|
for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) {
|
try {
|
provider.setInterfaceQuota(iface, ITetheringStatsProvider.QUOTA_UNLIMITED);
|
} catch (RemoteException e) {
|
Log.e(TAG, "Problem removing tethering data limit on provider " +
|
mTetheringStatsProviders.get(provider) + ": " + e);
|
}
|
}
|
}
|
}
|
}
|
|
@Override
|
public void setInterfaceAlert(String iface, long alertBytes) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
// quick sanity check
|
if (!mActiveQuotas.containsKey(iface)) {
|
throw new IllegalStateException("setting alert requires existing quota on iface");
|
}
|
|
synchronized (mQuotaLock) {
|
if (mActiveAlerts.containsKey(iface)) {
|
throw new IllegalStateException("iface " + iface + " already has alert");
|
}
|
|
try {
|
// TODO: support alert shared across interfaces
|
mNetdService.bandwidthSetInterfaceAlert(iface, alertBytes);
|
mActiveAlerts.put(iface, alertBytes);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
}
|
|
@Override
|
public void removeInterfaceAlert(String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
synchronized (mQuotaLock) {
|
if (!mActiveAlerts.containsKey(iface)) {
|
// TODO: eventually consider throwing
|
return;
|
}
|
|
try {
|
// TODO: support alert shared across interfaces
|
mNetdService.bandwidthRemoveInterfaceAlert(iface);
|
mActiveAlerts.remove(iface);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
}
|
|
@Override
|
public void setGlobalAlert(long alertBytes) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
mNetdService.bandwidthSetGlobalAlert(alertBytes);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
private void setUidOnMeteredNetworkList(int uid, boolean blacklist, boolean enable) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
synchronized (mQuotaLock) {
|
boolean oldEnable;
|
SparseBooleanArray quotaList;
|
synchronized (mRulesLock) {
|
quotaList = blacklist ? mUidRejectOnMetered : mUidAllowOnMetered;
|
oldEnable = quotaList.get(uid, false);
|
}
|
if (oldEnable == enable) {
|
// TODO: eventually consider throwing
|
return;
|
}
|
|
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
|
try {
|
if (blacklist) {
|
if (enable) {
|
mNetdService.bandwidthAddNaughtyApp(uid);
|
} else {
|
mNetdService.bandwidthRemoveNaughtyApp(uid);
|
}
|
} else {
|
if (enable) {
|
mNetdService.bandwidthAddNiceApp(uid);
|
} else {
|
mNetdService.bandwidthRemoveNiceApp(uid);
|
}
|
}
|
synchronized (mRulesLock) {
|
if (enable) {
|
quotaList.put(uid, true);
|
} else {
|
quotaList.delete(uid);
|
}
|
}
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
} finally {
|
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
|
}
|
}
|
}
|
|
@Override
|
public void setUidMeteredNetworkBlacklist(int uid, boolean enable) {
|
setUidOnMeteredNetworkList(uid, true, enable);
|
}
|
|
@Override
|
public void setUidMeteredNetworkWhitelist(int uid, boolean enable) {
|
setUidOnMeteredNetworkList(uid, false, enable);
|
}
|
|
@Override
|
public boolean setDataSaverModeEnabled(boolean enable) {
|
mContext.enforceCallingOrSelfPermission(NETWORK_SETTINGS, TAG);
|
|
if (DBG) Log.d(TAG, "setDataSaverMode: " + enable);
|
synchronized (mQuotaLock) {
|
if (mDataSaverMode == enable) {
|
Log.w(TAG, "setDataSaverMode(): already " + mDataSaverMode);
|
return true;
|
}
|
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "bandwidthEnableDataSaver");
|
try {
|
final boolean changed = mNetdService.bandwidthEnableDataSaver(enable);
|
if (changed) {
|
mDataSaverMode = enable;
|
} else {
|
Log.w(TAG, "setDataSaverMode(" + enable + "): netd command silently failed");
|
}
|
return changed;
|
} catch (RemoteException e) {
|
Log.w(TAG, "setDataSaverMode(" + enable + "): netd command failed", e);
|
return false;
|
} finally {
|
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
|
}
|
}
|
}
|
|
private static UidRangeParcel makeUidRangeParcel(int start, int stop) {
|
UidRangeParcel range = new UidRangeParcel();
|
range.start = start;
|
range.stop = stop;
|
return range;
|
}
|
|
private static UidRangeParcel[] toStableParcels(UidRange[] ranges) {
|
UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.length];
|
for (int i = 0; i < ranges.length; i++) {
|
stableRanges[i] = makeUidRangeParcel(ranges[i].start, ranges[i].stop);
|
}
|
return stableRanges;
|
}
|
|
@Override
|
public void setAllowOnlyVpnForUids(boolean add, UidRange[] uidRanges)
|
throws ServiceSpecificException {
|
mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
|
try {
|
mNetdService.networkRejectNonSecureVpn(add, toStableParcels(uidRanges));
|
} catch (ServiceSpecificException e) {
|
Log.w(TAG, "setAllowOnlyVpnForUids(" + add + ", " + Arrays.toString(uidRanges) + ")"
|
+ ": netd command failed", e);
|
throw e;
|
} catch (RemoteException e) {
|
Log.w(TAG, "setAllowOnlyVpnForUids(" + add + ", " + Arrays.toString(uidRanges) + ")"
|
+ ": netd command failed", e);
|
throw e.rethrowAsRuntimeException();
|
}
|
}
|
|
private void applyUidCleartextNetworkPolicy(int uid, int policy) {
|
final int policyValue;
|
switch (policy) {
|
case StrictMode.NETWORK_POLICY_ACCEPT:
|
policyValue = INetd.PENALTY_POLICY_ACCEPT;
|
break;
|
case StrictMode.NETWORK_POLICY_LOG:
|
policyValue = INetd.PENALTY_POLICY_LOG;
|
break;
|
case StrictMode.NETWORK_POLICY_REJECT:
|
policyValue = INetd.PENALTY_POLICY_REJECT;
|
break;
|
default:
|
throw new IllegalArgumentException("Unknown policy " + policy);
|
}
|
|
try {
|
mNetdService.strictUidCleartextPenalty(uid, policyValue);
|
mUidCleartextPolicy.put(uid, policy);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void setUidCleartextNetworkPolicy(int uid, int policy) {
|
if (Binder.getCallingUid() != uid) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
}
|
|
synchronized (mQuotaLock) {
|
final int oldPolicy = mUidCleartextPolicy.get(uid, StrictMode.NETWORK_POLICY_ACCEPT);
|
if (oldPolicy == policy) {
|
// This also ensures we won't needlessly apply an ACCEPT policy if we've just
|
// enabled strict and the underlying iptables rules are empty.
|
return;
|
}
|
|
// TODO: remove this code after removing prepareNativeDaemon()
|
if (!mStrictEnabled) {
|
// Module isn't enabled yet; stash the requested policy away to
|
// apply later once the daemon is connected.
|
mUidCleartextPolicy.put(uid, policy);
|
return;
|
}
|
|
// netd does not keep state on strict mode policies, and cannot replace a non-accept
|
// policy without deleting it first. Rather than add state to netd, just always send
|
// it an accept policy when switching between two non-accept policies.
|
// TODO: consider keeping state in netd so we can simplify this code.
|
if (oldPolicy != StrictMode.NETWORK_POLICY_ACCEPT &&
|
policy != StrictMode.NETWORK_POLICY_ACCEPT) {
|
applyUidCleartextNetworkPolicy(uid, StrictMode.NETWORK_POLICY_ACCEPT);
|
}
|
|
applyUidCleartextNetworkPolicy(uid, policy);
|
}
|
}
|
|
@Override
|
public boolean isBandwidthControlEnabled() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
return true;
|
}
|
|
@Override
|
public NetworkStats getNetworkStatsUidDetail(int uid, String[] ifaces) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
return mStatsFactory.readNetworkStatsDetail(uid, ifaces, TAG_ALL, null);
|
} catch (IOException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub {
|
@Override
|
public NetworkStats getTetherStats(int how) {
|
// We only need to return per-UID stats. Per-device stats are already counted by
|
// interface counters.
|
if (how != STATS_PER_UID) {
|
return new NetworkStats(SystemClock.elapsedRealtime(), 0);
|
}
|
|
final TetherStatsParcel[] tetherStatsVec;
|
try {
|
tetherStatsVec = mNetdService.tetherGetStats();
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException("problem parsing tethering stats: ", e);
|
}
|
|
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(),
|
tetherStatsVec.length);
|
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
|
for (TetherStatsParcel tetherStats : tetherStatsVec) {
|
try {
|
entry.iface = tetherStats.iface;
|
entry.uid = UID_TETHERING;
|
entry.set = SET_DEFAULT;
|
entry.tag = TAG_NONE;
|
entry.rxBytes = tetherStats.rxBytes;
|
entry.rxPackets = tetherStats.rxPackets;
|
entry.txBytes = tetherStats.txBytes;
|
entry.txPackets = tetherStats.txPackets;
|
stats.combineValues(entry);
|
} catch (ArrayIndexOutOfBoundsException e) {
|
throw new IllegalStateException("invalid tethering stats " + e);
|
}
|
}
|
|
return stats;
|
}
|
|
@Override
|
public void setInterfaceQuota(String iface, long quotaBytes) {
|
// Do nothing. netd is already informed of quota changes in setInterfaceQuota.
|
}
|
}
|
|
@Override
|
public NetworkStats getNetworkStatsTethering(int how) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
|
synchronized (mTetheringStatsProviders) {
|
for (ITetheringStatsProvider provider: mTetheringStatsProviders.keySet()) {
|
try {
|
stats.combineAllValues(provider.getTetherStats(how));
|
} catch (RemoteException e) {
|
Log.e(TAG, "Problem reading tethering stats from " +
|
mTetheringStatsProviders.get(provider) + ": " + e);
|
}
|
}
|
}
|
return stats;
|
}
|
|
@Override
|
public void addVpnUidRanges(int netId, UidRange[] ranges) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
mNetdService.networkAddUidRanges(netId, toStableParcels(ranges));
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void removeVpnUidRanges(int netId, UidRange[] ranges) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
mNetdService.networkRemoveUidRanges(netId, toStableParcels(ranges));
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void setFirewallEnabled(boolean enabled) {
|
enforceSystemUid();
|
try {
|
mNetdService.firewallSetFirewallType(
|
enabled ? INetd.FIREWALL_WHITELIST : INetd.FIREWALL_BLACKLIST);
|
mFirewallEnabled = enabled;
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public boolean isFirewallEnabled() {
|
enforceSystemUid();
|
return mFirewallEnabled;
|
}
|
|
@Override
|
public void setFirewallInterfaceRule(String iface, boolean allow) {
|
enforceSystemUid();
|
Preconditions.checkState(mFirewallEnabled);
|
try {
|
mNetdService.firewallSetInterfaceRule(iface,
|
allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
private void closeSocketsForFirewallChainLocked(int chain, String chainName) {
|
// UID ranges to close sockets on.
|
UidRangeParcel[] ranges;
|
// UID ranges whose sockets we won't touch.
|
int[] exemptUids;
|
|
int numUids = 0;
|
if (DBG) Slog.d(TAG, "Closing sockets after enabling chain " + chainName);
|
if (getFirewallType(chain) == FIREWALL_WHITELIST) {
|
// Close all sockets on all non-system UIDs...
|
ranges = new UidRangeParcel[] {
|
// TODO: is there a better way of finding all existing users? If so, we could
|
// specify their ranges here.
|
makeUidRangeParcel(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE),
|
};
|
// ... except for the UIDs that have allow rules.
|
synchronized (mRulesLock) {
|
final SparseIntArray rules = getUidFirewallRulesLR(chain);
|
exemptUids = new int[rules.size()];
|
for (int i = 0; i < exemptUids.length; i++) {
|
if (rules.valueAt(i) == FIREWALL_RULE_ALLOW) {
|
exemptUids[numUids] = rules.keyAt(i);
|
numUids++;
|
}
|
}
|
}
|
// Normally, whitelist chains only contain deny rules, so numUids == exemptUids.length.
|
// But the code does not guarantee this in any way, and at least in one case - if we add
|
// a UID rule to the firewall, and then disable the firewall - the chains can contain
|
// the wrong type of rule. In this case, don't close connections that we shouldn't.
|
//
|
// TODO: tighten up this code by ensuring we never set the wrong type of rule, and
|
// fix setFirewallEnabled to grab mQuotaLock and clear rules.
|
if (numUids != exemptUids.length) {
|
exemptUids = Arrays.copyOf(exemptUids, numUids);
|
}
|
} else {
|
// Close sockets for every UID that has a deny rule...
|
synchronized (mRulesLock) {
|
final SparseIntArray rules = getUidFirewallRulesLR(chain);
|
ranges = new UidRangeParcel[rules.size()];
|
for (int i = 0; i < ranges.length; i++) {
|
if (rules.valueAt(i) == FIREWALL_RULE_DENY) {
|
int uid = rules.keyAt(i);
|
ranges[numUids] = makeUidRangeParcel(uid, uid);
|
numUids++;
|
}
|
}
|
}
|
// As above; usually numUids == ranges.length, but not always.
|
if (numUids != ranges.length) {
|
ranges = Arrays.copyOf(ranges, numUids);
|
}
|
// ... with no exceptions.
|
exemptUids = new int[0];
|
}
|
|
try {
|
mNetdService.socketDestroy(ranges, exemptUids);
|
} catch(RemoteException | ServiceSpecificException e) {
|
Slog.e(TAG, "Error closing sockets after enabling chain " + chainName + ": " + e);
|
}
|
}
|
|
@Override
|
public void setFirewallChainEnabled(int chain, boolean enable) {
|
enforceSystemUid();
|
synchronized (mQuotaLock) {
|
synchronized (mRulesLock) {
|
if (getFirewallChainState(chain) == enable) {
|
// All is the same, nothing to do. This relies on the fact that netd has child
|
// chains default detached.
|
return;
|
}
|
setFirewallChainState(chain, enable);
|
}
|
|
final String chainName = getFirewallChainName(chain);
|
if (chain == FIREWALL_CHAIN_NONE) {
|
throw new IllegalArgumentException("Bad child chain: " + chainName);
|
}
|
|
try {
|
mNetdService.firewallEnableChildChain(chain, enable);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
|
// Close any sockets that were opened by the affected UIDs. This has to be done after
|
// disabling network connectivity, in case they react to the socket close by reopening
|
// the connection and race with the iptables commands that enable the firewall. All
|
// whitelist and blacklist chains allow RSTs through.
|
if (enable) {
|
closeSocketsForFirewallChainLocked(chain, chainName);
|
}
|
}
|
}
|
|
private String getFirewallChainName(int chain) {
|
switch (chain) {
|
case FIREWALL_CHAIN_STANDBY:
|
return FIREWALL_CHAIN_NAME_STANDBY;
|
case FIREWALL_CHAIN_DOZABLE:
|
return FIREWALL_CHAIN_NAME_DOZABLE;
|
case FIREWALL_CHAIN_POWERSAVE:
|
return FIREWALL_CHAIN_NAME_POWERSAVE;
|
default:
|
throw new IllegalArgumentException("Bad child chain: " + chain);
|
}
|
}
|
|
private int getFirewallType(int chain) {
|
switch (chain) {
|
case FIREWALL_CHAIN_STANDBY:
|
return FIREWALL_BLACKLIST;
|
case FIREWALL_CHAIN_DOZABLE:
|
return FIREWALL_WHITELIST;
|
case FIREWALL_CHAIN_POWERSAVE:
|
return FIREWALL_WHITELIST;
|
default:
|
return isFirewallEnabled() ? FIREWALL_WHITELIST : FIREWALL_BLACKLIST;
|
}
|
}
|
|
@Override
|
public void setFirewallUidRules(int chain, int[] uids, int[] rules) {
|
enforceSystemUid();
|
synchronized (mQuotaLock) {
|
synchronized (mRulesLock) {
|
SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
|
SparseIntArray newRules = new SparseIntArray();
|
// apply new set of rules
|
for (int index = uids.length - 1; index >= 0; --index) {
|
int uid = uids[index];
|
int rule = rules[index];
|
updateFirewallUidRuleLocked(chain, uid, rule);
|
newRules.put(uid, rule);
|
}
|
// collect the rules to remove.
|
SparseIntArray rulesToRemove = new SparseIntArray();
|
for (int index = uidFirewallRules.size() - 1; index >= 0; --index) {
|
int uid = uidFirewallRules.keyAt(index);
|
if (newRules.indexOfKey(uid) < 0) {
|
rulesToRemove.put(uid, FIREWALL_RULE_DEFAULT);
|
}
|
}
|
// remove dead rules
|
for (int index = rulesToRemove.size() - 1; index >= 0; --index) {
|
int uid = rulesToRemove.keyAt(index);
|
updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
|
}
|
}
|
try {
|
switch (chain) {
|
case FIREWALL_CHAIN_DOZABLE:
|
mNetdService.firewallReplaceUidChain("fw_dozable", true, uids);
|
break;
|
case FIREWALL_CHAIN_STANDBY:
|
mNetdService.firewallReplaceUidChain("fw_standby", false, uids);
|
break;
|
case FIREWALL_CHAIN_POWERSAVE:
|
mNetdService.firewallReplaceUidChain("fw_powersave", true, uids);
|
break;
|
case FIREWALL_CHAIN_NONE:
|
default:
|
Slog.d(TAG, "setFirewallUidRules() called on invalid chain: " + chain);
|
}
|
} catch (RemoteException e) {
|
Slog.w(TAG, "Error flushing firewall chain " + chain, e);
|
}
|
}
|
}
|
|
@Override
|
public void setFirewallUidRule(int chain, int uid, int rule) {
|
enforceSystemUid();
|
synchronized (mQuotaLock) {
|
setFirewallUidRuleLocked(chain, uid, rule);
|
}
|
}
|
|
private void setFirewallUidRuleLocked(int chain, int uid, int rule) {
|
if (updateFirewallUidRuleLocked(chain, uid, rule)) {
|
final int ruleType = getFirewallRuleType(chain, rule);
|
try {
|
mNetdService.firewallSetUidRule(chain, uid, ruleType);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
}
|
|
// TODO: now that netd supports batching, NMS should not keep these data structures anymore...
|
private boolean updateFirewallUidRuleLocked(int chain, int uid, int rule) {
|
synchronized (mRulesLock) {
|
SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
|
|
final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT);
|
if (DBG) {
|
Slog.d(TAG, "oldRule = " + oldUidFirewallRule
|
+ ", newRule=" + rule + " for uid=" + uid + " on chain " + chain);
|
}
|
if (oldUidFirewallRule == rule) {
|
if (DBG) Slog.d(TAG, "!!!!! Skipping change");
|
// TODO: eventually consider throwing
|
return false;
|
}
|
|
String ruleName = getFirewallRuleName(chain, rule);
|
String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule);
|
|
if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
|
uidFirewallRules.delete(uid);
|
} else {
|
uidFirewallRules.put(uid, rule);
|
}
|
return !ruleName.equals(oldRuleName);
|
}
|
}
|
|
private @NonNull String getFirewallRuleName(int chain, int rule) {
|
String ruleName;
|
if (getFirewallType(chain) == FIREWALL_WHITELIST) {
|
if (rule == FIREWALL_RULE_ALLOW) {
|
ruleName = "allow";
|
} else {
|
ruleName = "deny";
|
}
|
} else { // Blacklist mode
|
if (rule == FIREWALL_RULE_DENY) {
|
ruleName = "deny";
|
} else {
|
ruleName = "allow";
|
}
|
}
|
return ruleName;
|
}
|
|
@GuardedBy("mRulesLock")
|
private @NonNull SparseIntArray getUidFirewallRulesLR(int chain) {
|
switch (chain) {
|
case FIREWALL_CHAIN_STANDBY:
|
return mUidFirewallStandbyRules;
|
case FIREWALL_CHAIN_DOZABLE:
|
return mUidFirewallDozableRules;
|
case FIREWALL_CHAIN_POWERSAVE:
|
return mUidFirewallPowerSaveRules;
|
case FIREWALL_CHAIN_NONE:
|
return mUidFirewallRules;
|
default:
|
throw new IllegalArgumentException("Unknown chain:" + chain);
|
}
|
}
|
|
private int getFirewallRuleType(int chain, int rule) {
|
if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
|
return getFirewallType(chain) == FIREWALL_WHITELIST
|
? INetd.FIREWALL_RULE_DENY : INetd.FIREWALL_RULE_ALLOW;
|
}
|
return rule;
|
}
|
|
private static void enforceSystemUid() {
|
final int uid = Binder.getCallingUid();
|
if (uid != Process.SYSTEM_UID) {
|
throw new SecurityException("Only available to AID_SYSTEM");
|
}
|
}
|
|
@Override
|
public void registerNetworkActivityListener(INetworkActivityListener listener) {
|
mNetworkActivityListeners.register(listener);
|
}
|
|
@Override
|
public void unregisterNetworkActivityListener(INetworkActivityListener listener) {
|
mNetworkActivityListeners.unregister(listener);
|
}
|
|
@Override
|
public boolean isNetworkActive() {
|
synchronized (mNetworkActivityListeners) {
|
return mNetworkActive || mActiveIdleTimers.isEmpty();
|
}
|
}
|
|
private void reportNetworkActive() {
|
final int length = mNetworkActivityListeners.beginBroadcast();
|
try {
|
for (int i = 0; i < length; i++) {
|
try {
|
mNetworkActivityListeners.getBroadcastItem(i).onNetworkActive();
|
} catch (RemoteException | RuntimeException e) {
|
}
|
}
|
} finally {
|
mNetworkActivityListeners.finishBroadcast();
|
}
|
}
|
|
@Override
|
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
|
pw.print("mMobileActivityFromRadio="); pw.print(mMobileActivityFromRadio);
|
pw.print(" mLastPowerStateFromRadio="); pw.println(mLastPowerStateFromRadio);
|
pw.print("mNetworkActive="); pw.println(mNetworkActive);
|
|
synchronized (mQuotaLock) {
|
pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString());
|
pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
|
pw.print("Data saver mode: "); pw.println(mDataSaverMode);
|
synchronized (mRulesLock) {
|
dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
|
dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
|
}
|
}
|
|
synchronized (mRulesLock) {
|
dumpUidFirewallRule(pw, "", mUidFirewallRules);
|
|
pw.print("UID firewall standby chain enabled: "); pw.println(
|
getFirewallChainState(FIREWALL_CHAIN_STANDBY));
|
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_STANDBY, mUidFirewallStandbyRules);
|
|
pw.print("UID firewall dozable chain enabled: "); pw.println(
|
getFirewallChainState(FIREWALL_CHAIN_DOZABLE));
|
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_DOZABLE, mUidFirewallDozableRules);
|
|
pw.println("UID firewall powersave chain enabled: " +
|
getFirewallChainState(FIREWALL_CHAIN_POWERSAVE));
|
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_POWERSAVE, mUidFirewallPowerSaveRules);
|
}
|
|
synchronized (mIdleTimerLock) {
|
pw.println("Idle timers:");
|
for (HashMap.Entry<String, IdleTimerParams> ent : mActiveIdleTimers.entrySet()) {
|
pw.print(" "); pw.print(ent.getKey()); pw.println(":");
|
IdleTimerParams params = ent.getValue();
|
pw.print(" timeout="); pw.print(params.timeout);
|
pw.print(" type="); pw.print(params.type);
|
pw.print(" networkCount="); pw.println(params.networkCount);
|
}
|
}
|
|
pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
|
pw.print("Netd service status: " );
|
if (mNetdService == null) {
|
pw.println("disconnected");
|
} else {
|
try {
|
final boolean alive = mNetdService.isAlive();
|
pw.println(alive ? "alive": "dead");
|
} catch (RemoteException e) {
|
pw.println("unreachable");
|
}
|
}
|
}
|
|
private void dumpUidRuleOnQuotaLocked(PrintWriter pw, String name, SparseBooleanArray list) {
|
pw.print("UID bandwith control ");
|
pw.print(name);
|
pw.print(" rule: [");
|
final int size = list.size();
|
for (int i = 0; i < size; i++) {
|
pw.print(list.keyAt(i));
|
if (i < size - 1) pw.print(",");
|
}
|
pw.println("]");
|
}
|
|
private void dumpUidFirewallRule(PrintWriter pw, String name, SparseIntArray rules) {
|
pw.print("UID firewall ");
|
pw.print(name);
|
pw.print(" rule: [");
|
final int size = rules.size();
|
for (int i = 0; i < size; i++) {
|
pw.print(rules.keyAt(i));
|
pw.print(":");
|
pw.print(rules.valueAt(i));
|
if (i < size - 1) pw.print(",");
|
}
|
pw.println("]");
|
}
|
|
@Override
|
public void addInterfaceToNetwork(String iface, int netId) {
|
modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, netId, iface);
|
}
|
|
@Override
|
public void removeInterfaceFromNetwork(String iface, int netId) {
|
modifyInterfaceInNetwork(MODIFY_OPERATION_REMOVE, netId, iface);
|
}
|
|
private void modifyInterfaceInNetwork(boolean add, int netId, String iface) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
try {
|
if (add) {
|
mNetdService.networkAddInterface(netId, iface);
|
} else {
|
mNetdService.networkRemoveInterface(netId, iface);
|
}
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void addLegacyRouteForNetId(int netId, RouteInfo routeInfo, int uid) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
final LinkAddress la = routeInfo.getDestinationLinkAddress();
|
final String ifName = routeInfo.getInterface();
|
final String dst = la.toString();
|
final String nextHop;
|
|
if (routeInfo.hasGateway()) {
|
nextHop = routeInfo.getGateway().getHostAddress();
|
} else {
|
nextHop = "";
|
}
|
try {
|
mNetdService.networkAddLegacyRoute(netId, ifName, dst, nextHop, uid);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void setDefaultNetId(int netId) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
mNetdService.networkSetDefault(netId);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void clearDefaultNetId() {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
mNetdService.networkClearDefault();
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void setNetworkPermission(int netId, int permission) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
mNetdService.networkSetPermissionForNetwork(netId, permission);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void allowProtect(int uid) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
mNetdService.networkSetProtectAllow(uid);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void denyProtect(int uid) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
|
try {
|
mNetdService.networkSetProtectDeny(uid);
|
} catch (RemoteException | ServiceSpecificException e) {
|
throw new IllegalStateException(e);
|
}
|
}
|
|
@Override
|
public void addInterfaceToLocalNetwork(String iface, List<RouteInfo> routes) {
|
final boolean mAvoidIpv6 = mContext.getResources().getBoolean(com.android.internal
|
.R.bool.config_gmac1_mtu_less_than_1000);
|
modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, iface);
|
|
for (RouteInfo route : routes) {
|
if (!route.isDefaultRoute()) {
|
modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, route);
|
}
|
}
|
if (!mAvoidIpv6 && iface != "eth1") {
|
// IPv6 link local should be activated always.
|
modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID,
|
new RouteInfo(new IpPrefix("fe80::/64"), null, iface));
|
}
|
}
|
|
@Override
|
public void removeInterfaceFromLocalNetwork(String iface) {
|
modifyInterfaceInNetwork(MODIFY_OPERATION_REMOVE, INetd.LOCAL_NET_ID, iface);
|
}
|
|
@Override
|
public int removeRoutesFromLocalNetwork(List<RouteInfo> routes) {
|
int failures = 0;
|
|
for (RouteInfo route : routes) {
|
try {
|
modifyRoute(MODIFY_OPERATION_REMOVE, INetd.LOCAL_NET_ID, route);
|
} catch (IllegalStateException e) {
|
failures++;
|
}
|
}
|
|
return failures;
|
}
|
|
@Override
|
public boolean isNetworkRestricted(int uid) {
|
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
|
return isNetworkRestrictedInternal(uid);
|
}
|
|
private boolean isNetworkRestrictedInternal(int uid) {
|
synchronized (mRulesLock) {
|
if (getFirewallChainState(FIREWALL_CHAIN_STANDBY)
|
&& mUidFirewallStandbyRules.get(uid) == FIREWALL_RULE_DENY) {
|
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of app standby mode");
|
return true;
|
}
|
if (getFirewallChainState(FIREWALL_CHAIN_DOZABLE)
|
&& mUidFirewallDozableRules.get(uid) != FIREWALL_RULE_ALLOW) {
|
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of device idle mode");
|
return true;
|
}
|
if (getFirewallChainState(FIREWALL_CHAIN_POWERSAVE)
|
&& mUidFirewallPowerSaveRules.get(uid) != FIREWALL_RULE_ALLOW) {
|
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of power saver mode");
|
return true;
|
}
|
if (mUidRejectOnMetered.get(uid)) {
|
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
|
+ " in the background");
|
return true;
|
}
|
if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) {
|
if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
|
return true;
|
}
|
return false;
|
}
|
}
|
|
private void setFirewallChainState(int chain, boolean state) {
|
synchronized (mRulesLock) {
|
mFirewallChainStates.put(chain, state);
|
}
|
}
|
|
private boolean getFirewallChainState(int chain) {
|
synchronized (mRulesLock) {
|
return mFirewallChainStates.get(chain);
|
}
|
}
|
|
@VisibleForTesting
|
class LocalService extends NetworkManagementInternal {
|
@Override
|
public boolean isNetworkRestrictedForUid(int uid) {
|
return isNetworkRestrictedInternal(uid);
|
}
|
}
|
|
@VisibleForTesting
|
Injector getInjector() {
|
return new Injector();
|
}
|
|
@VisibleForTesting
|
class Injector {
|
void setDataSaverMode(boolean dataSaverMode) {
|
mDataSaverMode = dataSaverMode;
|
}
|
|
void setFirewallChainState(int chain, boolean state) {
|
NetworkManagementService.this.setFirewallChainState(chain, state);
|
}
|
|
void setFirewallRule(int chain, int uid, int rule) {
|
synchronized (mRulesLock) {
|
getUidFirewallRulesLR(chain).put(uid, rule);
|
}
|
}
|
|
void setUidOnMeteredNetworkList(boolean blacklist, int uid, boolean enable) {
|
synchronized (mRulesLock) {
|
if (blacklist) {
|
mUidRejectOnMetered.put(uid, enable);
|
} else {
|
mUidAllowOnMetered.put(uid, enable);
|
}
|
}
|
}
|
|
void reset() {
|
synchronized (mRulesLock) {
|
setDataSaverMode(false);
|
final int[] chains = {
|
FIREWALL_CHAIN_DOZABLE,
|
FIREWALL_CHAIN_STANDBY,
|
FIREWALL_CHAIN_POWERSAVE
|
};
|
for (int chain : chains) {
|
setFirewallChainState(chain, false);
|
getUidFirewallRulesLR(chain).clear();
|
}
|
mUidAllowOnMetered.clear();
|
mUidRejectOnMetered.clear();
|
}
|
}
|
}
|
}
|