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