NetworkManagementService.java revision 6143f5f7392fb0d3e5702a46a2415bd0ecb6efb4
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.net.NetworkStats.IFACE_ALL;
20import static android.net.NetworkStats.TAG_NONE;
21import static android.net.NetworkStats.UID_ALL;
22import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
23
24import android.content.Context;
25import android.content.pm.PackageManager;
26import android.net.INetworkManagementEventObserver;
27import android.net.InterfaceConfiguration;
28import android.net.LinkAddress;
29import android.net.NetworkStats;
30import android.net.NetworkUtils;
31import android.net.RouteInfo;
32import android.net.wifi.WifiConfiguration;
33import android.net.wifi.WifiConfiguration.KeyMgmt;
34import android.os.Binder;
35import android.os.INetworkManagementService;
36import android.os.SystemClock;
37import android.os.SystemProperties;
38import android.util.Log;
39import android.util.Slog;
40
41import java.io.BufferedReader;
42import java.io.DataInputStream;
43import java.io.File;
44import java.io.FileInputStream;
45import java.io.FileReader;
46import java.io.IOException;
47import java.io.InputStreamReader;
48import java.net.Inet4Address;
49import java.net.InetAddress;
50import java.util.ArrayList;
51import java.util.NoSuchElementException;
52import java.util.StringTokenizer;
53import java.util.concurrent.CountDownLatch;
54
55import libcore.io.IoUtils;
56
57/**
58 * @hide
59 */
60class NetworkManagementService extends INetworkManagementService.Stub {
61    private static final String TAG = "NetworkManagementService";
62    private static final boolean DBG = false;
63    private static final String NETD_TAG = "NetdConnector";
64
65    private static final int ADD = 1;
66    private static final int REMOVE = 2;
67
68    @Deprecated
69    private static final File STATS_UIDSTAT = new File("/proc/uid_stat");
70    private static final File STATS_NETFILTER = new File("/proc/net/xt_qtaguid/stats");
71
72    class NetdResponseCode {
73        public static final int InterfaceListResult       = 110;
74        public static final int TetherInterfaceListResult = 111;
75        public static final int TetherDnsFwdTgtListResult = 112;
76        public static final int TtyListResult             = 113;
77
78        public static final int TetherStatusResult        = 210;
79        public static final int IpFwdStatusResult         = 211;
80        public static final int InterfaceGetCfgResult     = 213;
81        public static final int SoftapStatusResult        = 214;
82        public static final int InterfaceRxCounterResult  = 216;
83        public static final int InterfaceTxCounterResult  = 217;
84        public static final int InterfaceRxThrottleResult = 218;
85        public static final int InterfaceTxThrottleResult = 219;
86
87        public static final int InterfaceChange           = 600;
88    }
89
90    /**
91     * Binder context for this service
92     */
93    private Context mContext;
94
95    /**
96     * connector object for communicating with netd
97     */
98    private NativeDaemonConnector mConnector;
99
100    private Thread mThread;
101    private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
102
103    private ArrayList<INetworkManagementEventObserver> mObservers;
104
105    /**
106     * Constructs a new NetworkManagementService instance
107     *
108     * @param context  Binder context for this service
109     */
110    private NetworkManagementService(Context context) {
111        mContext = context;
112        mObservers = new ArrayList<INetworkManagementEventObserver>();
113
114        if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
115            return;
116        }
117
118        mConnector = new NativeDaemonConnector(
119                new NetdCallbackReceiver(), "netd", 10, NETD_TAG);
120        mThread = new Thread(mConnector, NETD_TAG);
121    }
122
123    public static NetworkManagementService create(Context context) throws InterruptedException {
124        NetworkManagementService service = new NetworkManagementService(context);
125        if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
126        service.mThread.start();
127        if (DBG) Slog.d(TAG, "Awaiting socket connection");
128        service.mConnectedSignal.await();
129        if (DBG) Slog.d(TAG, "Connected");
130        return service;
131    }
132
133    public void registerObserver(INetworkManagementEventObserver obs) {
134        Slog.d(TAG, "Registering observer");
135        mObservers.add(obs);
136    }
137
138    public void unregisterObserver(INetworkManagementEventObserver obs) {
139        Slog.d(TAG, "Unregistering observer");
140        mObservers.remove(mObservers.indexOf(obs));
141    }
142
143    /**
144     * Notify our observers of an interface status change
145     */
146    private void notifyInterfaceStatusChanged(String iface, boolean up) {
147        for (INetworkManagementEventObserver obs : mObservers) {
148            try {
149                obs.interfaceStatusChanged(iface, up);
150            } catch (Exception ex) {
151                Slog.w(TAG, "Observer notifier failed", ex);
152            }
153        }
154    }
155
156    /**
157     * Notify our observers of an interface link status change.
158     * (typically, an Ethernet cable has been plugged-in or unplugged).
159     */
160    private void notifyInterfaceLinkStateChanged(String iface, boolean up) {
161        for (INetworkManagementEventObserver obs : mObservers) {
162            try {
163                obs.interfaceLinkStateChanged(iface, up);
164            } catch (Exception ex) {
165                Slog.w(TAG, "Observer notifier failed", ex);
166            }
167        }
168    }
169
170    /**
171     * Notify our observers of an interface addition.
172     */
173    private void notifyInterfaceAdded(String iface) {
174        for (INetworkManagementEventObserver obs : mObservers) {
175            try {
176                obs.interfaceAdded(iface);
177            } catch (Exception ex) {
178                Slog.w(TAG, "Observer notifier failed", ex);
179            }
180        }
181    }
182
183    /**
184     * Notify our observers of an interface removal.
185     */
186    private void notifyInterfaceRemoved(String iface) {
187        for (INetworkManagementEventObserver obs : mObservers) {
188            try {
189                obs.interfaceRemoved(iface);
190            } catch (Exception ex) {
191                Slog.w(TAG, "Observer notifier failed", ex);
192            }
193        }
194    }
195
196    /**
197     * Let us know the daemon is connected
198     */
199    protected void onConnected() {
200        if (DBG) Slog.d(TAG, "onConnected");
201        mConnectedSignal.countDown();
202    }
203
204
205    //
206    // Netd Callback handling
207    //
208
209    class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {
210        public void onDaemonConnected() {
211            NetworkManagementService.this.onConnected();
212            new Thread() {
213                public void run() {
214                }
215            }.start();
216        }
217        public boolean onEvent(int code, String raw, String[] cooked) {
218            if (code == NetdResponseCode.InterfaceChange) {
219                /*
220                 * a network interface change occured
221                 * Format: "NNN Iface added <name>"
222                 *         "NNN Iface removed <name>"
223                 *         "NNN Iface changed <name> <up/down>"
224                 *         "NNN Iface linkstatus <name> <up/down>"
225                 */
226                if (cooked.length < 4 || !cooked[1].equals("Iface")) {
227                    throw new IllegalStateException(
228                            String.format("Invalid event from daemon (%s)", raw));
229                }
230                if (cooked[2].equals("added")) {
231                    notifyInterfaceAdded(cooked[3]);
232                    return true;
233                } else if (cooked[2].equals("removed")) {
234                    notifyInterfaceRemoved(cooked[3]);
235                    return true;
236                } else if (cooked[2].equals("changed") && cooked.length == 5) {
237                    notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up"));
238                    return true;
239                } else if (cooked[2].equals("linkstatus") && cooked.length == 5) {
240                    notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up"));
241                    return true;
242                }
243                throw new IllegalStateException(
244                        String.format("Invalid event from daemon (%s)", raw));
245            }
246            return false;
247        }
248    }
249
250
251    //
252    // INetworkManagementService members
253    //
254
255    public String[] listInterfaces() throws IllegalStateException {
256        mContext.enforceCallingOrSelfPermission(
257                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
258
259        try {
260            return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult);
261        } catch (NativeDaemonConnectorException e) {
262            throw new IllegalStateException(
263                    "Cannot communicate with native daemon to list interfaces");
264        }
265    }
266
267    public InterfaceConfiguration getInterfaceConfig(String iface) throws IllegalStateException {
268        String rsp;
269        try {
270            rsp = mConnector.doCommand("interface getcfg " + iface).get(0);
271        } catch (NativeDaemonConnectorException e) {
272            throw new IllegalStateException(
273                    "Cannot communicate with native daemon to get interface config");
274        }
275        Slog.d(TAG, String.format("rsp <%s>", rsp));
276
277        // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz [flag1 flag2 flag3]
278        StringTokenizer st = new StringTokenizer(rsp);
279
280        InterfaceConfiguration cfg;
281        try {
282            try {
283                int code = Integer.parseInt(st.nextToken(" "));
284                if (code != NetdResponseCode.InterfaceGetCfgResult) {
285                    throw new IllegalStateException(
286                        String.format("Expected code %d, but got %d",
287                                NetdResponseCode.InterfaceGetCfgResult, code));
288                }
289            } catch (NumberFormatException nfe) {
290                throw new IllegalStateException(
291                        String.format("Invalid response from daemon (%s)", rsp));
292            }
293
294            cfg = new InterfaceConfiguration();
295            cfg.hwAddr = st.nextToken(" ");
296            InetAddress addr = null;
297            int prefixLength = 0;
298            try {
299                addr = NetworkUtils.numericToInetAddress(st.nextToken(" "));
300            } catch (IllegalArgumentException iae) {
301                Slog.e(TAG, "Failed to parse ipaddr", iae);
302            }
303
304            try {
305                prefixLength = Integer.parseInt(st.nextToken(" "));
306            } catch (NumberFormatException nfe) {
307                Slog.e(TAG, "Failed to parse prefixLength", nfe);
308            }
309
310            cfg.addr = new LinkAddress(addr, prefixLength);
311            cfg.interfaceFlags = st.nextToken("]").trim() +"]";
312        } catch (NoSuchElementException nsee) {
313            throw new IllegalStateException(
314                    String.format("Invalid response from daemon (%s)", rsp));
315        }
316        Slog.d(TAG, String.format("flags <%s>", cfg.interfaceFlags));
317        return cfg;
318    }
319
320    public void setInterfaceConfig(
321            String iface, InterfaceConfiguration cfg) throws IllegalStateException {
322        LinkAddress linkAddr = cfg.addr;
323        if (linkAddr == null || linkAddr.getAddress() == null) {
324            throw new IllegalStateException("Null LinkAddress given");
325        }
326        String cmd = String.format("interface setcfg %s %s %d %s", iface,
327                linkAddr.getAddress().getHostAddress(),
328                linkAddr.getNetworkPrefixLength(),
329                cfg.interfaceFlags);
330        try {
331            mConnector.doCommand(cmd);
332        } catch (NativeDaemonConnectorException e) {
333            throw new IllegalStateException(
334                    "Unable to communicate with native daemon to interface setcfg - " + e);
335        }
336    }
337
338    /* TODO: This is right now a IPv4 only function. Works for wifi which loses its
339       IPv6 addresses on interface down, but we need to do full clean up here */
340    public void clearInterfaceAddresses(String iface) throws IllegalStateException {
341         String cmd = String.format("interface clearaddrs %s", iface);
342        try {
343            mConnector.doCommand(cmd);
344        } catch (NativeDaemonConnectorException e) {
345            throw new IllegalStateException(
346                    "Unable to communicate with native daemon to interface clearallips - " + e);
347        }
348    }
349
350    public void addRoute(String interfaceName, RouteInfo route) {
351        modifyRoute(interfaceName, ADD, route);
352    }
353
354    public void removeRoute(String interfaceName, RouteInfo route) {
355        modifyRoute(interfaceName, REMOVE, route);
356    }
357
358    private void modifyRoute(String interfaceName, int action, RouteInfo route) {
359        ArrayList<String> rsp;
360
361        StringBuilder cmd;
362
363        switch (action) {
364            case ADD:
365            {
366                cmd = new StringBuilder("interface route add " + interfaceName);
367                break;
368            }
369            case REMOVE:
370            {
371                cmd = new StringBuilder("interface route remove " + interfaceName);
372                break;
373            }
374            default:
375                throw new IllegalStateException("Unknown action type " + action);
376        }
377
378        // create triplet: dest-ip-addr prefixlength gateway-ip-addr
379        LinkAddress la = route.getDestination();
380        cmd.append(' ');
381        cmd.append(la.getAddress().getHostAddress());
382        cmd.append(' ');
383        cmd.append(la.getNetworkPrefixLength());
384        cmd.append(' ');
385        if (route.getGateway() == null) {
386            if (la.getAddress() instanceof Inet4Address) {
387                cmd.append("0.0.0.0");
388            } else {
389                cmd.append ("::0");
390            }
391        } else {
392            cmd.append(route.getGateway().getHostAddress());
393        }
394        try {
395            rsp = mConnector.doCommand(cmd.toString());
396        } catch (NativeDaemonConnectorException e) {
397            throw new IllegalStateException(
398                    "Unable to communicate with native dameon to add routes - "
399                    + e);
400        }
401
402        for (String line : rsp) {
403            Log.v(TAG, "add route response is " + line);
404        }
405    }
406
407    private ArrayList<String> readRouteList(String filename) {
408        FileInputStream fstream = null;
409        ArrayList<String> list = new ArrayList<String>();
410
411        try {
412            fstream = new FileInputStream(filename);
413            DataInputStream in = new DataInputStream(fstream);
414            BufferedReader br = new BufferedReader(new InputStreamReader(in));
415            String s;
416
417            // throw away the title line
418
419            while (((s = br.readLine()) != null) && (s.length() != 0)) {
420                list.add(s);
421            }
422        } catch (IOException ex) {
423            // return current list, possibly empty
424        } finally {
425            if (fstream != null) {
426                try {
427                    fstream.close();
428                } catch (IOException ex) {}
429            }
430        }
431
432        return list;
433    }
434
435    public RouteInfo[] getRoutes(String interfaceName) {
436        ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>();
437
438        // v4 routes listed as:
439        // iface dest-addr gateway-addr flags refcnt use metric netmask mtu window IRTT
440        for (String s : readRouteList("/proc/net/route")) {
441            String[] fields = s.split("\t");
442
443            if (fields.length > 7) {
444                String iface = fields[0];
445
446                if (interfaceName.equals(iface)) {
447                    String dest = fields[1];
448                    String gate = fields[2];
449                    String flags = fields[3]; // future use?
450                    String mask = fields[7];
451                    try {
452                        // address stored as a hex string, ex: 0014A8C0
453                        InetAddress destAddr =
454                                NetworkUtils.intToInetAddress((int)Long.parseLong(dest, 16));
455                        int prefixLength =
456                                NetworkUtils.netmaskIntToPrefixLength(
457                                (int)Long.parseLong(mask, 16));
458                        LinkAddress linkAddress = new LinkAddress(destAddr, prefixLength);
459
460                        // address stored as a hex string, ex 0014A8C0
461                        InetAddress gatewayAddr =
462                                NetworkUtils.intToInetAddress((int)Long.parseLong(gate, 16));
463
464                        RouteInfo route = new RouteInfo(linkAddress, gatewayAddr);
465                        routes.add(route);
466                    } catch (Exception e) {
467                        Log.e(TAG, "Error parsing route " + s + " : " + e);
468                        continue;
469                    }
470                }
471            }
472        }
473
474        // v6 routes listed as:
475        // dest-addr prefixlength ?? ?? gateway-addr ?? ?? ?? ?? iface
476        for (String s : readRouteList("/proc/net/ipv6_route")) {
477            String[]fields = s.split("\\s+");
478            if (fields.length > 9) {
479                String iface = fields[9].trim();
480                if (interfaceName.equals(iface)) {
481                    String dest = fields[0];
482                    String prefix = fields[1];
483                    String gate = fields[4];
484
485                    try {
486                        // prefix length stored as a hex string, ex 40
487                        int prefixLength = Integer.parseInt(prefix, 16);
488
489                        // address stored as a 32 char hex string
490                        // ex fe800000000000000000000000000000
491                        InetAddress destAddr = NetworkUtils.hexToInet6Address(dest);
492                        LinkAddress linkAddress = new LinkAddress(destAddr, prefixLength);
493
494                        InetAddress gateAddr = NetworkUtils.hexToInet6Address(gate);
495
496                        RouteInfo route = new RouteInfo(linkAddress, gateAddr);
497                        routes.add(route);
498                    } catch (Exception e) {
499                        Log.e(TAG, "Error parsing route " + s + " : " + e);
500                        continue;
501                    }
502                }
503            }
504        }
505        return (RouteInfo[]) routes.toArray(new RouteInfo[0]);
506    }
507
508    public void shutdown() {
509        if (mContext.checkCallingOrSelfPermission(
510                android.Manifest.permission.SHUTDOWN)
511                != PackageManager.PERMISSION_GRANTED) {
512            throw new SecurityException("Requires SHUTDOWN permission");
513        }
514
515        Slog.d(TAG, "Shutting down");
516    }
517
518    public boolean getIpForwardingEnabled() throws IllegalStateException{
519        mContext.enforceCallingOrSelfPermission(
520                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
521
522        ArrayList<String> rsp;
523        try {
524            rsp = mConnector.doCommand("ipfwd status");
525        } catch (NativeDaemonConnectorException e) {
526            throw new IllegalStateException(
527                    "Unable to communicate with native daemon to ipfwd status");
528        }
529
530        for (String line : rsp) {
531            String[] tok = line.split(" ");
532            if (tok.length < 3) {
533                Slog.e(TAG, "Malformed response from native daemon: " + line);
534                return false;
535            }
536
537            int code = Integer.parseInt(tok[0]);
538            if (code == NetdResponseCode.IpFwdStatusResult) {
539                // 211 Forwarding <enabled/disabled>
540                return "enabled".equals(tok[2]);
541            } else {
542                throw new IllegalStateException(String.format("Unexpected response code %d", code));
543            }
544        }
545        throw new IllegalStateException("Got an empty response");
546    }
547
548    public void setIpForwardingEnabled(boolean enable) throws IllegalStateException {
549        mContext.enforceCallingOrSelfPermission(
550                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
551        mConnector.doCommand(String.format("ipfwd %sable", (enable ? "en" : "dis")));
552    }
553
554    public void startTethering(String[] dhcpRange)
555             throws IllegalStateException {
556        mContext.enforceCallingOrSelfPermission(
557                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
558        // cmd is "tether start first_start first_stop second_start second_stop ..."
559        // an odd number of addrs will fail
560        String cmd = "tether start";
561        for (String d : dhcpRange) {
562            cmd += " " + d;
563        }
564
565        try {
566            mConnector.doCommand(cmd);
567        } catch (NativeDaemonConnectorException e) {
568            throw new IllegalStateException("Unable to communicate to native daemon");
569        }
570    }
571
572    public void stopTethering() throws IllegalStateException {
573        mContext.enforceCallingOrSelfPermission(
574                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
575        try {
576            mConnector.doCommand("tether stop");
577        } catch (NativeDaemonConnectorException e) {
578            throw new IllegalStateException("Unable to communicate to native daemon to stop tether");
579        }
580    }
581
582    public boolean isTetheringStarted() throws IllegalStateException {
583        mContext.enforceCallingOrSelfPermission(
584                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
585
586        ArrayList<String> rsp;
587        try {
588            rsp = mConnector.doCommand("tether status");
589        } catch (NativeDaemonConnectorException e) {
590            throw new IllegalStateException(
591                    "Unable to communicate to native daemon to get tether status");
592        }
593
594        for (String line : rsp) {
595            String[] tok = line.split(" ");
596            if (tok.length < 3) {
597                throw new IllegalStateException("Malformed response for tether status: " + line);
598            }
599            int code = Integer.parseInt(tok[0]);
600            if (code == NetdResponseCode.TetherStatusResult) {
601                // XXX: Tethering services <started/stopped> <TBD>...
602                return "started".equals(tok[2]);
603            } else {
604                throw new IllegalStateException(String.format("Unexpected response code %d", code));
605            }
606        }
607        throw new IllegalStateException("Got an empty response");
608    }
609
610    public void tetherInterface(String iface) throws IllegalStateException {
611        mContext.enforceCallingOrSelfPermission(
612                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
613        try {
614            mConnector.doCommand("tether interface add " + iface);
615        } catch (NativeDaemonConnectorException e) {
616            throw new IllegalStateException(
617                    "Unable to communicate to native daemon for adding tether interface");
618        }
619    }
620
621    public void untetherInterface(String iface) {
622        mContext.enforceCallingOrSelfPermission(
623                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
624        try {
625            mConnector.doCommand("tether interface remove " + iface);
626        } catch (NativeDaemonConnectorException e) {
627            throw new IllegalStateException(
628                    "Unable to communicate to native daemon for removing tether interface");
629        }
630    }
631
632    public String[] listTetheredInterfaces() throws IllegalStateException {
633        mContext.enforceCallingOrSelfPermission(
634                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
635        try {
636            return mConnector.doListCommand(
637                    "tether interface list", NetdResponseCode.TetherInterfaceListResult);
638        } catch (NativeDaemonConnectorException e) {
639            throw new IllegalStateException(
640                    "Unable to communicate to native daemon for listing tether interfaces");
641        }
642    }
643
644    public void setDnsForwarders(String[] dns) throws IllegalStateException {
645        mContext.enforceCallingOrSelfPermission(
646                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
647        try {
648            String cmd = "tether dns set";
649            for (String s : dns) {
650                cmd += " " + NetworkUtils.numericToInetAddress(s).getHostAddress();
651            }
652            try {
653                mConnector.doCommand(cmd);
654            } catch (NativeDaemonConnectorException e) {
655                throw new IllegalStateException(
656                        "Unable to communicate to native daemon for setting tether dns");
657            }
658        } catch (IllegalArgumentException e) {
659            throw new IllegalStateException("Error resolving dns name", e);
660        }
661    }
662
663    public String[] getDnsForwarders() throws IllegalStateException {
664        mContext.enforceCallingOrSelfPermission(
665                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
666        try {
667            return mConnector.doListCommand(
668                    "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult);
669        } catch (NativeDaemonConnectorException e) {
670            throw new IllegalStateException(
671                    "Unable to communicate to native daemon for listing tether dns");
672        }
673    }
674
675    public void enableNat(String internalInterface, String externalInterface)
676            throws IllegalStateException {
677        mContext.enforceCallingOrSelfPermission(
678                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
679        try {
680            mConnector.doCommand(
681                    String.format("nat enable %s %s", internalInterface, externalInterface));
682        } catch (NativeDaemonConnectorException e) {
683            throw new IllegalStateException(
684                    "Unable to communicate to native daemon for enabling NAT interface");
685        }
686    }
687
688    public void disableNat(String internalInterface, String externalInterface)
689            throws IllegalStateException {
690        mContext.enforceCallingOrSelfPermission(
691                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
692        try {
693            mConnector.doCommand(
694                    String.format("nat disable %s %s", internalInterface, externalInterface));
695        } catch (NativeDaemonConnectorException e) {
696            throw new IllegalStateException(
697                    "Unable to communicate to native daemon for disabling NAT interface");
698        }
699    }
700
701    public String[] listTtys() throws IllegalStateException {
702        mContext.enforceCallingOrSelfPermission(
703                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
704        try {
705            return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult);
706        } catch (NativeDaemonConnectorException e) {
707            throw new IllegalStateException(
708                    "Unable to communicate to native daemon for listing TTYs");
709        }
710    }
711
712    public void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr,
713            String dns2Addr) throws IllegalStateException {
714        try {
715            mContext.enforceCallingOrSelfPermission(
716                    android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
717            mConnector.doCommand(String.format("pppd attach %s %s %s %s %s", tty,
718                    NetworkUtils.numericToInetAddress(localAddr).getHostAddress(),
719                    NetworkUtils.numericToInetAddress(remoteAddr).getHostAddress(),
720                    NetworkUtils.numericToInetAddress(dns1Addr).getHostAddress(),
721                    NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress()));
722        } catch (IllegalArgumentException e) {
723            throw new IllegalStateException("Error resolving addr", e);
724        } catch (NativeDaemonConnectorException e) {
725            throw new IllegalStateException("Error communicating to native daemon to attach pppd", e);
726        }
727    }
728
729    public void detachPppd(String tty) throws IllegalStateException {
730        mContext.enforceCallingOrSelfPermission(
731                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
732        try {
733            mConnector.doCommand(String.format("pppd detach %s", tty));
734        } catch (NativeDaemonConnectorException e) {
735            throw new IllegalStateException("Error communicating to native daemon to detach pppd", e);
736        }
737    }
738
739    public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface)
740             throws IllegalStateException {
741        mContext.enforceCallingOrSelfPermission(
742                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
743        mContext.enforceCallingOrSelfPermission(
744                android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
745        try {
746            mConnector.doCommand(String.format("softap stop " + wlanIface));
747            mConnector.doCommand(String.format("softap fwreload " + wlanIface + " AP"));
748            mConnector.doCommand(String.format("softap start " + wlanIface));
749            if (wifiConfig == null) {
750                mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface));
751            } else {
752                /**
753                 * softap set arg1 arg2 arg3 [arg4 arg5 arg6 arg7 arg8]
754                 * argv1 - wlan interface
755                 * argv2 - softap interface
756                 * argv3 - SSID
757                 * argv4 - Security
758                 * argv5 - Key
759                 * argv6 - Channel
760                 * argv7 - Preamble
761                 * argv8 - Max SCB
762                 */
763                 String str = String.format("softap set " + wlanIface + " " + softapIface +
764                                       " %s %s %s", convertQuotedString(wifiConfig.SSID),
765                                       getSecurityType(wifiConfig),
766                                       convertQuotedString(wifiConfig.preSharedKey));
767                mConnector.doCommand(str);
768            }
769            mConnector.doCommand(String.format("softap startap"));
770        } catch (NativeDaemonConnectorException e) {
771            throw new IllegalStateException("Error communicating to native daemon to start softap", e);
772        }
773    }
774
775    private String convertQuotedString(String s) {
776        if (s == null) {
777            return s;
778        }
779        /* Replace \ with \\, then " with \" and add quotes at end */
780        return '"' + s.replaceAll("\\\\","\\\\\\\\").replaceAll("\"","\\\\\"") + '"';
781    }
782
783    private String getSecurityType(WifiConfiguration wifiConfig) {
784        switch (wifiConfig.getAuthType()) {
785            case KeyMgmt.WPA_PSK:
786                return "wpa-psk";
787            case KeyMgmt.WPA2_PSK:
788                return "wpa2-psk";
789            default:
790                return "open";
791        }
792    }
793
794    public void stopAccessPoint() throws IllegalStateException {
795        mContext.enforceCallingOrSelfPermission(
796                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
797        mContext.enforceCallingOrSelfPermission(
798                android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
799        try {
800            mConnector.doCommand("softap stopap");
801        } catch (NativeDaemonConnectorException e) {
802            throw new IllegalStateException("Error communicating to native daemon to stop soft AP",
803                    e);
804        }
805    }
806
807    public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface)
808            throws IllegalStateException {
809        mContext.enforceCallingOrSelfPermission(
810                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
811        mContext.enforceCallingOrSelfPermission(
812            android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService");
813        try {
814            if (wifiConfig == null) {
815                mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface));
816            } else {
817                String str = String.format("softap set " + wlanIface + " " + softapIface
818                        + " %s %s %s", convertQuotedString(wifiConfig.SSID),
819                        getSecurityType(wifiConfig),
820                        convertQuotedString(wifiConfig.preSharedKey));
821                mConnector.doCommand(str);
822            }
823        } catch (NativeDaemonConnectorException e) {
824            throw new IllegalStateException("Error communicating to native daemon to set soft AP",
825                    e);
826        }
827    }
828
829    private long getInterfaceCounter(String iface, boolean rx) {
830        mContext.enforceCallingOrSelfPermission(
831                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
832        try {
833            String rsp;
834            try {
835                rsp = mConnector.doCommand(
836                        String.format("interface read%scounter %s", (rx ? "rx" : "tx"), iface)).get(0);
837            } catch (NativeDaemonConnectorException e1) {
838                Slog.e(TAG, "Error communicating with native daemon", e1);
839                return -1;
840            }
841
842            String[] tok = rsp.split(" ");
843            if (tok.length < 2) {
844                Slog.e(TAG, String.format("Malformed response for reading %s interface",
845                        (rx ? "rx" : "tx")));
846                return -1;
847            }
848
849            int code;
850            try {
851                code = Integer.parseInt(tok[0]);
852            } catch (NumberFormatException nfe) {
853                Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
854                return -1;
855            }
856            if ((rx && code != NetdResponseCode.InterfaceRxCounterResult) || (
857                    !rx && code != NetdResponseCode.InterfaceTxCounterResult)) {
858                Slog.e(TAG, String.format("Unexpected response code %d", code));
859                return -1;
860            }
861            return Long.parseLong(tok[1]);
862        } catch (Exception e) {
863            Slog.e(TAG, String.format(
864                    "Failed to read interface %s counters", (rx ? "rx" : "tx")), e);
865        }
866        return -1;
867    }
868
869    @Override
870    public NetworkStats getNetworkStatsSummary() {
871        mContext.enforceCallingOrSelfPermission(
872                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
873
874        final String[] ifaces = listInterfaces();
875        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), ifaces.length);
876
877        for (String iface : ifaces) {
878            final long rx = getInterfaceCounter(iface, true);
879            final long tx = getInterfaceCounter(iface, false);
880            stats.addEntry(iface, UID_ALL, TAG_NONE, rx, tx);
881        }
882
883        return stats;
884    }
885
886    @Override
887    public NetworkStats getNetworkStatsDetail() {
888        mContext.enforceCallingOrSelfPermission(
889                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
890
891        if (STATS_NETFILTER.exists()) {
892            return getNetworkStatsDetailNetfilter(UID_ALL);
893        } else {
894            return getNetworkStatsDetailUidstat(UID_ALL);
895        }
896    }
897
898    @Override
899    public NetworkStats getNetworkStatsUidDetail(int uid) {
900        if (Binder.getCallingUid() != uid) {
901            mContext.enforceCallingOrSelfPermission(
902                    android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
903        }
904
905        if (STATS_NETFILTER.exists()) {
906            return getNetworkStatsDetailNetfilter(uid);
907        } else {
908            return getNetworkStatsDetailUidstat(uid);
909        }
910    }
911
912    /**
913     * Build {@link NetworkStats} with detailed UID statistics.
914     */
915    private NetworkStats getNetworkStatsDetailNetfilter(int limitUid) {
916        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
917
918        BufferedReader reader = null;
919        try {
920            reader = new BufferedReader(new FileReader(STATS_NETFILTER));
921
922            // assumes format from kernel:
923            // idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
924
925            // skip first line, which is legend
926            String line = reader.readLine();
927            while ((line = reader.readLine()) != null) {
928                final StringTokenizer t = new StringTokenizer(line);
929
930                final String idx = t.nextToken();
931                final String iface = t.nextToken();
932
933                try {
934                    // TODO: kernel currently emits tag in upper half of long;
935                    // eventually switch to directly using int.
936                    final int tag = (int) (Long.parseLong(t.nextToken().substring(2), 16) >> 32);
937                    final int uid = Integer.parseInt(t.nextToken());
938                    final long rx = Long.parseLong(t.nextToken());
939                    final long tx = Long.parseLong(t.nextToken());
940
941                    if (limitUid == UID_ALL || limitUid == uid) {
942                        stats.addEntry(iface, uid, tag, rx, tx);
943                    }
944                } catch (NumberFormatException e) {
945                    Slog.w(TAG, "problem parsing stats for idx " + idx + ": " + e);
946                }
947            }
948        } catch (IOException e) {
949            Slog.w(TAG, "problem parsing stats: " + e);
950        } finally {
951            IoUtils.closeQuietly(reader);
952        }
953
954        return stats;
955    }
956
957    /**
958     * Build {@link NetworkStats} with detailed UID statistics.
959     *
960     * @deprecated since this uses older "uid_stat" data, and doesn't provide
961     *             tag-level granularity or additional variables.
962     */
963    @Deprecated
964    private NetworkStats getNetworkStatsDetailUidstat(int limitUid) {
965        final String[] knownUids;
966        if (limitUid == UID_ALL) {
967            knownUids = STATS_UIDSTAT.list();
968        } else {
969            knownUids = new String[] { String.valueOf(limitUid) };
970        }
971
972        final NetworkStats stats = new NetworkStats(
973                SystemClock.elapsedRealtime(), knownUids.length);
974        for (String uid : knownUids) {
975            final int uidInt = Integer.parseInt(uid);
976            final File uidPath = new File(STATS_UIDSTAT, uid);
977            final long rx = readSingleLongFromFile(new File(uidPath, "tcp_rcv"));
978            final long tx = readSingleLongFromFile(new File(uidPath, "tcp_snd"));
979            stats.addEntry(IFACE_ALL, uidInt, TAG_NONE, rx, tx);
980        }
981
982        return stats;
983    }
984
985    public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) {
986        mContext.enforceCallingOrSelfPermission(
987                android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
988        try {
989            mConnector.doCommand(String.format(
990                    "interface setthrottle %s %d %d", iface, rxKbps, txKbps));
991        } catch (NativeDaemonConnectorException e) {
992            Slog.e(TAG, "Error communicating with native daemon to set throttle", e);
993        }
994    }
995
996    private int getInterfaceThrottle(String iface, boolean rx) {
997        mContext.enforceCallingOrSelfPermission(
998                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
999        try {
1000            String rsp;
1001            try {
1002                rsp = mConnector.doCommand(
1003                        String.format("interface getthrottle %s %s", iface,
1004                                (rx ? "rx" : "tx"))).get(0);
1005            } catch (NativeDaemonConnectorException e) {
1006                Slog.e(TAG, "Error communicating with native daemon to getthrottle", e);
1007                return -1;
1008            }
1009
1010            String[] tok = rsp.split(" ");
1011            if (tok.length < 2) {
1012                Slog.e(TAG, "Malformed response to getthrottle command");
1013                return -1;
1014            }
1015
1016            int code;
1017            try {
1018                code = Integer.parseInt(tok[0]);
1019            } catch (NumberFormatException nfe) {
1020                Slog.e(TAG, String.format("Error parsing code %s", tok[0]));
1021                return -1;
1022            }
1023            if ((rx && code != NetdResponseCode.InterfaceRxThrottleResult) || (
1024                    !rx && code != NetdResponseCode.InterfaceTxThrottleResult)) {
1025                Slog.e(TAG, String.format("Unexpected response code %d", code));
1026                return -1;
1027            }
1028            return Integer.parseInt(tok[1]);
1029        } catch (Exception e) {
1030            Slog.e(TAG, String.format(
1031                    "Failed to read interface %s throttle value", (rx ? "rx" : "tx")), e);
1032        }
1033        return -1;
1034    }
1035
1036    public int getInterfaceRxThrottle(String iface) {
1037        return getInterfaceThrottle(iface, true);
1038    }
1039
1040    public int getInterfaceTxThrottle(String iface) {
1041        return getInterfaceThrottle(iface, false);
1042    }
1043
1044    @Override
1045    public void setBandwidthControlEnabled(boolean enabled) {
1046        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
1047        mConnector.doCommand(String.format("bandwidth %s", (enabled ? "enable" : "disable")));
1048    }
1049
1050    /**
1051     * Utility method to read a single plain-text {@link Long} from the given
1052     * {@link File}, usually from a {@code /proc/} filesystem.
1053     */
1054    private static long readSingleLongFromFile(File file) {
1055        try {
1056            final byte[] buffer = IoUtils.readFileAsByteArray(file.toString());
1057            return Long.parseLong(new String(buffer).trim());
1058        } catch (NumberFormatException e) {
1059            return -1;
1060        } catch (IOException e) {
1061            return -1;
1062        }
1063    }
1064}
1065