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