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