/* * 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.DUMP; import static android.Manifest.permission.SHUTDOWN; import static android.net.NetworkStats.SET_DEFAULT; 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 android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNICAST; import static android.net.RouteInfo.RTN_UNREACHABLE; import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult; import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult; import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult; import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult; import static com.android.server.NetworkManagementService.NetdResponseCode.TetherDnsFwdTgtListResult; import static com.android.server.NetworkManagementService.NetdResponseCode.TetherInterfaceListResult; import static com.android.server.NetworkManagementService.NetdResponseCode.TetherStatusResult; import static com.android.server.NetworkManagementService.NetdResponseCode.TetheringStatsListResult; import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult; import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED; import android.content.Context; import android.net.ConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.Network; import android.net.NetworkStats; import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.UidRange; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; import android.os.INetworkActivityListener; import android.os.INetworkManagementService; import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.PhoneStateListener; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.app.IBatteryStats; import com.android.internal.net.NetworkStatsFactory; import com.android.internal.util.Preconditions; import com.android.server.NativeDaemonConnector.Command; import com.android.server.NativeDaemonConnector.SensitiveArg; import com.android.server.net.LockdownVpnTracker; import com.google.android.collect.Maps; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.concurrent.CountDownLatch; /** * @hide */ public class NetworkManagementService extends INetworkManagementService.Stub implements Watchdog.Monitor { private static final String TAG = "NetworkManagementService"; private static final boolean DBG = false; private static final String NETD_TAG = "NetdConnector"; private static final String NETD_SOCKET_NAME = "netd"; 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"; class NetdResponseCode { /* Keep in sync with system/netd/server/ResponseCode.h */ public static final int InterfaceListResult = 110; public static final int TetherInterfaceListResult = 111; public static final int TetherDnsFwdTgtListResult = 112; public static final int TtyListResult = 113; public static final int TetheringStatsListResult = 114; public static final int TetherStatusResult = 210; public static final int IpFwdStatusResult = 211; public static final int InterfaceGetCfgResult = 213; public static final int SoftapStatusResult = 214; public static final int InterfaceRxCounterResult = 216; public static final int InterfaceTxCounterResult = 217; public static final int QuotaCounterResult = 220; public static final int TetheringStatsResult = 221; public static final int DnsProxyQueryResult = 222; public static final int ClatdStatusResult = 223; public static final int InterfaceChange = 600; public static final int BandwidthControl = 601; public static final int InterfaceClassActivity = 613; public static final int InterfaceAddressChange = 614; public static final int InterfaceDnsServerInfo = 615; public static final int RouteChange = 616; } static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1; /** * Binder context for this service */ private final Context mContext; /** * connector object for communicating with netd */ private final NativeDaemonConnector mConnector; private final Handler mFgHandler; private final Handler mDaemonHandler; private final PhoneStateListener mPhoneStateListener; private IBatteryStats mBatteryStats; private final Thread mThread; private CountDownLatch mConnectedSignal = new CountDownLatch(1); private final RemoteCallbackList mObservers = new RemoteCallbackList(); private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); private Object mQuotaLock = new Object(); /** Set of interfaces with active quotas. */ private HashMap mActiveQuotas = Maps.newHashMap(); /** Set of interfaces with active alerts. */ private HashMap mActiveAlerts = Maps.newHashMap(); /** Set of UIDs with active reject rules. */ private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray(); private 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 mActiveIdleTimers = Maps.newHashMap(); private volatile boolean mBandwidthControlEnabled; private volatile boolean mFirewallEnabled; private boolean mMobileActivityFromRadio = false; private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; private final RemoteCallbackList mNetworkActivityListeners = new RemoteCallbackList(); private boolean mNetworkActive; /** * Constructs a new NetworkManagementService instance * * @param context Binder context for this service */ private NetworkManagementService(Context context, String socket) { mContext = context; // make sure this is on the same looper as our NativeDaemonConnector for sync purposes mFgHandler = new Handler(FgThread.get().getLooper()); if ("simulator".equals(SystemProperties.get("ro.product.device"))) { mConnector = null; mThread = null; mDaemonHandler = null; mPhoneStateListener = null; return; } // Don't need this wake lock, since we now have a time stamp for when // the network actually went inactive. (It might be nice to still do this, // but I don't want to do it through the power manager because that pollutes the // battery stats history with pointless noise.) //PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = null; //pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, NETD_TAG); mConnector = new NativeDaemonConnector( new NetdCallbackReceiver(), socket, 10, NETD_TAG, 160, wl, FgThread.get().getLooper()); mThread = new Thread(mConnector, NETD_TAG); mDaemonHandler = new Handler(FgThread.get().getLooper()); mPhoneStateListener = new PhoneStateListener(SubscriptionManager.DEFAULT_SUB_ID, mDaemonHandler.getLooper()) { @Override public void onDataConnectionRealTimeInfoChanged( DataConnectionRealTimeInfo dcRtInfo) { if (DBG) Slog.d(TAG, "onDataConnectionRealTimeInfoChanged: " + dcRtInfo); notifyInterfaceClassActivity(ConnectivityManager.TYPE_MOBILE, dcRtInfo.getDcPowerState(), dcRtInfo.getTime(), true); } }; TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); if (tm != null) { tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_DATA_CONNECTION_REAL_TIME_INFO); } // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); } static NetworkManagementService create(Context context, String socket) throws InterruptedException { final NetworkManagementService service = new NetworkManagementService(context, socket); final CountDownLatch connectedSignal = service.mConnectedSignal; if (DBG) Slog.d(TAG, "Creating NetworkManagementService"); service.mThread.start(); if (DBG) Slog.d(TAG, "Awaiting socket connection"); connectedSignal.await(); if (DBG) Slog.d(TAG, "Connected"); return service; } public static NetworkManagementService create(Context context) throws InterruptedException { return create(context, NETD_SOCKET_NAME); } public void systemReady() { prepareNativeDaemon(); if (DBG) Slog.d(TAG, "Prepared"); } private IBatteryStats getBatteryStats() { synchronized (this) { if (mBatteryStats != null) { return mBatteryStats; } mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.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); } /** * Notify our observers of an interface status change */ private void notifyInterfaceStatusChanged(String iface, boolean up) { final int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceStatusChanged(iface, up); } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } /** * 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) { final int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceLinkStateChanged(iface, up); } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } /** * Notify our observers of an interface addition. */ private void notifyInterfaceAdded(String iface) { final int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceAdded(iface); } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } /** * 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); final int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceRemoved(iface); } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } /** * Notify our observers of a limit reached. */ private void notifyLimitReached(String limitName, String iface) { final int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).limitReached(limitName, iface); } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } /** * Notify our observers of a change in the data activity state of the interface */ private void notifyInterfaceClassActivity(int type, int powerState, long tsNanos, boolean fromRadio) { final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type); 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); } catch (RemoteException e) { } } } boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; 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 int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceClassDataActivityChanged( Integer.toString(type), isActive, tsNanos); } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } 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(); } } /** * Prepare native daemon once connected, enabling modules and pushing any * existing in-memory rules. */ private void prepareNativeDaemon() { mBandwidthControlEnabled = false; // only enable bandwidth control when support exists final boolean hasKernelSupport = new File("/proc/net/xt_qtaguid/ctrl").exists(); if (hasKernelSupport) { Slog.d(TAG, "enabling bandwidth control"); try { mConnector.execute("bandwidth", "enable"); mBandwidthControlEnabled = true; } catch (NativeDaemonConnectorException e) { Log.wtf(TAG, "problem enabling bandwidth controls", e); } } else { Slog.d(TAG, "not enabling bandwidth control"); } SystemProperties.set(PROP_QTAGUID_ENABLED, mBandwidthControlEnabled ? "1" : "0"); if (mBandwidthControlEnabled) { try { getBatteryStats().noteNetworkStatsEnabled(); } catch (RemoteException e) { } } // push any existing quota or UID rules synchronized (mQuotaLock) { int size = mActiveQuotas.size(); if (size > 0) { Slog.d(TAG, "pushing " + size + " active quota rules"); final HashMap activeQuotas = mActiveQuotas; mActiveQuotas = Maps.newHashMap(); for (Map.Entry entry : activeQuotas.entrySet()) { setInterfaceQuota(entry.getKey(), entry.getValue()); } } size = mActiveAlerts.size(); if (size > 0) { Slog.d(TAG, "pushing " + size + " active alert rules"); final HashMap activeAlerts = mActiveAlerts; mActiveAlerts = Maps.newHashMap(); for (Map.Entry entry : activeAlerts.entrySet()) { setInterfaceAlert(entry.getKey(), entry.getValue()); } } size = mUidRejectOnQuota.size(); if (size > 0) { Slog.d(TAG, "pushing " + size + " active uid rules"); final SparseBooleanArray uidRejectOnQuota = mUidRejectOnQuota; mUidRejectOnQuota = new SparseBooleanArray(); for (int i = 0; i < uidRejectOnQuota.size(); i++) { setUidNetworkRules(uidRejectOnQuota.keyAt(i), uidRejectOnQuota.valueAt(i)); } } } // TODO: Push any existing firewall state setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled()); } /** * Notify our observers of a new or updated interface address. */ private void notifyAddressUpdated(String iface, LinkAddress address) { final int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).addressUpdated(iface, address); } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } /** * Notify our observers of a deleted interface address. */ private void notifyAddressRemoved(String iface, LinkAddress address) { final int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).addressRemoved(iface, address); } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } /** * Notify our observers of DNS server information received. */ private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { final int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceDnsServerInfo(iface, lifetime, addresses); } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } /** * Notify our observers of a route change. */ private void notifyRouteChange(String action, RouteInfo route) { final int length = mObservers.beginBroadcast(); try { for (int i = 0; i < length; i++) { try { if (action.equals("updated")) { mObservers.getBroadcastItem(i).routeUpdated(route); } else { mObservers.getBroadcastItem(i).routeRemoved(route); } } catch (RemoteException e) { } catch (RuntimeException e) { } } } finally { mObservers.finishBroadcast(); } } // // Netd Callback handling // private class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks { @Override public void onDaemonConnected() { // event is dispatched from internal NDC thread, so we prepare the // daemon back on main thread. if (mConnectedSignal != null) { mConnectedSignal.countDown(); mConnectedSignal = null; } else { mFgHandler.post(new Runnable() { @Override public void run() { prepareNativeDaemon(); } }); } } @Override public boolean onCheckHoldWakeLock(int code) { return code == NetdResponseCode.InterfaceClassActivity; } @Override public boolean onEvent(int code, String raw, String[] cooked) { String errorMessage = String.format("Invalid event from daemon (%s)", raw); switch (code) { case NetdResponseCode.InterfaceChange: /* * a network interface change occured * Format: "NNN Iface added " * "NNN Iface removed " * "NNN Iface changed " * "NNN Iface linkstatus " */ if (cooked.length < 4 || !cooked[1].equals("Iface")) { throw new IllegalStateException(errorMessage); } if (cooked[2].equals("added")) { notifyInterfaceAdded(cooked[3]); return true; } else if (cooked[2].equals("removed")) { notifyInterfaceRemoved(cooked[3]); return true; } else if (cooked[2].equals("changed") && cooked.length == 5) { notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up")); return true; } else if (cooked[2].equals("linkstate") && cooked.length == 5) { notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up")); return true; } throw new IllegalStateException(errorMessage); // break; case NetdResponseCode.BandwidthControl: /* * Bandwidth control needs some attention * Format: "NNN limit alert " */ if (cooked.length < 5 || !cooked[1].equals("limit")) { throw new IllegalStateException(errorMessage); } if (cooked[2].equals("alert")) { notifyLimitReached(cooked[3], cooked[4]); return true; } throw new IllegalStateException(errorMessage); // break; case NetdResponseCode.InterfaceClassActivity: /* * An network interface class state changed (active/idle) * Format: "NNN IfaceClass