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