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