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