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