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