Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
     20 import static android.Manifest.permission.CHANGE_NETWORK_STATE;
     21 import static android.Manifest.permission.DUMP;
     22 import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
     23 import static android.net.NetworkStats.SET_DEFAULT;
     24 import static android.net.NetworkStats.TAG_NONE;
     25 import static android.net.NetworkStats.UID_ALL;
     26 import static android.net.TrafficStats.UID_TETHERING;
     27 import static android.provider.Settings.Secure.NETSTATS_ENABLED;
     28 import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
     29 
     30 import android.content.Context;
     31 import android.content.pm.PackageManager;
     32 import android.net.INetworkManagementEventObserver;
     33 import android.net.InterfaceConfiguration;
     34 import android.net.LinkAddress;
     35 import android.net.NetworkStats;
     36 import android.net.NetworkUtils;
     37 import android.net.RouteInfo;
     38 import android.net.wifi.WifiConfiguration;
     39 import android.net.wifi.WifiConfiguration.KeyMgmt;
     40 import android.os.Binder;
     41 import android.os.INetworkManagementService;
     42 import android.os.SystemClock;
     43 import android.os.SystemProperties;
     44 import android.provider.Settings;
     45 import android.util.Log;
     46 import android.util.Slog;
     47 import android.util.SparseBooleanArray;
     48 
     49 import com.android.internal.net.NetworkStatsFactory;
     50 import com.google.android.collect.Sets;
     51 
     52 import java.io.BufferedReader;
     53 import java.io.DataInputStream;
     54 import java.io.File;
     55 import java.io.FileDescriptor;
     56 import java.io.FileInputStream;
     57 import java.io.IOException;
     58 import java.io.InputStreamReader;
     59 import java.io.PrintWriter;
     60 import java.net.Inet4Address;
     61 import java.net.InetAddress;
     62 import java.net.InterfaceAddress;
     63 import java.net.NetworkInterface;
     64 import java.net.SocketException;
     65 import java.util.ArrayList;
     66 import java.util.Collection;
     67 import java.util.HashSet;
     68 import java.util.NoSuchElementException;
     69 import java.util.StringTokenizer;
     70 import java.util.concurrent.CountDownLatch;
     71 
     72 /**
     73  * @hide
     74  */
     75 public class NetworkManagementService extends INetworkManagementService.Stub
     76         implements Watchdog.Monitor {
     77     private static final String TAG = "NetworkManagementService";
     78     private static final boolean DBG = false;
     79     private static final String NETD_TAG = "NetdConnector";
     80 
     81     private static final int ADD = 1;
     82     private static final int REMOVE = 2;
     83 
     84     private static final String DEFAULT = "default";
     85     private static final String SECONDARY = "secondary";
     86 
     87     /**
     88      * Name representing {@link #setGlobalAlert(long)} limit when delivered to
     89      * {@link INetworkManagementEventObserver#limitReached(String, String)}.
     90      */
     91     public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
     92 
     93     class NetdResponseCode {
     94         /* Keep in sync with system/netd/ResponseCode.h */
     95         public static final int InterfaceListResult       = 110;
     96         public static final int TetherInterfaceListResult = 111;
     97         public static final int TetherDnsFwdTgtListResult = 112;
     98         public static final int TtyListResult             = 113;
     99 
    100         public static final int TetherStatusResult        = 210;
    101         public static final int IpFwdStatusResult         = 211;
    102         public static final int InterfaceGetCfgResult     = 213;
    103         public static final int SoftapStatusResult        = 214;
    104         public static final int InterfaceRxCounterResult  = 216;
    105         public static final int InterfaceTxCounterResult  = 217;
    106         public static final int InterfaceRxThrottleResult = 218;
    107         public static final int InterfaceTxThrottleResult = 219;
    108         public static final int QuotaCounterResult        = 220;
    109         public static final int TetheringStatsResult      = 221;
    110 
    111         public static final int InterfaceChange           = 600;
    112         public static final int BandwidthControl          = 601;
    113     }
    114 
    115     /**
    116      * Binder context for this service
    117      */
    118     private Context mContext;
    119 
    120     /**
    121      * connector object for communicating with netd
    122      */
    123     private NativeDaemonConnector mConnector;
    124 
    125     private Thread mThread;
    126     private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
    127 
    128     // TODO: replace with RemoteCallbackList
    129     private ArrayList<INetworkManagementEventObserver> mObservers;
    130 
    131     private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
    132 
    133     private Object mQuotaLock = new Object();
    134     /** Set of interfaces with active quotas. */
    135     private HashSet<String> mActiveQuotaIfaces = Sets.newHashSet();
    136     /** Set of interfaces with active alerts. */
    137     private HashSet<String> mActiveAlertIfaces = Sets.newHashSet();
    138     /** Set of UIDs with active reject rules. */
    139     private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray();
    140 
    141     private volatile boolean mBandwidthControlEnabled;
    142 
    143     /**
    144      * Constructs a new NetworkManagementService instance
    145      *
    146      * @param context  Binder context for this service
    147      */
    148     private NetworkManagementService(Context context) {
    149         mContext = context;
    150         mObservers = new ArrayList<INetworkManagementEventObserver>();
    151 
    152         if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
    153             return;
    154         }
    155 
    156         mConnector = new NativeDaemonConnector(
    157                 new NetdCallbackReceiver(), "netd", 10, NETD_TAG);
    158         mThread = new Thread(mConnector, NETD_TAG);
    159 
    160         // Add ourself to the Watchdog monitors.
    161         Watchdog.getInstance().addMonitor(this);
    162     }
    163 
    164     public static NetworkManagementService create(Context context) throws InterruptedException {
    165         NetworkManagementService service = new NetworkManagementService(context);
    166         if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
    167         service.mThread.start();
    168         if (DBG) Slog.d(TAG, "Awaiting socket connection");
    169         service.mConnectedSignal.await();
    170         if (DBG) Slog.d(TAG, "Connected");
    171         return service;
    172     }
    173 
    174     public void systemReady() {
    175         // only enable bandwidth control when support exists, and requested by
    176         // system setting.
    177         final boolean hasKernelSupport = new File("/proc/net/xt_qtaguid/ctrl").exists();
    178         final boolean shouldEnable =
    179                 Settings.Secure.getInt(mContext.getContentResolver(), NETSTATS_ENABLED, 1) != 0;
    180 
    181         if (hasKernelSupport && shouldEnable) {
    182             Slog.d(TAG, "enabling bandwidth control");
    183             try {
    184                 mConnector.doCommand("bandwidth enable");
    185                 mBandwidthControlEnabled = true;
    186             } catch (NativeDaemonConnectorException e) {
    187                 Log.wtf(TAG, "problem enabling bandwidth controls", e);
    188             }
    189         } else {
    190             Slog.d(TAG, "not enabling bandwidth control");
    191         }
    192 
    193         SystemProperties.set(PROP_QTAGUID_ENABLED, mBandwidthControlEnabled ? "1" : "0");
    194     }
    195 
    196     public void registerObserver(INetworkManagementEventObserver obs) {
    197         Slog.d(TAG, "Registering observer");
    198         mObservers.add(obs);
    199     }
    200 
    201     public void unregisterObserver(INetworkManagementEventObserver obs) {
    202         Slog.d(TAG, "Unregistering observer");
    203         mObservers.remove(mObservers.indexOf(obs));
    204     }
    205 
    206     /**
    207      * Notify our observers of an interface status change
    208      */
    209     private void notifyInterfaceStatusChanged(String iface, boolean up) {
    210         for (INetworkManagementEventObserver obs : mObservers) {
    211             try {
    212                 obs.interfaceStatusChanged(iface, up);
    213             } catch (Exception ex) {
    214                 Slog.w(TAG, "Observer notifier failed", ex);
    215             }
    216         }
    217     }
    218 
    219     /**
    220      * Notify our observers of an interface link state change
    221      * (typically, an Ethernet cable has been plugged-in or unplugged).
    222      */
    223     private void notifyInterfaceLinkStateChanged(String iface, boolean up) {
    224         for (INetworkManagementEventObserver obs : mObservers) {
    225             try {
    226                 obs.interfaceLinkStateChanged(iface, up);
    227             } catch (Exception ex) {
    228                 Slog.w(TAG, "Observer notifier failed", ex);
    229             }
    230         }
    231     }
    232 
    233     /**
    234      * Notify our observers of an interface addition.
    235      */
    236     private void notifyInterfaceAdded(String iface) {
    237         for (INetworkManagementEventObserver obs : mObservers) {
    238             try {
    239                 obs.interfaceAdded(iface);
    240             } catch (Exception ex) {
    241                 Slog.w(TAG, "Observer notifier failed", ex);
    242             }
    243         }
    244     }
    245 
    246     /**
    247      * Notify our observers of an interface removal.
    248      */
    249     private void notifyInterfaceRemoved(String iface) {
    250         // netd already clears out quota and alerts for removed ifaces; update
    251         // our sanity-checking state.
    252         mActiveAlertIfaces.remove(iface);
    253         mActiveQuotaIfaces.remove(iface);
    254 
    255         for (INetworkManagementEventObserver obs : mObservers) {
    256             try {
    257                 obs.interfaceRemoved(iface);
    258             } catch (Exception ex) {
    259                 Slog.w(TAG, "Observer notifier failed", ex);
    260             }
    261         }
    262     }
    263 
    264     /**
    265      * Notify our observers of a limit reached.
    266      */
    267     private void notifyLimitReached(String limitName, String iface) {
    268         for (INetworkManagementEventObserver obs : mObservers) {
    269             try {
    270                 obs.limitReached(limitName, iface);
    271             } catch (Exception ex) {
    272                 Slog.w(TAG, "Observer notifier failed", ex);
    273             }
    274         }
    275     }
    276 
    277     /**
    278      * Let us know the daemon is connected
    279      */
    280     protected void onDaemonConnected() {
    281         if (DBG) Slog.d(TAG, "onConnected");
    282         mConnectedSignal.countDown();
    283     }
    284 
    285 
    286     //
    287     // Netd Callback handling
    288     //
    289 
    290     class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {
    291         /** {@inheritDoc} */
    292         public void onDaemonConnected() {
    293             NetworkManagementService.this.onDaemonConnected();
    294         }
    295 
    296         /** {@inheritDoc} */
    297         public boolean onEvent(int code, String raw, String[] cooked) {
    298             switch (code) {
    299             case NetdResponseCode.InterfaceChange:
    300                     /*
    301                      * a network interface change occured
    302                      * Format: "NNN Iface added <name>"
    303                      *         "NNN Iface removed <name>"
    304                      *         "NNN Iface changed <name> <up/down>"
    305                      *         "NNN Iface linkstatus <name> <up/down>"
    306                      */
    307                     if (cooked.length < 4 || !cooked[1].equals("Iface")) {
    308                         throw new IllegalStateException(
    309                                 String.format("Invalid event from daemon (%s)", raw));
    310                     }
    311                     if (cooked[2].equals("added")) {
    312                         notifyInterfaceAdded(cooked[3]);
    313                         return true;
    314                     } else if (cooked[2].equals("removed")) {
    315                         notifyInterfaceRemoved(cooked[3]);
    316                         return true;
    317                     } else if (cooked[2].equals("changed") && cooked.length == 5) {
    318                         notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up"));
    319                         return true;
    320                     } else if (cooked[2].equals("linkstate") && cooked.length == 5) {
    321                         notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up"));
    322                         return true;
    323                     }
    324                     throw new IllegalStateException(
    325                             String.format("Invalid event from daemon (%s)", raw));
    326                     // break;
    327             case NetdResponseCode.BandwidthControl:
    328                     /*
    329                      * Bandwidth control needs some attention
    330                      * Format: "NNN limit alert <alertName> <ifaceName>"
    331                      */
    332                     if (cooked.length < 5 || !cooked[1].equals("limit")) {
    333                         throw new IllegalStateException(
    334                                 String.format("Invalid event from daemon (%s)", raw));
    335                     }
    336                     if (cooked[2].equals("alert")) {
    337                         notifyLimitReached(cooked[3], cooked[4]);
    338                         return true;
    339                     }
    340                     throw new IllegalStateException(
    341                             String.format("Invalid event from daemon (%s)", raw));
    342                     // break;
    343             default: break;
    344             }
    345             return false;
    346         }
    347     }
    348 
    349 
    350     //
    351     // INetworkManagementService members
    352     //
    353 
    354     public String[] listInterfaces() throws IllegalStateException {
    355         mContext.enforceCallingOrSelfPermission(
    356                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
    357 
    358         try {
    359             return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult);
    360         } catch (NativeDaemonConnectorException e) {
    361             throw new IllegalStateException(
    362                     "Cannot communicate with native daemon to list interfaces");
    363         }
    364     }
    365 
    366     public InterfaceConfiguration getInterfaceConfig(String iface) throws IllegalStateException {
    367         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
    368         String rsp;
    369         try {
    370             rsp = mConnector.doCommand("interface getcfg " + iface).get(0);
    371         } catch (NativeDaemonConnectorException e) {
    372             throw new IllegalStateException(
    373                     "Cannot communicate with native daemon to get interface config");
    374         }
    375         Slog.d(TAG, String.format("rsp <%s>", rsp));
    376 
    377         // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz [flag1 flag2 flag3]
    378         StringTokenizer st = new StringTokenizer(rsp);
    379 
    380         InterfaceConfiguration cfg;
    381         try {
    382             try {
    383                 int code = Integer.parseInt(st.nextToken(" "));
    384                 if (code != NetdResponseCode.InterfaceGetCfgResult) {
    385                     throw new IllegalStateException(
    386                         String.format("Expected code %d, but got %d",
    387                                 NetdResponseCode.InterfaceGetCfgResult, code));
    388                 }
    389             } catch (NumberFormatException nfe) {
    390                 throw new IllegalStateException(
    391                         String.format("Invalid response from daemon (%s)", rsp));
    392             }
    393 
    394             cfg = new InterfaceConfiguration();
    395             cfg.hwAddr = st.nextToken(" ");
    396             InetAddress addr = null;
    397             int prefixLength = 0;
    398             try {
    399                 addr = NetworkUtils.numericToInetAddress(st.nextToken(" "));
    400             } catch (IllegalArgumentException iae) {
    401                 Slog.e(TAG, "Failed to parse ipaddr", iae);
    402             }
    403 
    404             try {
    405                 prefixLength = Integer.parseInt(st.nextToken(" "));
    406             } catch (NumberFormatException nfe) {
    407                 Slog.e(TAG, "Failed to parse prefixLength", nfe);
    408             }
    409 
    410             cfg.addr = new LinkAddress(addr, prefixLength);
    411             cfg.interfaceFlags = st.nextToken("]").trim() +"]";
    412         } catch (NoSuchElementException nsee) {
    413             throw new IllegalStateException(
    414                     String.format("Invalid response from daemon (%s)", rsp));
    415         }
    416         Slog.d(TAG, String.format("flags <%s>", cfg.interfaceFlags));
    417         return cfg;
    418     }
    419 
    420     public void setInterfaceConfig(
    421             String iface, InterfaceConfiguration cfg) throws IllegalStateException {
    422         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
    423         LinkAddress linkAddr = cfg.addr;
    424         if (linkAddr == null || linkAddr.getAddress() == null) {
    425             throw new IllegalStateException("Null LinkAddress given");
    426         }
    427         String cmd = String.format("interface setcfg %s %s %d %s", iface,
    428                 linkAddr.getAddress().getHostAddress(),
    429                 linkAddr.getNetworkPrefixLength(),
    430                 cfg.interfaceFlags);
    431         try {
    432             mConnector.doCommand(cmd);
    433         } catch (NativeDaemonConnectorException e) {
    434             throw new IllegalStateException(
    435                     "Unable to communicate with native daemon to interface setcfg - " + e);
    436         }
    437     }
    438 
    439     public void setInterfaceDown(String iface) throws IllegalStateException {
    440         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
    441         try {
    442             InterfaceConfiguration ifcg = getInterfaceConfig(iface);
    443             ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down");
    444             setInterfaceConfig(iface, ifcg);
    445         } catch (NativeDaemonConnectorException e) {
    446             throw new IllegalStateException(
    447                     "Unable to communicate with native daemon for interface down - " + e);
    448         }
    449     }
    450 
    451     public void setInterfaceUp(String iface) throws IllegalStateException {
    452         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
    453         try {
    454             InterfaceConfiguration ifcg = getInterfaceConfig(iface);
    455             ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
    456             setInterfaceConfig(iface, ifcg);
    457         } catch (NativeDaemonConnectorException e) {
    458             throw new IllegalStateException(
    459                     "Unable to communicate with native daemon for interface up - " + e);
    460         }
    461     }
    462 
    463     public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable)
    464             throws IllegalStateException {
    465         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
    466         String cmd = String.format("interface ipv6privacyextensions %s %s", iface,
    467                 enable ? "enable" : "disable");
    468         try {
    469             mConnector.doCommand(cmd);
    470         } catch (NativeDaemonConnectorException e) {
    471             throw new IllegalStateException(
    472                     "Unable to communicate with native daemon to set ipv6privacyextensions - " + e);
    473         }
    474     }
    475 
    476 
    477 
    478     /* TODO: This is right now a IPv4 only function. Works for wifi which loses its
    479        IPv6 addresses on interface down, but we need to do full clean up here */
    480     public void clearInterfaceAddresses(String iface) throws IllegalStateException {
    481         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
    482         String cmd = String.format("interface clearaddrs %s", iface);
    483         try {
    484             mConnector.doCommand(cmd);
    485         } catch (NativeDaemonConnectorException e) {
    486             throw new IllegalStateException(
    487                     "Unable to communicate with native daemon to interface clearallips - " + e);
    488         }
    489     }
    490 
    491     public void enableIpv6(String iface) throws IllegalStateException {
    492         mContext.enforceCallingOrSelfPermission(
    493                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    494         try {
    495             mConnector.doCommand(String.format("interface ipv6 %s enable", iface));
    496         } catch (NativeDaemonConnectorException e) {
    497             throw new IllegalStateException(
    498                     "Unable to communicate to native daemon for enabling ipv6");
    499         }
    500     }
    501 
    502     public void disableIpv6(String iface) throws IllegalStateException {
    503         mContext.enforceCallingOrSelfPermission(
    504                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    505         try {
    506             mConnector.doCommand(String.format("interface ipv6 %s disable", iface));
    507         } catch (NativeDaemonConnectorException e) {
    508             throw new IllegalStateException(
    509                     "Unable to communicate to native daemon for disabling ipv6");
    510         }
    511     }
    512 
    513     public void addRoute(String interfaceName, RouteInfo route) {
    514         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
    515         modifyRoute(interfaceName, ADD, route, DEFAULT);
    516     }
    517 
    518     public void removeRoute(String interfaceName, RouteInfo route) {
    519         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
    520         modifyRoute(interfaceName, REMOVE, route, DEFAULT);
    521     }
    522 
    523     public void addSecondaryRoute(String interfaceName, RouteInfo route) {
    524         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
    525         modifyRoute(interfaceName, ADD, route, SECONDARY);
    526     }
    527 
    528     public void removeSecondaryRoute(String interfaceName, RouteInfo route) {
    529         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
    530         modifyRoute(interfaceName, REMOVE, route, SECONDARY);
    531     }
    532 
    533     private void modifyRoute(String interfaceName, int action, RouteInfo route, String type) {
    534         ArrayList<String> rsp;
    535 
    536         StringBuilder cmd;
    537 
    538         switch (action) {
    539             case ADD:
    540             {
    541                 cmd = new StringBuilder("interface route add " + interfaceName + " " + type);
    542                 break;
    543             }
    544             case REMOVE:
    545             {
    546                 cmd = new StringBuilder("interface route remove " + interfaceName + " " + type);
    547                 break;
    548             }
    549             default:
    550                 throw new IllegalStateException("Unknown action type " + action);
    551         }
    552 
    553         // create triplet: dest-ip-addr prefixlength gateway-ip-addr
    554         LinkAddress la = route.getDestination();
    555         cmd.append(' ');
    556         cmd.append(la.getAddress().getHostAddress());
    557         cmd.append(' ');
    558         cmd.append(la.getNetworkPrefixLength());
    559         cmd.append(' ');
    560         if (route.getGateway() == null) {
    561             if (la.getAddress() instanceof Inet4Address) {
    562                 cmd.append("0.0.0.0");
    563             } else {
    564                 cmd.append ("::0");
    565             }
    566         } else {
    567             cmd.append(route.getGateway().getHostAddress());
    568         }
    569         try {
    570             rsp = mConnector.doCommand(cmd.toString());
    571         } catch (NativeDaemonConnectorException e) {
    572             throw new IllegalStateException(
    573                     "Unable to communicate with native dameon to add routes - "
    574                     + e);
    575         }
    576 
    577         if (DBG) {
    578             for (String line : rsp) {
    579                 Log.v(TAG, "add route response is " + line);
    580             }
    581         }
    582     }
    583 
    584     private ArrayList<String> readRouteList(String filename) {
    585         FileInputStream fstream = null;
    586         ArrayList<String> list = new ArrayList<String>();
    587 
    588         try {
    589             fstream = new FileInputStream(filename);
    590             DataInputStream in = new DataInputStream(fstream);
    591             BufferedReader br = new BufferedReader(new InputStreamReader(in));
    592             String s;
    593 
    594             // throw away the title line
    595 
    596             while (((s = br.readLine()) != null) && (s.length() != 0)) {
    597                 list.add(s);
    598             }
    599         } catch (IOException ex) {
    600             // return current list, possibly empty
    601         } finally {
    602             if (fstream != null) {
    603                 try {
    604                     fstream.close();
    605                 } catch (IOException ex) {}
    606             }
    607         }
    608 
    609         return list;
    610     }
    611 
    612     public RouteInfo[] getRoutes(String interfaceName) {
    613         mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
    614         ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>();
    615 
    616         // v4 routes listed as:
    617         // iface dest-addr gateway-addr flags refcnt use metric netmask mtu window IRTT
    618         for (String s : readRouteList("/proc/net/route")) {
    619             String[] fields = s.split("\t");
    620 
    621             if (fields.length > 7) {
    622                 String iface = fields[0];
    623 
    624                 if (interfaceName.equals(iface)) {
    625                     String dest = fields[1];
    626                     String gate = fields[2];
    627                     String flags = fields[3]; // future use?
    628                     String mask = fields[7];
    629                     try {
    630                         // address stored as a hex string, ex: 0014A8C0
    631                         InetAddress destAddr =
    632                                 NetworkUtils.intToInetAddress((int)Long.parseLong(dest, 16));
    633                         int prefixLength =
    634                                 NetworkUtils.netmaskIntToPrefixLength(
    635                                 (int)Long.parseLong(mask, 16));
    636                         LinkAddress linkAddress = new LinkAddress(destAddr, prefixLength);
    637 
    638                         // address stored as a hex string, ex 0014A8C0
    639                         InetAddress gatewayAddr =
    640                                 NetworkUtils.intToInetAddress((int)Long.parseLong(gate, 16));
    641 
    642                         RouteInfo route = new RouteInfo(linkAddress, gatewayAddr);
    643                         routes.add(route);
    644                     } catch (Exception e) {
    645                         Log.e(TAG, "Error parsing route " + s + " : " + e);
    646                         continue;
    647                     }
    648                 }
    649             }
    650         }
    651 
    652         // v6 routes listed as:
    653         // dest-addr prefixlength ?? ?? gateway-addr ?? ?? ?? ?? iface
    654         for (String s : readRouteList("/proc/net/ipv6_route")) {
    655             String[]fields = s.split("\\s+");
    656             if (fields.length > 9) {
    657                 String iface = fields[9].trim();
    658                 if (interfaceName.equals(iface)) {
    659                     String dest = fields[0];
    660                     String prefix = fields[1];
    661                     String gate = fields[4];
    662 
    663                     try {
    664                         // prefix length stored as a hex string, ex 40
    665                         int prefixLength = Integer.parseInt(prefix, 16);
    666 
    667                         // address stored as a 32 char hex string
    668                         // ex fe800000000000000000000000000000
    669                         InetAddress destAddr = NetworkUtils.hexToInet6Address(dest);
    670                         LinkAddress linkAddress = new LinkAddress(destAddr, prefixLength);
    671 
    672                         InetAddress gateAddr = NetworkUtils.hexToInet6Address(gate);
    673 
    674                         RouteInfo route = new RouteInfo(linkAddress, gateAddr);
    675                         routes.add(route);
    676                     } catch (Exception e) {
    677                         Log.e(TAG, "Error parsing route " + s + " : " + e);
    678                         continue;
    679                     }
    680                 }
    681             }
    682         }
    683         return (RouteInfo[]) routes.toArray(new RouteInfo[0]);
    684     }
    685 
    686     public void shutdown() {
    687         if (mContext.checkCallingOrSelfPermission(
    688                 android.Manifest.permission.SHUTDOWN)
    689                 != PackageManager.PERMISSION_GRANTED) {
    690             throw new SecurityException("Requires SHUTDOWN permission");
    691         }
    692 
    693         Slog.d(TAG, "Shutting down");
    694     }
    695 
    696     public boolean getIpForwardingEnabled() throws IllegalStateException{
    697         mContext.enforceCallingOrSelfPermission(
    698                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
    699 
    700         ArrayList<String> rsp;
    701         try {
    702             rsp = mConnector.doCommand("ipfwd status");
    703         } catch (NativeDaemonConnectorException e) {
    704             throw new IllegalStateException(
    705                     "Unable to communicate with native daemon to ipfwd status");
    706         }
    707 
    708         for (String line : rsp) {
    709             String[] tok = line.split(" ");
    710             if (tok.length < 3) {
    711                 Slog.e(TAG, "Malformed response from native daemon: " + line);
    712                 return false;
    713             }
    714 
    715             int code = Integer.parseInt(tok[0]);
    716             if (code == NetdResponseCode.IpFwdStatusResult) {
    717                 // 211 Forwarding <enabled/disabled>
    718                 return "enabled".equals(tok[2]);
    719             } else {
    720                 throw new IllegalStateException(String.format("Unexpected response code %d", code));
    721             }
    722         }
    723         throw new IllegalStateException("Got an empty response");
    724     }
    725 
    726     public void setIpForwardingEnabled(boolean enable) throws IllegalStateException {
    727         mContext.enforceCallingOrSelfPermission(
    728                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    729         mConnector.doCommand(String.format("ipfwd %sable", (enable ? "en" : "dis")));
    730     }
    731 
    732     public void startTethering(String[] dhcpRange)
    733              throws IllegalStateException {
    734         mContext.enforceCallingOrSelfPermission(
    735                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    736         // cmd is "tether start first_start first_stop second_start second_stop ..."
    737         // an odd number of addrs will fail
    738         String cmd = "tether start";
    739         for (String d : dhcpRange) {
    740             cmd += " " + d;
    741         }
    742 
    743         try {
    744             mConnector.doCommand(cmd);
    745         } catch (NativeDaemonConnectorException e) {
    746             throw new IllegalStateException("Unable to communicate to native daemon");
    747         }
    748     }
    749 
    750     public void stopTethering() throws IllegalStateException {
    751         mContext.enforceCallingOrSelfPermission(
    752                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    753         try {
    754             mConnector.doCommand("tether stop");
    755         } catch (NativeDaemonConnectorException e) {
    756             throw new IllegalStateException("Unable to communicate to native daemon to stop tether");
    757         }
    758     }
    759 
    760     public boolean isTetheringStarted() throws IllegalStateException {
    761         mContext.enforceCallingOrSelfPermission(
    762                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
    763 
    764         ArrayList<String> rsp;
    765         try {
    766             rsp = mConnector.doCommand("tether status");
    767         } catch (NativeDaemonConnectorException e) {
    768             throw new IllegalStateException(
    769                     "Unable to communicate to native daemon to get tether status");
    770         }
    771 
    772         for (String line : rsp) {
    773             String[] tok = line.split(" ");
    774             if (tok.length < 3) {
    775                 throw new IllegalStateException("Malformed response for tether status: " + line);
    776             }
    777             int code = Integer.parseInt(tok[0]);
    778             if (code == NetdResponseCode.TetherStatusResult) {
    779                 // XXX: Tethering services <started/stopped> <TBD>...
    780                 return "started".equals(tok[2]);
    781             } else {
    782                 throw new IllegalStateException(String.format("Unexpected response code %d", code));
    783             }
    784         }
    785         throw new IllegalStateException("Got an empty response");
    786     }
    787 
    788     public void tetherInterface(String iface) throws IllegalStateException {
    789         mContext.enforceCallingOrSelfPermission(
    790                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    791         try {
    792             mConnector.doCommand("tether interface add " + iface);
    793         } catch (NativeDaemonConnectorException e) {
    794             throw new IllegalStateException(
    795                     "Unable to communicate to native daemon for adding tether interface");
    796         }
    797     }
    798 
    799     public void untetherInterface(String iface) {
    800         mContext.enforceCallingOrSelfPermission(
    801                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    802         try {
    803             mConnector.doCommand("tether interface remove " + iface);
    804         } catch (NativeDaemonConnectorException e) {
    805             throw new IllegalStateException(
    806                     "Unable to communicate to native daemon for removing tether interface");
    807         }
    808     }
    809 
    810     public String[] listTetheredInterfaces() throws IllegalStateException {
    811         mContext.enforceCallingOrSelfPermission(
    812                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
    813         try {
    814             return mConnector.doListCommand(
    815                     "tether interface list", NetdResponseCode.TetherInterfaceListResult);
    816         } catch (NativeDaemonConnectorException e) {
    817             throw new IllegalStateException(
    818                     "Unable to communicate to native daemon for listing tether interfaces");
    819         }
    820     }
    821 
    822     public void setDnsForwarders(String[] dns) throws IllegalStateException {
    823         mContext.enforceCallingOrSelfPermission(
    824                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    825         try {
    826             String cmd = "tether dns set";
    827             for (String s : dns) {
    828                 cmd += " " + NetworkUtils.numericToInetAddress(s).getHostAddress();
    829             }
    830             try {
    831                 mConnector.doCommand(cmd);
    832             } catch (NativeDaemonConnectorException e) {
    833                 throw new IllegalStateException(
    834                         "Unable to communicate to native daemon for setting tether dns");
    835             }
    836         } catch (IllegalArgumentException e) {
    837             throw new IllegalStateException("Error resolving dns name", e);
    838         }
    839     }
    840 
    841     public String[] getDnsForwarders() throws IllegalStateException {
    842         mContext.enforceCallingOrSelfPermission(
    843                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
    844         try {
    845             return mConnector.doListCommand(
    846                     "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult);
    847         } catch (NativeDaemonConnectorException e) {
    848             throw new IllegalStateException(
    849                     "Unable to communicate to native daemon for listing tether dns");
    850         }
    851     }
    852 
    853     private void modifyNat(String cmd, String internalInterface, String externalInterface)
    854             throws SocketException {
    855         cmd = String.format("nat %s %s %s", cmd, internalInterface, externalInterface);
    856 
    857         NetworkInterface internalNetworkInterface =
    858                 NetworkInterface.getByName(internalInterface);
    859         if (internalNetworkInterface == null) {
    860             cmd += " 0";
    861         } else {
    862             Collection<InterfaceAddress>interfaceAddresses =
    863                     internalNetworkInterface.getInterfaceAddresses();
    864             cmd += " " + interfaceAddresses.size();
    865             for (InterfaceAddress ia : interfaceAddresses) {
    866                 InetAddress addr = NetworkUtils.getNetworkPart(ia.getAddress(),
    867                         ia.getNetworkPrefixLength());
    868                 cmd = cmd + " " + addr.getHostAddress() + "/" + ia.getNetworkPrefixLength();
    869             }
    870         }
    871 
    872         mConnector.doCommand(cmd);
    873     }
    874 
    875     public void enableNat(String internalInterface, String externalInterface)
    876             throws IllegalStateException {
    877         mContext.enforceCallingOrSelfPermission(
    878                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    879         if (DBG) Log.d(TAG, "enableNat(" + internalInterface + ", " + externalInterface + ")");
    880         try {
    881             modifyNat("enable", internalInterface, externalInterface);
    882         } catch (Exception e) {
    883             Log.e(TAG, "enableNat got Exception " + e.toString());
    884             throw new IllegalStateException(
    885                     "Unable to communicate to native daemon for enabling NAT interface");
    886         }
    887     }
    888 
    889     public void disableNat(String internalInterface, String externalInterface)
    890             throws IllegalStateException {
    891         mContext.enforceCallingOrSelfPermission(
    892                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    893         if (DBG) Log.d(TAG, "disableNat(" + internalInterface + ", " + externalInterface + ")");
    894         try {
    895             modifyNat("disable", internalInterface, externalInterface);
    896         } catch (Exception e) {
    897             Log.e(TAG, "disableNat got Exception " + e.toString());
    898             throw new IllegalStateException(
    899                     "Unable to communicate to native daemon for disabling NAT interface");
    900         }
    901     }
    902 
    903     public String[] listTtys() throws IllegalStateException {
    904         mContext.enforceCallingOrSelfPermission(
    905                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
    906         try {
    907             return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult);
    908         } catch (NativeDaemonConnectorException e) {
    909             throw new IllegalStateException(
    910                     "Unable to communicate to native daemon for listing TTYs");
    911         }
    912     }
    913 
    914     public void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr,
    915             String dns2Addr) throws IllegalStateException {
    916         try {
    917             mContext.enforceCallingOrSelfPermission(
    918                     android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    919             mConnector.doCommand(String.format("pppd attach %s %s %s %s %s", tty,
    920                     NetworkUtils.numericToInetAddress(localAddr).getHostAddress(),
    921                     NetworkUtils.numericToInetAddress(remoteAddr).getHostAddress(),
    922                     NetworkUtils.numericToInetAddress(dns1Addr).getHostAddress(),
    923                     NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress()));
    924         } catch (IllegalArgumentException e) {
    925             throw new IllegalStateException("Error resolving addr", e);
    926         } catch (NativeDaemonConnectorException e) {
    927             throw new IllegalStateException("Error communicating to native daemon to attach pppd", e);
    928         }
    929     }
    930 
    931     public void detachPppd(String tty) throws IllegalStateException {
    932         mContext.enforceCallingOrSelfPermission(
    933                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    934         try {
    935             mConnector.doCommand(String.format("pppd detach %s", tty));
    936         } catch (NativeDaemonConnectorException e) {
    937             throw new IllegalStateException("Error communicating to native daemon to detach pppd", e);
    938         }
    939     }
    940 
    941     public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface)
    942              throws IllegalStateException {
    943         mContext.enforceCallingOrSelfPermission(
    944                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    945         mContext.enforceCallingOrSelfPermission(
    946                 android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
    947         try {
    948             wifiFirmwareReload(wlanIface, "AP");
    949             mConnector.doCommand(String.format("softap start " + wlanIface));
    950             if (wifiConfig == null) {
    951                 mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface));
    952             } else {
    953                 /**
    954                  * softap set arg1 arg2 arg3 [arg4 arg5 arg6 arg7 arg8]
    955                  * argv1 - wlan interface
    956                  * argv2 - softap interface
    957                  * argv3 - SSID
    958                  * argv4 - Security
    959                  * argv5 - Key
    960                  * argv6 - Channel
    961                  * argv7 - Preamble
    962                  * argv8 - Max SCB
    963                  */
    964                  String str = String.format("softap set " + wlanIface + " " + softapIface +
    965                                        " %s %s %s", convertQuotedString(wifiConfig.SSID),
    966                                        getSecurityType(wifiConfig),
    967                                        convertQuotedString(wifiConfig.preSharedKey));
    968                 mConnector.doCommand(str);
    969             }
    970             mConnector.doCommand(String.format("softap startap"));
    971         } catch (NativeDaemonConnectorException e) {
    972             throw new IllegalStateException("Error communicating to native daemon to start softap", e);
    973         }
    974     }
    975 
    976     private String convertQuotedString(String s) {
    977         if (s == null) {
    978             return s;
    979         }
    980         /* Replace \ with \\, then " with \" and add quotes at end */
    981         return '"' + s.replaceAll("\\\\","\\\\\\\\").replaceAll("\"","\\\\\"") + '"';
    982     }
    983 
    984     private String getSecurityType(WifiConfiguration wifiConfig) {
    985         switch (wifiConfig.getAuthType()) {
    986             case KeyMgmt.WPA_PSK:
    987                 return "wpa-psk";
    988             case KeyMgmt.WPA2_PSK:
    989                 return "wpa2-psk";
    990             default:
    991                 return "open";
    992         }
    993     }
    994 
    995     /* @param mode can be "AP", "STA" or "P2P" */
    996     public void wifiFirmwareReload(String wlanIface, String mode) throws IllegalStateException {
    997         mContext.enforceCallingOrSelfPermission(
    998                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
    999         mContext.enforceCallingOrSelfPermission(
   1000                 android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
   1001 
   1002         try {
   1003             mConnector.doCommand(String.format("softap fwreload " + wlanIface + " " + mode));
   1004         } catch (NativeDaemonConnectorException e) {
   1005             throw new IllegalStateException("Error communicating to native daemon ", e);
   1006         }
   1007     }
   1008 
   1009     public void stopAccessPoint(String wlanIface) throws IllegalStateException {
   1010         mContext.enforceCallingOrSelfPermission(
   1011                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
   1012         mContext.enforceCallingOrSelfPermission(
   1013                 android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
   1014         try {
   1015             mConnector.doCommand("softap stopap");
   1016             mConnector.doCommand("softap stop " + wlanIface);
   1017             wifiFirmwareReload(wlanIface, "STA");
   1018         } catch (NativeDaemonConnectorException e) {
   1019             throw new IllegalStateException("Error communicating to native daemon to stop soft AP",
   1020                     e);
   1021         }
   1022     }
   1023 
   1024     public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface)
   1025             throws IllegalStateException {
   1026         mContext.enforceCallingOrSelfPermission(
   1027                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
   1028         mContext.enforceCallingOrSelfPermission(
   1029             android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
   1030         try {
   1031             if (wifiConfig == null) {
   1032                 mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface));
   1033             } else {
   1034                 String str = String.format("softap set " + wlanIface + " " + softapIface
   1035                         + " %s %s %s", convertQuotedString(wifiConfig.SSID),
   1036                         getSecurityType(wifiConfig),
   1037                         convertQuotedString(wifiConfig.preSharedKey));
   1038                 mConnector.doCommand(str);
   1039             }
   1040         } catch (NativeDaemonConnectorException e) {
   1041             throw new IllegalStateException("Error communicating to native daemon to set soft AP",
   1042                     e);
   1043         }
   1044     }
   1045 
   1046     private long getInterfaceCounter(String iface, boolean rx) {
   1047         mContext.enforceCallingOrSelfPermission(
   1048                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
   1049         try {
   1050             String rsp;
   1051             try {
   1052                 rsp = mConnector.doCommand(
   1053                         String.format("interface read%scounter %s", (rx ? "rx" : "tx"), iface)).get(0);
   1054             } catch (NativeDaemonConnectorException e1) {
   1055                 Slog.e(TAG, "Error communicating with native daemon", e1);
   1056                 return -1;
   1057             }
   1058 
   1059             String[] tok = rsp.split(" ");
   1060             if (tok.length < 2) {
   1061                 Slog.e(TAG, String.format("Malformed response for reading %s interface",
   1062                         (rx ? "rx" : "tx")));
   1063                 return -1;
   1064             }
   1065 
   1066             int code;
   1067             try {
   1068                 code = Integer.parseInt(tok[0]);
   1069             } catch (NumberFormatException nfe) {
   1070                 Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
   1071                 return -1;
   1072             }
   1073             if ((rx && code != NetdResponseCode.InterfaceRxCounterResult) || (
   1074                     !rx && code != NetdResponseCode.InterfaceTxCounterResult)) {
   1075                 Slog.e(TAG, String.format("Unexpected response code %d", code));
   1076                 return -1;
   1077             }
   1078             return Long.parseLong(tok[1]);
   1079         } catch (Exception e) {
   1080             Slog.e(TAG, String.format(
   1081                     "Failed to read interface %s counters", (rx ? "rx" : "tx")), e);
   1082         }
   1083         return -1;
   1084     }
   1085 
   1086     @Override
   1087     public NetworkStats getNetworkStatsSummary() {
   1088         mContext.enforceCallingOrSelfPermission(
   1089                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
   1090         return mStatsFactory.readNetworkStatsSummary();
   1091     }
   1092 
   1093     @Override
   1094     public NetworkStats getNetworkStatsDetail() {
   1095         mContext.enforceCallingOrSelfPermission(
   1096                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
   1097         return mStatsFactory.readNetworkStatsDetail(UID_ALL);
   1098     }
   1099 
   1100     @Override
   1101     public void setInterfaceQuota(String iface, long quotaBytes) {
   1102         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
   1103 
   1104         // silently discard when control disabled
   1105         // TODO: eventually migrate to be always enabled
   1106         if (!mBandwidthControlEnabled) return;
   1107 
   1108         synchronized (mQuotaLock) {
   1109             if (mActiveQuotaIfaces.contains(iface)) {
   1110                 throw new IllegalStateException("iface " + iface + " already has quota");
   1111             }
   1112 
   1113             final StringBuilder command = new StringBuilder();
   1114             command.append("bandwidth setiquota ").append(iface).append(" ").append(quotaBytes);
   1115 
   1116             try {
   1117                 // TODO: support quota shared across interfaces
   1118                 mConnector.doCommand(command.toString());
   1119                 mActiveQuotaIfaces.add(iface);
   1120             } catch (NativeDaemonConnectorException e) {
   1121                 throw new IllegalStateException("Error communicating to native daemon", e);
   1122             }
   1123         }
   1124     }
   1125 
   1126     @Override
   1127     public void removeInterfaceQuota(String iface) {
   1128         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
   1129 
   1130         // silently discard when control disabled
   1131         // TODO: eventually migrate to be always enabled
   1132         if (!mBandwidthControlEnabled) return;
   1133 
   1134         synchronized (mQuotaLock) {
   1135             if (!mActiveQuotaIfaces.contains(iface)) {
   1136                 // TODO: eventually consider throwing
   1137                 return;
   1138             }
   1139 
   1140             final StringBuilder command = new StringBuilder();
   1141             command.append("bandwidth removeiquota ").append(iface);
   1142 
   1143             mActiveQuotaIfaces.remove(iface);
   1144             mActiveAlertIfaces.remove(iface);
   1145 
   1146             try {
   1147                 // TODO: support quota shared across interfaces
   1148                 mConnector.doCommand(command.toString());
   1149             } catch (NativeDaemonConnectorException e) {
   1150                 // TODO: include current iptables state
   1151                 throw new IllegalStateException("Error communicating to native daemon", e);
   1152             }
   1153         }
   1154     }
   1155 
   1156     @Override
   1157     public void setInterfaceAlert(String iface, long alertBytes) {
   1158         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
   1159 
   1160         // silently discard when control disabled
   1161         // TODO: eventually migrate to be always enabled
   1162         if (!mBandwidthControlEnabled) return;
   1163 
   1164         // quick sanity check
   1165         if (!mActiveQuotaIfaces.contains(iface)) {
   1166             throw new IllegalStateException("setting alert requires existing quota on iface");
   1167         }
   1168 
   1169         synchronized (mQuotaLock) {
   1170             if (mActiveAlertIfaces.contains(iface)) {
   1171                 throw new IllegalStateException("iface " + iface + " already has alert");
   1172             }
   1173 
   1174             final StringBuilder command = new StringBuilder();
   1175             command.append("bandwidth setinterfacealert ").append(iface).append(" ").append(
   1176                     alertBytes);
   1177 
   1178             try {
   1179                 // TODO: support alert shared across interfaces
   1180                 mConnector.doCommand(command.toString());
   1181                 mActiveAlertIfaces.add(iface);
   1182             } catch (NativeDaemonConnectorException e) {
   1183                 throw new IllegalStateException("Error communicating to native daemon", e);
   1184             }
   1185         }
   1186     }
   1187 
   1188     @Override
   1189     public void removeInterfaceAlert(String iface) {
   1190         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
   1191 
   1192         // silently discard when control disabled
   1193         // TODO: eventually migrate to be always enabled
   1194         if (!mBandwidthControlEnabled) return;
   1195 
   1196         synchronized (mQuotaLock) {
   1197             if (!mActiveAlertIfaces.contains(iface)) {
   1198                 // TODO: eventually consider throwing
   1199                 return;
   1200             }
   1201 
   1202             final StringBuilder command = new StringBuilder();
   1203             command.append("bandwidth removeinterfacealert ").append(iface);
   1204 
   1205             try {
   1206                 // TODO: support alert shared across interfaces
   1207                 mConnector.doCommand(command.toString());
   1208                 mActiveAlertIfaces.remove(iface);
   1209             } catch (NativeDaemonConnectorException e) {
   1210                 throw new IllegalStateException("Error communicating to native daemon", e);
   1211             }
   1212         }
   1213     }
   1214 
   1215     @Override
   1216     public void setGlobalAlert(long alertBytes) {
   1217         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
   1218 
   1219         // silently discard when control disabled
   1220         // TODO: eventually migrate to be always enabled
   1221         if (!mBandwidthControlEnabled) return;
   1222 
   1223         final StringBuilder command = new StringBuilder();
   1224         command.append("bandwidth setglobalalert ").append(alertBytes);
   1225 
   1226         try {
   1227             mConnector.doCommand(command.toString());
   1228         } catch (NativeDaemonConnectorException e) {
   1229             throw new IllegalStateException("Error communicating to native daemon", e);
   1230         }
   1231     }
   1232 
   1233     @Override
   1234     public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
   1235         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
   1236 
   1237         // silently discard when control disabled
   1238         // TODO: eventually migrate to be always enabled
   1239         if (!mBandwidthControlEnabled) return;
   1240 
   1241         synchronized (mUidRejectOnQuota) {
   1242             final boolean oldRejectOnQuota = mUidRejectOnQuota.get(uid, false);
   1243             if (oldRejectOnQuota == rejectOnQuotaInterfaces) {
   1244                 // TODO: eventually consider throwing
   1245                 return;
   1246             }
   1247 
   1248             final StringBuilder command = new StringBuilder();
   1249             command.append("bandwidth");
   1250             if (rejectOnQuotaInterfaces) {
   1251                 command.append(" addnaughtyapps");
   1252             } else {
   1253                 command.append(" removenaughtyapps");
   1254             }
   1255             command.append(" ").append(uid);
   1256 
   1257             try {
   1258                 mConnector.doCommand(command.toString());
   1259                 if (rejectOnQuotaInterfaces) {
   1260                     mUidRejectOnQuota.put(uid, true);
   1261                 } else {
   1262                     mUidRejectOnQuota.delete(uid);
   1263                 }
   1264             } catch (NativeDaemonConnectorException e) {
   1265                 throw new IllegalStateException("Error communicating to native daemon", e);
   1266             }
   1267         }
   1268     }
   1269 
   1270     @Override
   1271     public boolean isBandwidthControlEnabled() {
   1272         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
   1273         return mBandwidthControlEnabled;
   1274     }
   1275 
   1276     @Override
   1277     public NetworkStats getNetworkStatsUidDetail(int uid) {
   1278         if (Binder.getCallingUid() != uid) {
   1279             mContext.enforceCallingOrSelfPermission(
   1280                     android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
   1281         }
   1282         return mStatsFactory.readNetworkStatsDetail(uid);
   1283     }
   1284 
   1285     @Override
   1286     public NetworkStats getNetworkStatsTethering(String[] ifacePairs) {
   1287         mContext.enforceCallingOrSelfPermission(
   1288                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
   1289 
   1290         if (ifacePairs.length % 2 != 0) {
   1291             throw new IllegalArgumentException(
   1292                     "unexpected ifacePairs; length=" + ifacePairs.length);
   1293         }
   1294 
   1295         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
   1296         for (int i = 0; i < ifacePairs.length; i += 2) {
   1297             final String ifaceIn = ifacePairs[i];
   1298             final String ifaceOut = ifacePairs[i + 1];
   1299             if (ifaceIn != null && ifaceOut != null) {
   1300                 stats.combineValues(getNetworkStatsTethering(ifaceIn, ifaceOut));
   1301             }
   1302         }
   1303         return stats;
   1304     }
   1305 
   1306     private NetworkStats.Entry getNetworkStatsTethering(String ifaceIn, String ifaceOut) {
   1307         final StringBuilder command = new StringBuilder();
   1308         command.append("bandwidth gettetherstats ").append(ifaceIn).append(" ").append(ifaceOut);
   1309 
   1310         final String rsp;
   1311         try {
   1312             rsp = mConnector.doCommand(command.toString()).get(0);
   1313         } catch (NativeDaemonConnectorException e) {
   1314             throw new IllegalStateException("Error communicating to native daemon", e);
   1315         }
   1316 
   1317         final String[] tok = rsp.split(" ");
   1318         /* Expecting: "code ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets" */
   1319         if (tok.length != 7) {
   1320             throw new IllegalStateException("Native daemon returned unexpected result: " + rsp);
   1321         }
   1322 
   1323         final int code;
   1324         try {
   1325             code = Integer.parseInt(tok[0]);
   1326         } catch (NumberFormatException e) {
   1327             throw new IllegalStateException(
   1328                     "Failed to parse native daemon return code for " + ifaceIn + " " + ifaceOut);
   1329         }
   1330         if (code != NetdResponseCode.TetheringStatsResult) {
   1331             throw new IllegalStateException(
   1332                     "Unexpected return code from native daemon for " + ifaceIn + " " + ifaceOut);
   1333         }
   1334 
   1335         try {
   1336             final NetworkStats.Entry entry = new NetworkStats.Entry();
   1337             entry.iface = ifaceIn;
   1338             entry.uid = UID_TETHERING;
   1339             entry.set = SET_DEFAULT;
   1340             entry.tag = TAG_NONE;
   1341             entry.rxBytes = Long.parseLong(tok[3]);
   1342             entry.rxPackets = Long.parseLong(tok[4]);
   1343             entry.txBytes = Long.parseLong(tok[5]);
   1344             entry.txPackets = Long.parseLong(tok[6]);
   1345             return entry;
   1346         } catch (NumberFormatException e) {
   1347             throw new IllegalStateException(
   1348                     "problem parsing tethering stats for " + ifaceIn + " " + ifaceOut + ": " + e);
   1349         }
   1350     }
   1351 
   1352     public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) {
   1353         mContext.enforceCallingOrSelfPermission(
   1354                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
   1355         try {
   1356             mConnector.doCommand(String.format(
   1357                     "interface setthrottle %s %d %d", iface, rxKbps, txKbps));
   1358         } catch (NativeDaemonConnectorException e) {
   1359             Slog.e(TAG, "Error communicating with native daemon to set throttle", e);
   1360         }
   1361     }
   1362 
   1363     private int getInterfaceThrottle(String iface, boolean rx) {
   1364         mContext.enforceCallingOrSelfPermission(
   1365                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
   1366         try {
   1367             String rsp;
   1368             try {
   1369                 rsp = mConnector.doCommand(
   1370                         String.format("interface getthrottle %s %s", iface,
   1371                                 (rx ? "rx" : "tx"))).get(0);
   1372             } catch (NativeDaemonConnectorException e) {
   1373                 Slog.e(TAG, "Error communicating with native daemon to getthrottle", e);
   1374                 return -1;
   1375             }
   1376 
   1377             String[] tok = rsp.split(" ");
   1378             if (tok.length < 2) {
   1379                 Slog.e(TAG, "Malformed response to getthrottle command");
   1380                 return -1;
   1381             }
   1382 
   1383             int code;
   1384             try {
   1385                 code = Integer.parseInt(tok[0]);
   1386             } catch (NumberFormatException nfe) {
   1387                 Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
   1388                 return -1;
   1389             }
   1390             if ((rx && code != NetdResponseCode.InterfaceRxThrottleResult) || (
   1391                     !rx && code != NetdResponseCode.InterfaceTxThrottleResult)) {
   1392                 Slog.e(TAG, String.format("Unexpected response code %d", code));
   1393                 return -1;
   1394             }
   1395             return Integer.parseInt(tok[1]);
   1396         } catch (Exception e) {
   1397             Slog.e(TAG, String.format(
   1398                     "Failed to read interface %s throttle value", (rx ? "rx" : "tx")), e);
   1399         }
   1400         return -1;
   1401     }
   1402 
   1403     public int getInterfaceRxThrottle(String iface) {
   1404         return getInterfaceThrottle(iface, true);
   1405     }
   1406 
   1407     public int getInterfaceTxThrottle(String iface) {
   1408         return getInterfaceThrottle(iface, false);
   1409     }
   1410 
   1411     public void setDefaultInterfaceForDns(String iface) throws IllegalStateException {
   1412         mContext.enforceCallingOrSelfPermission(
   1413                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
   1414         try {
   1415             String cmd = "resolver setdefaultif " + iface;
   1416 
   1417             mConnector.doCommand(cmd);
   1418         } catch (NativeDaemonConnectorException e) {
   1419             throw new IllegalStateException(
   1420                     "Error communicating with native daemon to set default interface", e);
   1421         }
   1422     }
   1423 
   1424     public void setDnsServersForInterface(String iface, String[] servers)
   1425             throws IllegalStateException {
   1426         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE,
   1427                 "NetworkManagementService");
   1428         try {
   1429             String cmd = "resolver setifdns " + iface;
   1430             for (String s : servers) {
   1431                 InetAddress a = NetworkUtils.numericToInetAddress(s);
   1432                 if (a.isAnyLocalAddress() == false) {
   1433                     cmd += " " + a.getHostAddress();
   1434                 }
   1435             }
   1436             mConnector.doCommand(cmd);
   1437         } catch (IllegalArgumentException e) {
   1438             throw new IllegalStateException("Error setting dnsn for interface", e);
   1439         } catch (NativeDaemonConnectorException e) {
   1440             throw new IllegalStateException(
   1441                     "Error communicating with native daemon to set dns for interface", e);
   1442         }
   1443     }
   1444 
   1445     public void flushDefaultDnsCache() throws IllegalStateException {
   1446         mContext.enforceCallingOrSelfPermission(
   1447                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
   1448         try {
   1449             String cmd = "resolver flushdefaultif";
   1450 
   1451             mConnector.doCommand(cmd);
   1452         } catch (NativeDaemonConnectorException e) {
   1453             throw new IllegalStateException(
   1454                     "Error communicating with native deamon to flush default interface", e);
   1455         }
   1456     }
   1457 
   1458     public void flushInterfaceDnsCache(String iface) throws IllegalStateException {
   1459         mContext.enforceCallingOrSelfPermission(
   1460                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
   1461         try {
   1462             String cmd = "resolver flushif " + iface;
   1463 
   1464             mConnector.doCommand(cmd);
   1465         } catch (NativeDaemonConnectorException e) {
   1466             throw new IllegalStateException(
   1467                     "Error communicating with native daemon to flush interface " + iface, e);
   1468         }
   1469     }
   1470 
   1471     /** {@inheritDoc} */
   1472     public void monitor() {
   1473         if (mConnector != null) {
   1474             mConnector.monitor();
   1475         }
   1476     }
   1477 
   1478     @Override
   1479     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   1480         mContext.enforceCallingOrSelfPermission(DUMP, TAG);
   1481 
   1482         pw.print("Bandwidth control enabled: "); pw.println(mBandwidthControlEnabled);
   1483 
   1484         synchronized (mQuotaLock) {
   1485             pw.print("Active quota ifaces: "); pw.println(mActiveQuotaIfaces.toString());
   1486             pw.print("Active alert ifaces: "); pw.println(mActiveAlertIfaces.toString());
   1487         }
   1488 
   1489         synchronized (mUidRejectOnQuota) {
   1490             pw.print("UID reject on quota ifaces: [");
   1491             final int size = mUidRejectOnQuota.size();
   1492             for (int i = 0; i < size; i++) {
   1493                 pw.print(mUidRejectOnQuota.keyAt(i));
   1494                 if (i < size - 1) pw.print(",");
   1495             }
   1496             pw.println("]");
   1497         }
   1498     }
   1499 }
   1500