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