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