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