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