Vpn.java revision 34e7813e962de99df9813014678ef5901227c5f1
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.Context;
22import android.content.Intent;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageManager;
25import android.content.res.Resources;
26import android.graphics.Bitmap;
27import android.graphics.Canvas;
28import android.graphics.drawable.Drawable;
29import android.net.INetworkManagementEventObserver;
30import android.net.LocalSocket;
31import android.net.LocalSocketAddress;
32import android.os.Binder;
33import android.os.ParcelFileDescriptor;
34import android.os.Process;
35import android.os.SystemClock;
36import android.os.SystemProperties;
37import android.util.Log;
38
39import com.android.internal.R;
40import com.android.internal.net.VpnConfig;
41import com.android.server.ConnectivityService.VpnCallback;
42
43import java.io.OutputStream;
44import java.nio.charset.Charsets;
45import java.util.Arrays;
46
47/**
48 * @hide
49 */
50public class Vpn extends INetworkManagementEventObserver.Stub {
51
52    private final static String TAG = "Vpn";
53    private final static String VPN = android.Manifest.permission.VPN;
54
55    private final Context mContext;
56    private final VpnCallback mCallback;
57
58    private String mPackageName = VpnConfig.LEGACY_VPN;
59    private String mInterfaceName;
60    private LegacyVpnRunner mLegacyVpnRunner;
61
62    public Vpn(Context context, VpnCallback callback) {
63        mContext = context;
64        mCallback = callback;
65    }
66
67    /**
68     * Protect a socket from routing changes by binding it to the given
69     * interface. The socket IS closed by this method.
70     *
71     * @param socket The socket to be bound.
72     * @param name The name of the interface.
73     */
74    public void protect(ParcelFileDescriptor socket, String name) {
75        try {
76            mContext.enforceCallingPermission(VPN, "protect");
77            jniProtectSocket(socket.getFd(), name);
78        } finally {
79            try {
80                socket.close();
81            } catch (Exception e) {
82                // ignore
83            }
84        }
85    }
86
87    /**
88     * Prepare for a VPN application. If the new application is valid,
89     * the previous prepared application is revoked. Since legacy VPN
90     * is not a real application, it uses {@link VpnConfig#LEGACY_VPN}
91     * as its package name. Note that this method does not check if
92     * the applications are the same.
93     *
94     * @param packageName The package name of the VPN application.
95     * @return The package name of the current prepared application.
96     */
97    public synchronized String prepare(String packageName) {
98        // Return the current prepared application if the new one is null.
99        if (packageName == null) {
100            return mPackageName;
101        }
102
103        // Only system user can call this method.
104        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
105            throw new SecurityException("Unauthorized Caller");
106        }
107
108        // Check the permission of the given package.
109        PackageManager pm = mContext.getPackageManager();
110        if (!packageName.equals(VpnConfig.LEGACY_VPN) &&
111                pm.checkPermission(VPN, packageName) != PackageManager.PERMISSION_GRANTED) {
112            throw new SecurityException(packageName + " does not have " + VPN);
113        }
114
115        // Reset the interface and hide the notification.
116        if (mInterfaceName != null) {
117            jniResetInterface(mInterfaceName);
118            mCallback.restore();
119            hideNotification();
120            mInterfaceName = null;
121        }
122
123        // Send out the broadcast or stop LegacyVpnRunner.
124        if (!mPackageName.equals(VpnConfig.LEGACY_VPN)) {
125            Intent intent = new Intent(VpnConfig.ACTION_VPN_REVOKED);
126            intent.setPackage(mPackageName);
127            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
128            mContext.sendBroadcast(intent);
129        } else if (mLegacyVpnRunner != null) {
130            mLegacyVpnRunner.exit();
131            mLegacyVpnRunner = null;
132        }
133
134        Log.i(TAG, "Switched from " + mPackageName + " to " + packageName);
135        mPackageName = packageName;
136        return mPackageName;
137    }
138
139    /**
140     * Establish a VPN network and return the file descriptor of the VPN
141     * interface. This methods returns {@code null} if the application is
142     * not prepared or revoked.
143     *
144     * @param config The parameters to configure the network.
145     * @return The file descriptor of the VPN interface.
146     */
147    public synchronized ParcelFileDescriptor establish(VpnConfig config) {
148        // Check the permission of the caller.
149        mContext.enforceCallingPermission(VPN, "establish");
150
151        // Check if the caller is already prepared.
152        PackageManager pm = mContext.getPackageManager();
153        ApplicationInfo app = null;
154        try {
155            app = pm.getApplicationInfo(mPackageName, 0);
156        } catch (Exception e) {
157            return null;
158        }
159        if (Binder.getCallingUid() != app.uid) {
160            return null;
161        }
162
163        // Load the label.
164        String label = app.loadLabel(pm).toString();
165
166        // Load the icon and convert it into a bitmap.
167        Drawable icon = app.loadIcon(pm);
168        Bitmap bitmap = null;
169        if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
170            int width = mContext.getResources().getDimensionPixelSize(
171                    android.R.dimen.notification_large_icon_width);
172            int height = mContext.getResources().getDimensionPixelSize(
173                    android.R.dimen.notification_large_icon_height);
174            icon.setBounds(0, 0, width, height);
175            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
176            icon.draw(new Canvas(bitmap));
177        }
178
179        // Configure the interface. Abort if any of these steps fails.
180        ParcelFileDescriptor descriptor = ParcelFileDescriptor.adoptFd(
181                jniConfigure(config.mtu, config.addresses, config.routes));
182        try {
183            String name = jniGetInterfaceName(descriptor.getFd());
184            if (mInterfaceName != null && !mInterfaceName.equals(name)) {
185                jniResetInterface(mInterfaceName);
186            }
187            mInterfaceName = name;
188        } catch (RuntimeException e) {
189            try {
190                descriptor.close();
191            } catch (Exception ex) {
192                // ignore
193            }
194            throw e;
195        }
196
197        // Override DNS servers and search domains.
198        mCallback.override(config.dnsServers, config.searchDomains);
199
200        // Fill more values.
201        config.packagz = mPackageName;
202        config.interfaze = mInterfaceName;
203
204        // Show the notification!
205        showNotification(config, label, bitmap);
206        return descriptor;
207    }
208
209    // INetworkManagementEventObserver.Stub
210    public void interfaceStatusChanged(String name, boolean up) {
211    }
212
213    // INetworkManagementEventObserver.Stub
214    public void interfaceLinkStateChanged(String name, boolean up) {
215    }
216
217    // INetworkManagementEventObserver.Stub
218    public void interfaceAdded(String name) {
219    }
220
221    // INetworkManagementEventObserver.Stub
222    public synchronized void interfaceRemoved(String name) {
223        if (name.equals(mInterfaceName) && jniCheckInterface(name) == 0) {
224            mCallback.restore();
225            hideNotification();
226            mInterfaceName = null;
227        }
228    }
229
230    private void showNotification(VpnConfig config, String label, Bitmap icon) {
231        NotificationManager nm = (NotificationManager)
232                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
233
234        if (nm != null) {
235            String title = (label == null) ? mContext.getString(R.string.vpn_title) :
236                    mContext.getString(R.string.vpn_title_long, label);
237            String text = (config.session == null) ? mContext.getString(R.string.vpn_text) :
238                    mContext.getString(R.string.vpn_text_long, config.session);
239
240            long identity = Binder.clearCallingIdentity();
241            Notification notification = new Notification.Builder(mContext)
242                    .setSmallIcon(R.drawable.vpn_connected)
243                    .setLargeIcon(icon)
244                    .setContentTitle(title)
245                    .setContentText(text)
246                    .setContentIntent(VpnConfig.getIntentForNotification(mContext, config))
247                    .setDefaults(Notification.DEFAULT_ALL)
248                    .setOngoing(true)
249                    .getNotification();
250            nm.notify(R.drawable.vpn_connected, notification);
251            Binder.restoreCallingIdentity(identity);
252        }
253    }
254
255    private void hideNotification() {
256        NotificationManager nm = (NotificationManager)
257                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
258
259        if (nm != null) {
260            long identity = Binder.clearCallingIdentity();
261            nm.cancel(R.drawable.vpn_connected);
262            Binder.restoreCallingIdentity(identity);
263        }
264    }
265
266    private native int jniConfigure(int mtu, String addresses, String routes);
267    private native String jniGetInterfaceName(int fd);
268    private native void jniResetInterface(String name);
269    private native int jniCheckInterface(String name);
270    private native void jniProtectSocket(int fd, String name);
271
272    /**
273     * Handle a legacy VPN request. This method stops the daemons and restart
274     * them if arguments are not null. Heavy things are offloaded to another
275     * thread, so callers will not be blocked for a long time.
276     *
277     * @param config The parameters to configure the network.
278     * @param raoocn The arguments to be passed to racoon.
279     * @param mtpd The arguments to be passed to mtpd.
280     */
281    public synchronized void doLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
282        // There is nothing to stop if another VPN application is prepared.
283        if (config == null && !mPackageName.equals(VpnConfig.LEGACY_VPN)) {
284            return;
285        }
286
287        // Reset everything. This also checks the caller.
288        prepare(VpnConfig.LEGACY_VPN);
289
290        // Start a new runner and we are done!
291        if (config != null) {
292            mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
293            mLegacyVpnRunner.start();
294        }
295    }
296
297    /**
298     * Bringing up a VPN connection takes time, and that is all this thread
299     * does. Here we have plenty of time. The only thing we need to take
300     * care of is responding to interruptions as soon as possible. Otherwise
301     * requests will be piled up. This can be done in a Handler as a state
302     * machine, but it is much easier to read in the current form.
303     */
304    private class LegacyVpnRunner extends Thread {
305        private static final String TAG = "LegacyVpnRunner";
306        private static final String NONE = "--";
307
308        private final VpnConfig mConfig;
309        private final String[] mDaemons;
310        private final String[][] mArguments;
311        private long mTimer = -1;
312
313        public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
314            super(TAG);
315            mConfig = config;
316            mDaemons = new String[] {"racoon", "mtpd"};
317            mArguments = new String[][] {racoon, mtpd};
318
319            mConfig.packagz = VpnConfig.LEGACY_VPN;
320        }
321
322        public void exit() {
323            // We assume that everything is reset after the daemons die.
324            for (String daemon : mDaemons) {
325                SystemProperties.set("ctl.stop", daemon);
326            }
327            interrupt();
328        }
329
330        @Override
331        public void run() {
332            // Wait for the previous thread since it has been interrupted.
333            Log.v(TAG, "wait");
334            synchronized (TAG) {
335                Log.v(TAG, "begin");
336                execute();
337                Log.v(TAG, "end");
338            }
339        }
340
341        private void checkpoint(boolean yield) throws InterruptedException {
342            long now = SystemClock.elapsedRealtime();
343            if (mTimer == -1) {
344                mTimer = now;
345                Thread.sleep(1);
346            } else if (now - mTimer <= 30000) {
347                Thread.sleep(yield ? 200 : 1);
348            } else {
349                throw new InterruptedException("time is up");
350            }
351        }
352
353        private void execute() {
354            // Catch all exceptions so we can clean up few things.
355            try {
356                // Initialize the timer.
357                checkpoint(false);
358
359                // First stop the daemons.
360                for (String daemon : mDaemons) {
361                    SystemProperties.set("ctl.stop", daemon);
362                }
363
364                // Wait for the daemons to stop.
365                for (String daemon : mDaemons) {
366                    String key = "init.svc." + daemon;
367                    while (!"stopped".equals(SystemProperties.get(key))) {
368                        checkpoint(true);
369                    }
370                }
371
372                // Reset the properties.
373                SystemProperties.set("vpn.dns", NONE);
374                SystemProperties.set("vpn.via", NONE);
375                while (!NONE.equals(SystemProperties.get("vpn.dns")) ||
376                        !NONE.equals(SystemProperties.get("vpn.via"))) {
377                    checkpoint(true);
378                }
379
380                // Check if we need to restart any of the daemons.
381                boolean restart = false;
382                for (String[] arguments : mArguments) {
383                    restart = restart || (arguments != null);
384                }
385                if (!restart) {
386                    return;
387                }
388
389                // Start the daemon with arguments.
390                for (int i = 0; i < mDaemons.length; ++i) {
391                    String[] arguments = mArguments[i];
392                    if (arguments == null) {
393                        continue;
394                    }
395
396                    // Start the daemon.
397                    String daemon = mDaemons[i];
398                    SystemProperties.set("ctl.start", daemon);
399
400                    // Wait for the daemon to start.
401                    String key = "init.svc." + daemon;
402                    while (!"running".equals(SystemProperties.get(key))) {
403                        checkpoint(true);
404                    }
405
406                    // Create the control socket.
407                    LocalSocket socket = new LocalSocket();
408                    LocalSocketAddress address = new LocalSocketAddress(
409                            daemon, LocalSocketAddress.Namespace.RESERVED);
410
411                    // Wait for the socket to connect.
412                    while (true) {
413                        try {
414                            socket.connect(address);
415                            break;
416                        } catch (Exception e) {
417                            // ignore
418                        }
419                        checkpoint(true);
420                    }
421                    socket.setSoTimeout(500);
422
423                    // Send over the arguments.
424                    OutputStream out = socket.getOutputStream();
425                    for (String argument : arguments) {
426                        byte[] bytes = argument.getBytes(Charsets.UTF_8);
427                        if (bytes.length >= 0xFFFF) {
428                            throw new IllegalArgumentException("argument is too large");
429                        }
430                        out.write(bytes.length >> 8);
431                        out.write(bytes.length);
432                        out.write(bytes);
433                        checkpoint(false);
434                    }
435
436                    // Send End-Of-Arguments.
437                    out.write(0xFF);
438                    out.write(0xFF);
439                    out.flush();
440                    socket.close();
441                }
442
443                // Now here is the beast from the old days. We check few
444                // properties to figure out the current status. Ideally we
445                // can read things back from the sockets and get rid of the
446                // properties, but we have no time...
447                while (NONE.equals(SystemProperties.get("vpn.dns")) ||
448                        NONE.equals(SystemProperties.get("vpn.via"))) {
449
450                    // Check if a running daemon is dead.
451                    for (int i = 0; i < mDaemons.length; ++i) {
452                        String daemon = mDaemons[i];
453                        if (mArguments[i] != null && !"running".equals(
454                                SystemProperties.get("init.svc." + daemon))) {
455                            throw new IllegalArgumentException(daemon + " is dead");
456                        }
457                    }
458                    checkpoint(true);
459                }
460
461                // Now we are connected. Get the interface.
462                mConfig.interfaze = SystemProperties.get("vpn.via");
463
464                // Get the DNS servers if they are not set in config.
465                if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
466                    String dnsServers = SystemProperties.get("vpn.dns").trim();
467                    if (!dnsServers.isEmpty()) {
468                        mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
469                    }
470                }
471
472                // TODO: support search domains from ISAKMP mode config.
473
474                // The final step must be synchronized.
475                synchronized (Vpn.this) {
476                    // Check if the thread is interrupted while we are waiting.
477                    checkpoint(false);
478
479                    // Check if the interface is gone while we are waiting.
480                    if (jniCheckInterface(mConfig.interfaze) == 0) {
481                        throw new IllegalStateException(mConfig.interfaze + " is gone");
482                    }
483
484                    // Now INetworkManagementEventObserver is watching our back.
485                    mInterfaceName = mConfig.interfaze;
486                    mCallback.override(mConfig.dnsServers, mConfig.searchDomains);
487                    showNotification(mConfig, null, null);
488                }
489                Log.i(TAG, "Connected!");
490            } catch (Exception e) {
491                Log.i(TAG, "Abort because " + e.getMessage());
492                exit();
493            }
494        }
495    }
496}
497