Vpn.java revision 065b299df4159602327977dd007cb2cd6b64ab20
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 android.app.Notification;
20import android.app.NotificationManager;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.content.res.Resources;
29import android.graphics.Bitmap;
30import android.graphics.Canvas;
31import android.graphics.drawable.Drawable;
32import android.net.INetworkManagementEventObserver;
33import android.net.LocalSocket;
34import android.net.LocalSocketAddress;
35import android.os.Binder;
36import android.os.FileUtils;
37import android.os.IBinder;
38import android.os.Parcel;
39import android.os.ParcelFileDescriptor;
40import android.os.Process;
41import android.os.SystemClock;
42import android.os.SystemProperties;
43import android.util.Log;
44
45import com.android.internal.R;
46import com.android.internal.net.LegacyVpnInfo;
47import com.android.internal.net.VpnConfig;
48import com.android.server.ConnectivityService.VpnCallback;
49
50import java.io.File;
51import java.io.InputStream;
52import java.io.OutputStream;
53import java.nio.charset.Charsets;
54import java.util.Arrays;
55
56import libcore.io.IoUtils;
57
58/**
59 * @hide
60 */
61public class Vpn extends INetworkManagementEventObserver.Stub {
62
63    private final static String TAG = "Vpn";
64
65    private final static String BIND_VPN_SERVICE =
66            android.Manifest.permission.BIND_VPN_SERVICE;
67
68    private final Context mContext;
69    private final VpnCallback mCallback;
70
71    private String mPackage = VpnConfig.LEGACY_VPN;
72    private String mInterface;
73    private Connection mConnection;
74    private LegacyVpnRunner mLegacyVpnRunner;
75
76    public Vpn(Context context, VpnCallback callback) {
77        mContext = context;
78        mCallback = callback;
79    }
80
81    /**
82     * Prepare for a VPN application. This method is designed to solve
83     * race conditions. It first compares the current prepared package
84     * with {@code oldPackage}. If they are the same, the prepared
85     * package is revoked and replaced with {@code newPackage}. If
86     * {@code oldPackage} is {@code null}, the comparison is omitted.
87     * If {@code newPackage} is the same package or {@code null}, the
88     * revocation is omitted. This method returns {@code true} if the
89     * operation is succeeded.
90     *
91     * Legacy VPN is handled specially since it is not a real package.
92     * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
93     * it can be revoked by itself.
94     *
95     * @param oldPackage The package name of the old VPN application.
96     * @param newPackage The package name of the new VPN application.
97     * @return true if the operation is succeeded.
98     */
99    public synchronized boolean prepare(String oldPackage, String newPackage) {
100        // Return false if the package does not match.
101        if (oldPackage != null && !oldPackage.equals(mPackage)) {
102            return false;
103        }
104
105        // Return true if we do not need to revoke.
106        if (newPackage == null ||
107                (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) {
108            return true;
109        }
110
111        // Check if the caller is authorized.
112        enforceControlPermission();
113
114        // Reset the interface and hide the notification.
115        if (mInterface != null) {
116            jniReset(mInterface);
117            long identity = Binder.clearCallingIdentity();
118            mCallback.restore();
119            hideNotification();
120            Binder.restoreCallingIdentity(identity);
121            mInterface = null;
122        }
123
124        // Revoke the connection or stop LegacyVpnRunner.
125        if (mConnection != null) {
126            try {
127                mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
128                        Parcel.obtain(), null, IBinder.FLAG_ONEWAY);
129            } catch (Exception e) {
130                // ignore
131            }
132            mContext.unbindService(mConnection);
133            mConnection = null;
134        } else if (mLegacyVpnRunner != null) {
135            mLegacyVpnRunner.exit();
136            mLegacyVpnRunner = null;
137        }
138
139        Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
140        mPackage = newPackage;
141        return true;
142    }
143
144    /**
145     * Protect a socket from routing changes by binding it to the given
146     * interface. The socket is NOT closed by this method.
147     *
148     * @param socket The socket to be bound.
149     * @param name The name of the interface.
150     */
151    public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
152        PackageManager pm = mContext.getPackageManager();
153        ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
154        if (Binder.getCallingUid() != app.uid) {
155            throw new SecurityException("Unauthorized Caller");
156        }
157        jniProtect(socket.getFd(), interfaze);
158    }
159
160    /**
161     * Establish a VPN network and return the file descriptor of the VPN
162     * interface. This methods returns {@code null} if the application is
163     * revoked or not prepared.
164     *
165     * @param config The parameters to configure the network.
166     * @return The file descriptor of the VPN interface.
167     */
168    public synchronized ParcelFileDescriptor establish(VpnConfig config) {
169        // Check if the caller is already prepared.
170        PackageManager pm = mContext.getPackageManager();
171        ApplicationInfo app = null;
172        try {
173            app = pm.getApplicationInfo(mPackage, 0);
174        } catch (Exception e) {
175            return null;
176        }
177        if (Binder.getCallingUid() != app.uid) {
178            return null;
179        }
180
181        // Check if the service is properly declared.
182        Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
183        intent.setClassName(mPackage, config.user);
184        ResolveInfo info = pm.resolveService(intent, 0);
185        if (info == null) {
186            throw new SecurityException("Cannot find " + config.user);
187        }
188        if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
189            throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
190        }
191
192        // Load the label.
193        String label = app.loadLabel(pm).toString();
194
195        // Load the icon and convert it into a bitmap.
196        Drawable icon = app.loadIcon(pm);
197        Bitmap bitmap = null;
198        if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
199            int width = mContext.getResources().getDimensionPixelSize(
200                    android.R.dimen.notification_large_icon_width);
201            int height = mContext.getResources().getDimensionPixelSize(
202                    android.R.dimen.notification_large_icon_height);
203            icon.setBounds(0, 0, width, height);
204            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
205            Canvas c = new Canvas(bitmap);
206            icon.draw(c);
207            c.setBitmap(null);
208        }
209
210        // Configure the interface. Abort if any of these steps fails.
211        ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
212        try {
213            String interfaze = jniGetName(tun.getFd());
214            if (jniSetAddresses(interfaze, config.addresses) < 1) {
215                throw new IllegalArgumentException("At least one address must be specified");
216            }
217            if (config.routes != null) {
218                jniSetRoutes(interfaze, config.routes);
219            }
220            Connection connection = new Connection();
221            if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
222                throw new IllegalStateException("Cannot bind " + config.user);
223            }
224            if (mConnection != null) {
225                mContext.unbindService(mConnection);
226            }
227            if (mInterface != null && !mInterface.equals(interfaze)) {
228                jniReset(mInterface);
229            }
230            mConnection = connection;
231            mInterface = interfaze;
232        } catch (RuntimeException e) {
233            IoUtils.closeQuietly(tun);
234            throw e;
235        }
236        Log.i(TAG, "Established by " + config.user + " on " + mInterface);
237
238        // Fill more values.
239        config.user = mPackage;
240        config.interfaze = mInterface;
241
242        // Override DNS servers and show the notification.
243        long identity = Binder.clearCallingIdentity();
244        mCallback.override(config.dnsServers, config.searchDomains);
245        showNotification(config, label, bitmap);
246        Binder.restoreCallingIdentity(identity);
247        return tun;
248    }
249
250    // INetworkManagementEventObserver.Stub
251    @Override
252    public void interfaceAdded(String interfaze) {
253    }
254
255    // INetworkManagementEventObserver.Stub
256    @Override
257    public synchronized void interfaceStatusChanged(String interfaze, boolean up) {
258        if (!up && mLegacyVpnRunner != null) {
259            mLegacyVpnRunner.check(interfaze);
260        }
261    }
262
263    // INetworkManagementEventObserver.Stub
264    @Override
265    public void interfaceLinkStateChanged(String interfaze, boolean up) {
266    }
267
268    // INetworkManagementEventObserver.Stub
269    @Override
270    public synchronized void interfaceRemoved(String interfaze) {
271        if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
272            long identity = Binder.clearCallingIdentity();
273            mCallback.restore();
274            hideNotification();
275            Binder.restoreCallingIdentity(identity);
276            mInterface = null;
277            if (mConnection != null) {
278                mContext.unbindService(mConnection);
279                mConnection = null;
280            } else if (mLegacyVpnRunner != null) {
281                mLegacyVpnRunner.exit();
282                mLegacyVpnRunner = null;
283            }
284        }
285    }
286
287    // INetworkManagementEventObserver.Stub
288    @Override
289    public void limitReached(String limit, String interfaze) {
290    }
291
292    public void interfaceClassDataActivityChanged(String label, boolean active) {
293    }
294
295    private void enforceControlPermission() {
296        // System user is allowed to control VPN.
297        if (Binder.getCallingUid() == Process.SYSTEM_UID) {
298            return;
299        }
300
301        try {
302            // System dialogs are also allowed to control VPN.
303            PackageManager pm = mContext.getPackageManager();
304            ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0);
305            if (Binder.getCallingUid() == app.uid) {
306                return;
307            }
308        } catch (Exception e) {
309            // ignore
310        }
311
312        throw new SecurityException("Unauthorized Caller");
313    }
314
315    private class Connection implements ServiceConnection {
316        private IBinder mService;
317
318        @Override
319        public void onServiceConnected(ComponentName name, IBinder service) {
320            mService = service;
321        }
322
323        @Override
324        public void onServiceDisconnected(ComponentName name) {
325            mService = null;
326        }
327    }
328
329    private void showNotification(VpnConfig config, String label, Bitmap icon) {
330        NotificationManager nm = (NotificationManager)
331                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
332
333        if (nm != null) {
334            String title = (label == null) ? mContext.getString(R.string.vpn_title) :
335                    mContext.getString(R.string.vpn_title_long, label);
336            String text = (config.session == null) ? mContext.getString(R.string.vpn_text) :
337                    mContext.getString(R.string.vpn_text_long, config.session);
338            config.startTime = SystemClock.elapsedRealtime();
339
340            Notification notification = new Notification.Builder(mContext)
341                    .setSmallIcon(R.drawable.vpn_connected)
342                    .setLargeIcon(icon)
343                    .setContentTitle(title)
344                    .setContentText(text)
345                    .setContentIntent(VpnConfig.getIntentForStatusPanel(mContext, config))
346                    .setDefaults(0)
347                    .setOngoing(true)
348                    .getNotification();
349            nm.notify(R.drawable.vpn_connected, notification);
350        }
351    }
352
353    private void hideNotification() {
354        NotificationManager nm = (NotificationManager)
355                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
356
357        if (nm != null) {
358            nm.cancel(R.drawable.vpn_connected);
359        }
360    }
361
362    private native int jniCreate(int mtu);
363    private native String jniGetName(int tun);
364    private native int jniSetAddresses(String interfaze, String addresses);
365    private native int jniSetRoutes(String interfaze, String routes);
366    private native void jniReset(String interfaze);
367    private native int jniCheck(String interfaze);
368    private native void jniProtect(int socket, String interfaze);
369
370    /**
371     * Start legacy VPN. This method stops the daemons and restart them
372     * if arguments are not null. Heavy things are offloaded to another
373     * thread, so callers will not be blocked for a long time.
374     *
375     * @param config The parameters to configure the network.
376     * @param raoocn The arguments to be passed to racoon.
377     * @param mtpd The arguments to be passed to mtpd.
378     */
379    public synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
380        // Prepare for the new request. This also checks the caller.
381        prepare(null, VpnConfig.LEGACY_VPN);
382
383        // Start a new LegacyVpnRunner and we are done!
384        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
385        mLegacyVpnRunner.start();
386    }
387
388    /**
389     * Return the information of the current ongoing legacy VPN.
390     */
391    public synchronized LegacyVpnInfo getLegacyVpnInfo() {
392        // Check if the caller is authorized.
393        enforceControlPermission();
394        return (mLegacyVpnRunner == null) ? null : mLegacyVpnRunner.getInfo();
395    }
396
397    /**
398     * Bringing up a VPN connection takes time, and that is all this thread
399     * does. Here we have plenty of time. The only thing we need to take
400     * care of is responding to interruptions as soon as possible. Otherwise
401     * requests will be piled up. This can be done in a Handler as a state
402     * machine, but it is much easier to read in the current form.
403     */
404    private class LegacyVpnRunner extends Thread {
405        private static final String TAG = "LegacyVpnRunner";
406
407        private final VpnConfig mConfig;
408        private final String[] mDaemons;
409        private final String[][] mArguments;
410        private final LocalSocket[] mSockets;
411        private final String mOuterInterface;
412        private final LegacyVpnInfo mInfo;
413
414        private long mTimer = -1;
415
416        public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
417            super(TAG);
418            mConfig = config;
419            mDaemons = new String[] {"racoon", "mtpd"};
420            mArguments = new String[][] {racoon, mtpd};
421            mSockets = new LocalSocket[mDaemons.length];
422            mInfo = new LegacyVpnInfo();
423
424            // This is the interface which VPN is running on.
425            mOuterInterface = mConfig.interfaze;
426
427            // Legacy VPN is not a real package, so we use it to carry the key.
428            mInfo.key = mConfig.user;
429            mConfig.user = VpnConfig.LEGACY_VPN;
430        }
431
432        public void check(String interfaze) {
433            if (interfaze.equals(mOuterInterface)) {
434                Log.i(TAG, "Legacy VPN is going down with " + interfaze);
435                exit();
436            }
437        }
438
439        public void exit() {
440            // We assume that everything is reset after stopping the daemons.
441            interrupt();
442            for (LocalSocket socket : mSockets) {
443                IoUtils.closeQuietly(socket);
444            }
445        }
446
447        public LegacyVpnInfo getInfo() {
448            // Update the info when VPN is disconnected.
449            if (mInfo.state == LegacyVpnInfo.STATE_CONNECTED && mInterface == null) {
450                mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED;
451                mInfo.intent = null;
452            }
453            return mInfo;
454        }
455
456        @Override
457        public void run() {
458            // Wait for the previous thread since it has been interrupted.
459            Log.v(TAG, "Waiting");
460            synchronized (TAG) {
461                Log.v(TAG, "Executing");
462                execute();
463            }
464        }
465
466        private void checkpoint(boolean yield) throws InterruptedException {
467            long now = SystemClock.elapsedRealtime();
468            if (mTimer == -1) {
469                mTimer = now;
470                Thread.sleep(1);
471            } else if (now - mTimer <= 60000) {
472                Thread.sleep(yield ? 200 : 1);
473            } else {
474                mInfo.state = LegacyVpnInfo.STATE_TIMEOUT;
475                throw new IllegalStateException("Time is up");
476            }
477        }
478
479        private void execute() {
480            // Catch all exceptions so we can clean up few things.
481            try {
482                // Initialize the timer.
483                checkpoint(false);
484                mInfo.state = LegacyVpnInfo.STATE_INITIALIZING;
485
486                // Wait for the daemons to stop.
487                for (String daemon : mDaemons) {
488                    String key = "init.svc." + daemon;
489                    while (!"stopped".equals(SystemProperties.get(key, "stopped"))) {
490                        checkpoint(true);
491                    }
492                }
493
494                // Clear the previous state.
495                File state = new File("/data/misc/vpn/state");
496                state.delete();
497                if (state.exists()) {
498                    throw new IllegalStateException("Cannot delete the state");
499                }
500                new File("/data/misc/vpn/abort").delete();
501
502                // Check if we need to restart any of the daemons.
503                boolean restart = false;
504                for (String[] arguments : mArguments) {
505                    restart = restart || (arguments != null);
506                }
507                if (!restart) {
508                    mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED;
509                    return;
510                }
511                mInfo.state = LegacyVpnInfo.STATE_CONNECTING;
512
513                // Start the daemon with arguments.
514                for (int i = 0; i < mDaemons.length; ++i) {
515                    String[] arguments = mArguments[i];
516                    if (arguments == null) {
517                        continue;
518                    }
519
520                    // Start the daemon.
521                    String daemon = mDaemons[i];
522                    SystemProperties.set("ctl.start", daemon);
523
524                    // Wait for the daemon to start.
525                    String key = "init.svc." + daemon;
526                    while (!"running".equals(SystemProperties.get(key))) {
527                        checkpoint(true);
528                    }
529
530                    // Create the control socket.
531                    mSockets[i] = new LocalSocket();
532                    LocalSocketAddress address = new LocalSocketAddress(
533                            daemon, LocalSocketAddress.Namespace.RESERVED);
534
535                    // Wait for the socket to connect.
536                    while (true) {
537                        try {
538                            mSockets[i].connect(address);
539                            break;
540                        } catch (Exception e) {
541                            // ignore
542                        }
543                        checkpoint(true);
544                    }
545                    mSockets[i].setSoTimeout(500);
546
547                    // Send over the arguments.
548                    OutputStream out = mSockets[i].getOutputStream();
549                    for (String argument : arguments) {
550                        byte[] bytes = argument.getBytes(Charsets.UTF_8);
551                        if (bytes.length >= 0xFFFF) {
552                            throw new IllegalArgumentException("Argument is too large");
553                        }
554                        out.write(bytes.length >> 8);
555                        out.write(bytes.length);
556                        out.write(bytes);
557                        checkpoint(false);
558                    }
559                    out.write(0xFF);
560                    out.write(0xFF);
561                    out.flush();
562
563                    // Wait for End-of-File.
564                    InputStream in = mSockets[i].getInputStream();
565                    while (true) {
566                        try {
567                            if (in.read() == -1) {
568                                break;
569                            }
570                        } catch (Exception e) {
571                            // ignore
572                        }
573                        checkpoint(true);
574                    }
575                }
576
577                // Wait for the daemons to create the new state.
578                while (!state.exists()) {
579                    // Check if a running daemon is dead.
580                    for (int i = 0; i < mDaemons.length; ++i) {
581                        String daemon = mDaemons[i];
582                        if (mArguments[i] != null && !"running".equals(
583                                SystemProperties.get("init.svc." + daemon))) {
584                            throw new IllegalStateException(daemon + " is dead");
585                        }
586                    }
587                    checkpoint(true);
588                }
589
590                // Now we are connected. Read and parse the new state.
591                String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
592                if (parameters.length != 6) {
593                    throw new IllegalStateException("Cannot parse the state");
594                }
595
596                // Set the interface and the addresses in the config.
597                mConfig.interfaze = parameters[0].trim();
598                mConfig.addresses = parameters[1].trim();
599
600                // Set the routes if they are not set in the config.
601                if (mConfig.routes == null || mConfig.routes.isEmpty()) {
602                    mConfig.routes = parameters[2].trim();
603                }
604
605                // Set the DNS servers if they are not set in the config.
606                if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
607                    String dnsServers = parameters[3].trim();
608                    if (!dnsServers.isEmpty()) {
609                        mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
610                    }
611                }
612
613                // Set the search domains if they are not set in the config.
614                if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
615                    String searchDomains = parameters[4].trim();
616                    if (!searchDomains.isEmpty()) {
617                        mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
618                    }
619                }
620
621                // Set the routes.
622                jniSetRoutes(mConfig.interfaze, mConfig.routes);
623
624                // Here is the last step and it must be done synchronously.
625                synchronized (Vpn.this) {
626                    // Check if the thread is interrupted while we are waiting.
627                    checkpoint(false);
628
629                    // Check if the interface is gone while we are waiting.
630                    if (jniCheck(mConfig.interfaze) == 0) {
631                        throw new IllegalStateException(mConfig.interfaze + " is gone");
632                    }
633
634                    // Now INetworkManagementEventObserver is watching our back.
635                    mInterface = mConfig.interfaze;
636                    mCallback.override(mConfig.dnsServers, mConfig.searchDomains);
637                    showNotification(mConfig, null, null);
638
639                    Log.i(TAG, "Connected!");
640                    mInfo.state = LegacyVpnInfo.STATE_CONNECTED;
641                    mInfo.intent = VpnConfig.getIntentForStatusPanel(mContext, null);
642                }
643            } catch (Exception e) {
644                Log.i(TAG, "Aborting", e);
645                exit();
646            } finally {
647                // Kill the daemons if they fail to stop.
648                if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING) {
649                    for (String daemon : mDaemons) {
650                        SystemProperties.set("ctl.stop", daemon);
651                    }
652                }
653
654                // Do not leave an unstable state.
655                if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING ||
656                        mInfo.state == LegacyVpnInfo.STATE_CONNECTING) {
657                    mInfo.state = LegacyVpnInfo.STATE_FAILED;
658                }
659            }
660        }
661    }
662}
663