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