/* * 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_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_TETHERING; import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult; import static com.android.server.NetworkManagementService.NetdResponseCode.GetMarkResult; 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.INetworkManagementEventObserver; import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.NetworkStats; import android.net.NetworkUtils; import android.net.RouteInfo; 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.INetworkManagementService; 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.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.InetAddress; import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; 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 String ADD = "add"; private static final String REMOVE = "remove"; private static final String ALLOW = "allow"; private static final String DENY = "deny"; private static final String DEFAULT = "default"; private static final String SECONDARY = "secondary"; /** * 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/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 GetMarkResult = 225; public static final int InterfaceChange = 600; public static final int BandwidthControl = 601; public static final int InterfaceClassActivity = 613; public static final int InterfaceAddressChange = 614; } /** * Binder context for this service */ private Context mContext; /** * connector object for communicating with netd */ private NativeDaemonConnector mConnector; private final Handler mMainHandler = new Handler(); private 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 String label; public int networkCount; IdleTimerParams(int timeout, String label) { this.timeout = timeout; this.label = label; this.networkCount = 1; } } private HashMap mActiveIdleTimers = Maps.newHashMap(); private volatile boolean mBandwidthControlEnabled; private volatile boolean mFirewallEnabled; /** * Constructs a new NetworkManagementService instance * * @param context Binder context for this service */ private NetworkManagementService(Context context, String socket) { mContext = context; if ("simulator".equals(SystemProperties.get("ro.product.device"))) { return; } mConnector = new NativeDaemonConnector( new NetdCallbackReceiver(), socket, 10, NETD_TAG, 160); mThread = new Thread(mConnector, NETD_TAG); // 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"); } @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(); for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceStatusChanged(iface, up); } catch (RemoteException e) { } catch (RuntimeException e) { } } 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(); for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceLinkStateChanged(iface, up); } catch (RemoteException e) { } catch (RuntimeException e) { } } mObservers.finishBroadcast(); } /** * Notify our observers of an interface addition. */ private void notifyInterfaceAdded(String iface) { final int length = mObservers.beginBroadcast(); for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceAdded(iface); } catch (RemoteException e) { } catch (RuntimeException e) { } } 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(); for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceRemoved(iface); } catch (RemoteException e) { } catch (RuntimeException e) { } } mObservers.finishBroadcast(); } /** * Notify our observers of a limit reached. */ private void notifyLimitReached(String limitName, String iface) { final int length = mObservers.beginBroadcast(); for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).limitReached(limitName, iface); } catch (RemoteException e) { } catch (RuntimeException e) { } } mObservers.finishBroadcast(); } /** * Notify our observers of a change in the data activity state of the interface */ private void notifyInterfaceClassActivity(String label, boolean active) { final int length = mObservers.beginBroadcast(); for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceClassDataActivityChanged(label, active); } catch (RemoteException e) { } catch (RuntimeException e) { } } mObservers.finishBroadcast(); } /** * 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 { IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)) .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 address, String iface, int flags, int scope) { final int length = mObservers.beginBroadcast(); for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).addressUpdated(address, iface, flags, scope); } catch (RemoteException e) { } catch (RuntimeException e) { } } mObservers.finishBroadcast(); } /** * Notify our observers of a deleted interface address. */ private void notifyAddressRemoved(String address, String iface, int flags, int scope) { final int length = mObservers.beginBroadcast(); for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).addressRemoved(address, iface, flags, scope); } catch (RemoteException e) { } catch (RuntimeException e) { } } 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 { mMainHandler.post(new Runnable() { @Override public void run() { prepareNativeDaemon(); } }); } } @Override public boolean onEvent(int code, String raw, String[] cooked) { 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( String.format("Invalid event from daemon (%s)", raw)); } 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( String.format("Invalid event from daemon (%s)", raw)); // break; case NetdResponseCode.BandwidthControl: /* * Bandwidth control needs some attention * Format: "NNN limit alert " */ if (cooked.length < 5 || !cooked[1].equals("limit")) { throw new IllegalStateException( String.format("Invalid event from daemon (%s)", raw)); } if (cooked[2].equals("alert")) { notifyLimitReached(cooked[3], cooked[4]); return true; } throw new IllegalStateException( String.format("Invalid event from daemon (%s)", raw)); // break; case NetdResponseCode.InterfaceClassActivity: /* * An network interface class state changed (active/idle) * Format: "NNN IfaceClass