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