Vpn.java revision 53c04bdd35a85aa65d1a1f18ca2ee34970e2c2d0
1/*
2 * Copyright (C) 2011 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.connectivity;
18
19import static android.Manifest.permission.BIND_VPN_SERVICE;
20
21import android.app.Notification;
22import android.app.NotificationManager;
23import android.app.PendingIntent;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.ServiceConnection;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.graphics.Bitmap;
32import android.graphics.Canvas;
33import android.graphics.drawable.Drawable;
34import android.net.BaseNetworkStateTracker;
35import android.net.ConnectivityManager;
36import android.net.INetworkManagementEventObserver;
37import android.net.LinkProperties;
38import android.net.LocalSocket;
39import android.net.LocalSocketAddress;
40import android.net.NetworkInfo;
41import android.net.RouteInfo;
42import android.net.NetworkInfo.DetailedState;
43import android.os.Binder;
44import android.os.FileUtils;
45import android.os.IBinder;
46import android.os.INetworkManagementService;
47import android.os.Parcel;
48import android.os.ParcelFileDescriptor;
49import android.os.Process;
50import android.os.RemoteException;
51import android.os.SystemClock;
52import android.os.SystemService;
53import android.os.UserHandle;
54import android.security.Credentials;
55import android.security.KeyStore;
56import android.util.Log;
57import android.widget.Toast;
58
59import com.android.internal.R;
60import com.android.internal.net.LegacyVpnInfo;
61import com.android.internal.net.VpnConfig;
62import com.android.internal.net.VpnProfile;
63import com.android.internal.util.Preconditions;
64import com.android.server.ConnectivityService.VpnCallback;
65import com.android.server.net.BaseNetworkObserver;
66
67import java.io.File;
68import java.io.InputStream;
69import java.io.OutputStream;
70import java.net.Inet4Address;
71import java.net.InetAddress;
72import java.nio.charset.Charsets;
73import java.util.Arrays;
74
75import libcore.io.IoUtils;
76
77/**
78 * @hide
79 */
80public class Vpn extends BaseNetworkStateTracker {
81    private static final String TAG = "Vpn";
82    private static final boolean LOGD = true;
83
84    // TODO: create separate trackers for each unique VPN to support
85    // automated reconnection
86
87    private final VpnCallback mCallback;
88
89    private String mPackage = VpnConfig.LEGACY_VPN;
90    private String mInterface;
91    private Connection mConnection;
92    private LegacyVpnRunner mLegacyVpnRunner;
93    private PendingIntent mStatusIntent;
94    private boolean mEnableNotif = true;
95
96    public Vpn(Context context, VpnCallback callback, INetworkManagementService netService) {
97        // TODO: create dedicated TYPE_VPN network type
98        super(ConnectivityManager.TYPE_DUMMY);
99        mContext = context;
100        mCallback = callback;
101
102        try {
103            netService.registerObserver(mObserver);
104        } catch (RemoteException e) {
105            Log.wtf(TAG, "Problem registering observer", e);
106        }
107    }
108
109    public void setEnableNotifications(boolean enableNotif) {
110        mEnableNotif = enableNotif;
111    }
112
113    @Override
114    protected void startMonitoringInternal() {
115        // Ignored; events are sent through callbacks for now
116    }
117
118    @Override
119    public boolean teardown() {
120        // TODO: finish migration to unique tracker for each VPN
121        throw new UnsupportedOperationException();
122    }
123
124    @Override
125    public boolean reconnect() {
126        // TODO: finish migration to unique tracker for each VPN
127        throw new UnsupportedOperationException();
128    }
129
130    @Override
131    public String getTcpBufferSizesPropName() {
132        return PROP_TCP_BUFFER_UNKNOWN;
133    }
134
135    /**
136     * Update current state, dispaching event to listeners.
137     */
138    private void updateState(DetailedState detailedState, String reason) {
139        if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
140        mNetworkInfo.setDetailedState(detailedState, reason, null);
141        mCallback.onStateChanged(new NetworkInfo(mNetworkInfo));
142    }
143
144    /**
145     * Prepare for a VPN application. This method is designed to solve
146     * race conditions. It first compares the current prepared package
147     * with {@code oldPackage}. If they are the same, the prepared
148     * package is revoked and replaced with {@code newPackage}. If
149     * {@code oldPackage} is {@code null}, the comparison is omitted.
150     * If {@code newPackage} is the same package or {@code null}, the
151     * revocation is omitted. This method returns {@code true} if the
152     * operation is succeeded.
153     *
154     * Legacy VPN is handled specially since it is not a real package.
155     * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
156     * it can be revoked by itself.
157     *
158     * @param oldPackage The package name of the old VPN application.
159     * @param newPackage The package name of the new VPN application.
160     * @return true if the operation is succeeded.
161     */
162    public synchronized boolean prepare(String oldPackage, String newPackage) {
163        // Return false if the package does not match.
164        if (oldPackage != null && !oldPackage.equals(mPackage)) {
165            return false;
166        }
167
168        // Return true if we do not need to revoke.
169        if (newPackage == null ||
170                (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) {
171            return true;
172        }
173
174        // Check if the caller is authorized.
175        enforceControlPermission();
176
177        // Reset the interface and hide the notification.
178        if (mInterface != null) {
179            jniReset(mInterface);
180            final long token = Binder.clearCallingIdentity();
181            try {
182                mCallback.restore();
183                hideNotification();
184            } finally {
185                Binder.restoreCallingIdentity(token);
186            }
187            mInterface = null;
188        }
189
190        // Revoke the connection or stop LegacyVpnRunner.
191        if (mConnection != null) {
192            try {
193                mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
194                        Parcel.obtain(), null, IBinder.FLAG_ONEWAY);
195            } catch (Exception e) {
196                // ignore
197            }
198            mContext.unbindService(mConnection);
199            mConnection = null;
200        } else if (mLegacyVpnRunner != null) {
201            mLegacyVpnRunner.exit();
202            mLegacyVpnRunner = null;
203        }
204
205        Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
206        mPackage = newPackage;
207        updateState(DetailedState.IDLE, "prepare");
208        return true;
209    }
210
211    /**
212     * Protect a socket from routing changes by binding it to the given
213     * interface. The socket is NOT closed by this method.
214     *
215     * @param socket The socket to be bound.
216     * @param interfaze The name of the interface.
217     */
218    public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
219        PackageManager pm = mContext.getPackageManager();
220        ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
221        if (Binder.getCallingUid() != app.uid) {
222            throw new SecurityException("Unauthorized Caller");
223        }
224        jniProtect(socket.getFd(), interfaze);
225    }
226
227    /**
228     * Establish a VPN network and return the file descriptor of the VPN
229     * interface. This methods returns {@code null} if the application is
230     * revoked or not prepared.
231     *
232     * @param config The parameters to configure the network.
233     * @return The file descriptor of the VPN interface.
234     */
235    public synchronized ParcelFileDescriptor establish(VpnConfig config) {
236        // Check if the caller is already prepared.
237        PackageManager pm = mContext.getPackageManager();
238        ApplicationInfo app = null;
239        try {
240            app = pm.getApplicationInfo(mPackage, 0);
241        } catch (Exception e) {
242            return null;
243        }
244        if (Binder.getCallingUid() != app.uid) {
245            return null;
246        }
247
248        // Check if the service is properly declared.
249        Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
250        intent.setClassName(mPackage, config.user);
251        ResolveInfo info = pm.resolveService(intent, 0);
252        if (info == null) {
253            throw new SecurityException("Cannot find " + config.user);
254        }
255        if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
256            throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
257        }
258
259        // Load the label.
260        String label = app.loadLabel(pm).toString();
261
262        // Load the icon and convert it into a bitmap.
263        Drawable icon = app.loadIcon(pm);
264        Bitmap bitmap = null;
265        if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
266            int width = mContext.getResources().getDimensionPixelSize(
267                    android.R.dimen.notification_large_icon_width);
268            int height = mContext.getResources().getDimensionPixelSize(
269                    android.R.dimen.notification_large_icon_height);
270            icon.setBounds(0, 0, width, height);
271            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
272            Canvas c = new Canvas(bitmap);
273            icon.draw(c);
274            c.setBitmap(null);
275        }
276
277        // Configure the interface. Abort if any of these steps fails.
278        ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
279        try {
280            updateState(DetailedState.CONNECTING, "establish");
281            String interfaze = jniGetName(tun.getFd());
282            if (jniSetAddresses(interfaze, config.addresses) < 1) {
283                throw new IllegalArgumentException("At least one address must be specified");
284            }
285            if (config.routes != null) {
286                jniSetRoutes(interfaze, config.routes);
287            }
288            Connection connection = new Connection();
289            if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
290                throw new IllegalStateException("Cannot bind " + config.user);
291            }
292            if (mConnection != null) {
293                mContext.unbindService(mConnection);
294            }
295            if (mInterface != null && !mInterface.equals(interfaze)) {
296                jniReset(mInterface);
297            }
298            mConnection = connection;
299            mInterface = interfaze;
300        } catch (RuntimeException e) {
301            updateState(DetailedState.FAILED, "establish");
302            IoUtils.closeQuietly(tun);
303            throw e;
304        }
305        Log.i(TAG, "Established by " + config.user + " on " + mInterface);
306
307        // Fill more values.
308        config.user = mPackage;
309        config.interfaze = mInterface;
310
311        // Override DNS servers and show the notification.
312        final long token = Binder.clearCallingIdentity();
313        try {
314            mCallback.override(config.dnsServers, config.searchDomains);
315            showNotification(config, label, bitmap);
316        } finally {
317            Binder.restoreCallingIdentity(token);
318        }
319        // TODO: ensure that contract class eventually marks as connected
320        updateState(DetailedState.AUTHENTICATING, "establish");
321        return tun;
322    }
323
324    @Deprecated
325    public synchronized void interfaceStatusChanged(String iface, boolean up) {
326        try {
327            mObserver.interfaceStatusChanged(iface, up);
328        } catch (RemoteException e) {
329            // ignored; target is local
330        }
331    }
332
333    private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
334        @Override
335        public void interfaceStatusChanged(String interfaze, boolean up) {
336            synchronized (Vpn.this) {
337                if (!up && mLegacyVpnRunner != null) {
338                    mLegacyVpnRunner.check(interfaze);
339                }
340            }
341        }
342
343        @Override
344        public void interfaceRemoved(String interfaze) {
345            synchronized (Vpn.this) {
346                if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
347                    final long token = Binder.clearCallingIdentity();
348                    try {
349                        mCallback.restore();
350                        hideNotification();
351                    } finally {
352                        Binder.restoreCallingIdentity(token);
353                    }
354                    mInterface = null;
355                    if (mConnection != null) {
356                        mContext.unbindService(mConnection);
357                        mConnection = null;
358                        updateState(DetailedState.DISCONNECTED, "interfaceRemoved");
359                    } else if (mLegacyVpnRunner != null) {
360                        mLegacyVpnRunner.exit();
361                        mLegacyVpnRunner = null;
362                    }
363                }
364            }
365        }
366    };
367
368    private void enforceControlPermission() {
369        // System user is allowed to control VPN.
370        if (Binder.getCallingUid() == Process.SYSTEM_UID) {
371            return;
372        }
373
374        try {
375            // System dialogs are also allowed to control VPN.
376            PackageManager pm = mContext.getPackageManager();
377            ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0);
378            if (Binder.getCallingUid() == app.uid) {
379                return;
380            }
381        } catch (Exception e) {
382            // ignore
383        }
384
385        throw new SecurityException("Unauthorized Caller");
386    }
387
388    private class Connection implements ServiceConnection {
389        private IBinder mService;
390
391        @Override
392        public void onServiceConnected(ComponentName name, IBinder service) {
393            mService = service;
394        }
395
396        @Override
397        public void onServiceDisconnected(ComponentName name) {
398            mService = null;
399        }
400    }
401
402    private void showNotification(VpnConfig config, String label, Bitmap icon) {
403        if (!mEnableNotif) return;
404        mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config);
405
406        NotificationManager nm = (NotificationManager)
407                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
408
409        if (nm != null) {
410            String title = (label == null) ? mContext.getString(R.string.vpn_title) :
411                    mContext.getString(R.string.vpn_title_long, label);
412            String text = (config.session == null) ? mContext.getString(R.string.vpn_text) :
413                    mContext.getString(R.string.vpn_text_long, config.session);
414            config.startTime = SystemClock.elapsedRealtime();
415
416            Notification notification = new Notification.Builder(mContext)
417                    .setSmallIcon(R.drawable.vpn_connected)
418                    .setLargeIcon(icon)
419                    .setContentTitle(title)
420                    .setContentText(text)
421                    .setContentIntent(mStatusIntent)
422                    .setDefaults(0)
423                    .setOngoing(true)
424                    .build();
425            nm.notifyAsUser(null, R.drawable.vpn_connected, notification, UserHandle.ALL);
426        }
427    }
428
429    private void hideNotification() {
430        if (!mEnableNotif) return;
431        mStatusIntent = null;
432
433        NotificationManager nm = (NotificationManager)
434                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
435
436        if (nm != null) {
437            nm.cancelAsUser(null, R.drawable.vpn_connected, UserHandle.ALL);
438        }
439    }
440
441    private native int jniCreate(int mtu);
442    private native String jniGetName(int tun);
443    private native int jniSetAddresses(String interfaze, String addresses);
444    private native int jniSetRoutes(String interfaze, String routes);
445    private native void jniReset(String interfaze);
446    private native int jniCheck(String interfaze);
447    private native void jniProtect(int socket, String interfaze);
448
449    private static String findLegacyVpnGateway(LinkProperties prop) {
450        for (RouteInfo route : prop.getRoutes()) {
451            // Currently legacy VPN only works on IPv4.
452            if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
453                return route.getGateway().getHostAddress();
454            }
455        }
456
457        throw new IllegalStateException("Unable to find suitable gateway");
458    }
459
460    /**
461     * Start legacy VPN, controlling native daemons as needed. Creates a
462     * secondary thread to perform connection work, returning quickly.
463     */
464    public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
465        if (keyStore.state() != KeyStore.State.UNLOCKED) {
466            throw new IllegalStateException("KeyStore isn't unlocked");
467        }
468
469        final String iface = egress.getInterfaceName();
470        final String gateway = findLegacyVpnGateway(egress);
471
472        // Load certificates.
473        String privateKey = "";
474        String userCert = "";
475        String caCert = "";
476        String serverCert = "";
477        if (!profile.ipsecUserCert.isEmpty()) {
478            privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
479            byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
480            userCert = (value == null) ? null : new String(value, Charsets.UTF_8);
481        }
482        if (!profile.ipsecCaCert.isEmpty()) {
483            byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
484            caCert = (value == null) ? null : new String(value, Charsets.UTF_8);
485        }
486        if (!profile.ipsecServerCert.isEmpty()) {
487            byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
488            serverCert = (value == null) ? null : new String(value, Charsets.UTF_8);
489        }
490        if (privateKey == null || userCert == null || caCert == null || serverCert == null) {
491            throw new IllegalStateException("Cannot load credentials");
492        }
493
494        // Prepare arguments for racoon.
495        String[] racoon = null;
496        switch (profile.type) {
497            case VpnProfile.TYPE_L2TP_IPSEC_PSK:
498                racoon = new String[] {
499                    iface, profile.server, "udppsk", profile.ipsecIdentifier,
500                    profile.ipsecSecret, "1701",
501                };
502                break;
503            case VpnProfile.TYPE_L2TP_IPSEC_RSA:
504                racoon = new String[] {
505                    iface, profile.server, "udprsa", privateKey, userCert,
506                    caCert, serverCert, "1701",
507                };
508                break;
509            case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
510                racoon = new String[] {
511                    iface, profile.server, "xauthpsk", profile.ipsecIdentifier,
512                    profile.ipsecSecret, profile.username, profile.password, "", gateway,
513                };
514                break;
515            case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
516                racoon = new String[] {
517                    iface, profile.server, "xauthrsa", privateKey, userCert,
518                    caCert, serverCert, profile.username, profile.password, "", gateway,
519                };
520                break;
521            case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
522                racoon = new String[] {
523                    iface, profile.server, "hybridrsa",
524                    caCert, serverCert, profile.username, profile.password, "", gateway,
525                };
526                break;
527        }
528
529        // Prepare arguments for mtpd.
530        String[] mtpd = null;
531        switch (profile.type) {
532            case VpnProfile.TYPE_PPTP:
533                mtpd = new String[] {
534                    iface, "pptp", profile.server, "1723",
535                    "name", profile.username, "password", profile.password,
536                    "linkname", "vpn", "refuse-eap", "nodefaultroute",
537                    "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
538                    (profile.mppe ? "+mppe" : "nomppe"),
539                };
540                break;
541            case VpnProfile.TYPE_L2TP_IPSEC_PSK:
542            case VpnProfile.TYPE_L2TP_IPSEC_RSA:
543                mtpd = new String[] {
544                    iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
545                    "name", profile.username, "password", profile.password,
546                    "linkname", "vpn", "refuse-eap", "nodefaultroute",
547                    "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
548                };
549                break;
550        }
551
552        VpnConfig config = new VpnConfig();
553        config.legacy = true;
554        config.user = profile.key;
555        config.interfaze = iface;
556        config.session = profile.name;
557        config.routes = profile.routes;
558        if (!profile.dnsServers.isEmpty()) {
559            config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
560        }
561        if (!profile.searchDomains.isEmpty()) {
562            config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
563        }
564
565        startLegacyVpn(config, racoon, mtpd);
566    }
567
568    private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
569        stopLegacyVpn();
570
571        // Prepare for the new request. This also checks the caller.
572        prepare(null, VpnConfig.LEGACY_VPN);
573        updateState(DetailedState.CONNECTING, "startLegacyVpn");
574
575        // Start a new LegacyVpnRunner and we are done!
576        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
577        mLegacyVpnRunner.start();
578    }
579
580    public synchronized void stopLegacyVpn() {
581        if (mLegacyVpnRunner != null) {
582            mLegacyVpnRunner.exit();
583            mLegacyVpnRunner = null;
584
585            synchronized (LegacyVpnRunner.TAG) {
586                // wait for old thread to completely finish before spinning up
587                // new instance, otherwise state updates can be out of order.
588            }
589        }
590    }
591
592    /**
593     * Return the information of the current ongoing legacy VPN.
594     */
595    public synchronized LegacyVpnInfo getLegacyVpnInfo() {
596        // Check if the caller is authorized.
597        enforceControlPermission();
598        if (mLegacyVpnRunner == null) return null;
599
600        final LegacyVpnInfo info = new LegacyVpnInfo();
601        info.key = mLegacyVpnRunner.mConfig.user;
602        info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo);
603        if (mNetworkInfo.isConnected()) {
604            info.intent = mStatusIntent;
605        }
606        return info;
607    }
608
609    public VpnConfig getLegacyVpnConfig() {
610        if (mLegacyVpnRunner != null) {
611            return mLegacyVpnRunner.mConfig;
612        } else {
613            return null;
614        }
615    }
616
617    /**
618     * Bringing up a VPN connection takes time, and that is all this thread
619     * does. Here we have plenty of time. The only thing we need to take
620     * care of is responding to interruptions as soon as possible. Otherwise
621     * requests will be piled up. This can be done in a Handler as a state
622     * machine, but it is much easier to read in the current form.
623     */
624    private class LegacyVpnRunner extends Thread {
625        private static final String TAG = "LegacyVpnRunner";
626
627        private final VpnConfig mConfig;
628        private final String[] mDaemons;
629        private final String[][] mArguments;
630        private final LocalSocket[] mSockets;
631        private final String mOuterInterface;
632
633        private long mTimer = -1;
634
635        public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
636            super(TAG);
637            mConfig = config;
638            mDaemons = new String[] {"racoon", "mtpd"};
639            // TODO: clear arguments from memory once launched
640            mArguments = new String[][] {racoon, mtpd};
641            mSockets = new LocalSocket[mDaemons.length];
642
643            // This is the interface which VPN is running on,
644            // mConfig.interfaze will change to point to OUR
645            // internal interface soon. TODO - add inner/outer to mconfig
646            mOuterInterface = mConfig.interfaze;
647        }
648
649        public void check(String interfaze) {
650            if (interfaze.equals(mOuterInterface)) {
651                Log.i(TAG, "Legacy VPN is going down with " + interfaze);
652                exit();
653            }
654        }
655
656        public void exit() {
657            // We assume that everything is reset after stopping the daemons.
658            interrupt();
659            for (LocalSocket socket : mSockets) {
660                IoUtils.closeQuietly(socket);
661            }
662            updateState(DetailedState.DISCONNECTED, "exit");
663        }
664
665        @Override
666        public void run() {
667            // Wait for the previous thread since it has been interrupted.
668            Log.v(TAG, "Waiting");
669            synchronized (TAG) {
670                Log.v(TAG, "Executing");
671                execute();
672                monitorDaemons();
673            }
674        }
675
676        private void checkpoint(boolean yield) throws InterruptedException {
677            long now = SystemClock.elapsedRealtime();
678            if (mTimer == -1) {
679                mTimer = now;
680                Thread.sleep(1);
681            } else if (now - mTimer <= 60000) {
682                Thread.sleep(yield ? 200 : 1);
683            } else {
684                updateState(DetailedState.FAILED, "checkpoint");
685                throw new IllegalStateException("Time is up");
686            }
687        }
688
689        private void execute() {
690            // Catch all exceptions so we can clean up few things.
691            boolean initFinished = false;
692            try {
693                // Initialize the timer.
694                checkpoint(false);
695
696                // Wait for the daemons to stop.
697                for (String daemon : mDaemons) {
698                    while (!SystemService.isStopped(daemon)) {
699                        checkpoint(true);
700                    }
701                }
702
703                // Clear the previous state.
704                File state = new File("/data/misc/vpn/state");
705                state.delete();
706                if (state.exists()) {
707                    throw new IllegalStateException("Cannot delete the state");
708                }
709                new File("/data/misc/vpn/abort").delete();
710                initFinished = true;
711
712                // Check if we need to restart any of the daemons.
713                boolean restart = false;
714                for (String[] arguments : mArguments) {
715                    restart = restart || (arguments != null);
716                }
717                if (!restart) {
718                    updateState(DetailedState.DISCONNECTED, "execute");
719                    return;
720                }
721                updateState(DetailedState.CONNECTING, "execute");
722
723                // Start the daemon with arguments.
724                for (int i = 0; i < mDaemons.length; ++i) {
725                    String[] arguments = mArguments[i];
726                    if (arguments == null) {
727                        continue;
728                    }
729
730                    // Start the daemon.
731                    String daemon = mDaemons[i];
732                    SystemService.start(daemon);
733
734                    // Wait for the daemon to start.
735                    while (!SystemService.isRunning(daemon)) {
736                        checkpoint(true);
737                    }
738
739                    // Create the control socket.
740                    mSockets[i] = new LocalSocket();
741                    LocalSocketAddress address = new LocalSocketAddress(
742                            daemon, LocalSocketAddress.Namespace.RESERVED);
743
744                    // Wait for the socket to connect.
745                    while (true) {
746                        try {
747                            mSockets[i].connect(address);
748                            break;
749                        } catch (Exception e) {
750                            // ignore
751                        }
752                        checkpoint(true);
753                    }
754                    mSockets[i].setSoTimeout(500);
755
756                    // Send over the arguments.
757                    OutputStream out = mSockets[i].getOutputStream();
758                    for (String argument : arguments) {
759                        byte[] bytes = argument.getBytes(Charsets.UTF_8);
760                        if (bytes.length >= 0xFFFF) {
761                            throw new IllegalArgumentException("Argument is too large");
762                        }
763                        out.write(bytes.length >> 8);
764                        out.write(bytes.length);
765                        out.write(bytes);
766                        checkpoint(false);
767                    }
768                    out.write(0xFF);
769                    out.write(0xFF);
770                    out.flush();
771
772                    // Wait for End-of-File.
773                    InputStream in = mSockets[i].getInputStream();
774                    while (true) {
775                        try {
776                            if (in.read() == -1) {
777                                break;
778                            }
779                        } catch (Exception e) {
780                            // ignore
781                        }
782                        checkpoint(true);
783                    }
784                }
785
786                // Wait for the daemons to create the new state.
787                while (!state.exists()) {
788                    // Check if a running daemon is dead.
789                    for (int i = 0; i < mDaemons.length; ++i) {
790                        String daemon = mDaemons[i];
791                        if (mArguments[i] != null && !SystemService.isRunning(daemon)) {
792                            throw new IllegalStateException(daemon + " is dead");
793                        }
794                    }
795                    checkpoint(true);
796                }
797
798                // Now we are connected. Read and parse the new state.
799                String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
800                if (parameters.length != 6) {
801                    throw new IllegalStateException("Cannot parse the state");
802                }
803
804                // Set the interface and the addresses in the config.
805                mConfig.interfaze = parameters[0].trim();
806                mConfig.addresses = parameters[1].trim();
807
808                // Set the routes if they are not set in the config.
809                if (mConfig.routes == null || mConfig.routes.isEmpty()) {
810                    mConfig.routes = parameters[2].trim();
811                }
812
813                // Set the DNS servers if they are not set in the config.
814                if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
815                    String dnsServers = parameters[3].trim();
816                    if (!dnsServers.isEmpty()) {
817                        mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
818                    }
819                }
820
821                // Set the search domains if they are not set in the config.
822                if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
823                    String searchDomains = parameters[4].trim();
824                    if (!searchDomains.isEmpty()) {
825                        mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
826                    }
827                }
828
829                // Set the routes.
830                jniSetRoutes(mConfig.interfaze, mConfig.routes);
831
832                // Here is the last step and it must be done synchronously.
833                synchronized (Vpn.this) {
834                    // Check if the thread is interrupted while we are waiting.
835                    checkpoint(false);
836
837                    // Check if the interface is gone while we are waiting.
838                    if (jniCheck(mConfig.interfaze) == 0) {
839                        throw new IllegalStateException(mConfig.interfaze + " is gone");
840                    }
841
842                    // Now INetworkManagementEventObserver is watching our back.
843                    mInterface = mConfig.interfaze;
844                    mCallback.override(mConfig.dnsServers, mConfig.searchDomains);
845                    showNotification(mConfig, null, null);
846
847                    Log.i(TAG, "Connected!");
848                    updateState(DetailedState.CONNECTED, "execute");
849                }
850            } catch (Exception e) {
851                Log.i(TAG, "Aborting", e);
852                exit();
853            } finally {
854                // Kill the daemons if they fail to stop.
855                if (!initFinished) {
856                    for (String daemon : mDaemons) {
857                        SystemService.stop(daemon);
858                    }
859                }
860
861                // Do not leave an unstable state.
862                if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
863                    updateState(DetailedState.FAILED, "execute");
864                }
865            }
866        }
867
868        /**
869         * Monitor the daemons we started, moving to disconnected state if the
870         * underlying services fail.
871         */
872        private void monitorDaemons() {
873            if (!mNetworkInfo.isConnected()) {
874                return;
875            }
876
877            try {
878                while (true) {
879                    Thread.sleep(2000);
880                    for (int i = 0; i < mDaemons.length; i++) {
881                        if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
882                            return;
883                        }
884                    }
885                }
886            } catch (InterruptedException e) {
887                Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
888            } finally {
889                for (String daemon : mDaemons) {
890                    SystemService.stop(daemon);
891                }
892
893                updateState(DetailedState.DISCONNECTED, "babysit");
894            }
895        }
896    }
897}
898