1 /* 2 * Copyright (C) 2017 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 package android.net; 17 18 import static android.net.IpSecManager.INVALID_RESOURCE_ID; 19 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.SystemApi; 23 import android.content.Context; 24 import android.os.Binder; 25 import android.os.IBinder; 26 import android.os.RemoteException; 27 import android.os.ServiceManager; 28 import android.util.Log; 29 import com.android.internal.util.Preconditions; 30 import dalvik.system.CloseGuard; 31 import java.io.IOException; 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.net.InetAddress; 35 36 /** 37 * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec. 38 * 39 * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout 40 * the lifetime of the underlying transform. If a transform object leaves scope, the underlying 41 * transform may be disabled automatically, with likely undesirable results. 42 * 43 * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array 44 * of traffic or may represent a transport mode transform operating on a Socket or Sockets. 45 * 46 * @hide 47 */ 48 public final class IpSecTransform implements AutoCloseable { 49 private static final String TAG = "IpSecTransform"; 50 51 /** 52 * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies 53 * to traffic towards the host. 54 */ 55 public static final int DIRECTION_IN = 0; 56 57 /** 58 * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies 59 * to traffic from the host. 60 */ 61 public static final int DIRECTION_OUT = 1; 62 63 /** @hide */ 64 @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) 65 @Retention(RetentionPolicy.SOURCE) 66 public @interface TransformDirection {} 67 68 /** @hide */ 69 private static final int MODE_TUNNEL = 0; 70 71 /** @hide */ 72 private static final int MODE_TRANSPORT = 1; 73 74 /** @hide */ 75 public static final int ENCAP_NONE = 0; 76 77 /** 78 * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad 79 * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP. 80 * 81 * @hide 82 */ 83 public static final int ENCAP_ESPINUDP_NON_IKE = 1; 84 85 /** 86 * IpSec traffic will be encapsulated within UDP as per <a 87 * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>. 88 * 89 * @hide 90 */ 91 public static final int ENCAP_ESPINUDP = 2; 92 93 /** @hide */ 94 @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE}) 95 @Retention(RetentionPolicy.SOURCE) 96 public @interface EncapType {} 97 98 private IpSecTransform(Context context, IpSecConfig config) { 99 mContext = context; 100 mConfig = config; 101 mResourceId = INVALID_RESOURCE_ID; 102 } 103 104 private IIpSecService getIpSecService() { 105 IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE); 106 if (b == null) { 107 throw new RemoteException("Failed to connect to IpSecService") 108 .rethrowAsRuntimeException(); 109 } 110 111 return IIpSecService.Stub.asInterface(b); 112 } 113 114 private void checkResultStatusAndThrow(int status) 115 throws IOException, IpSecManager.ResourceUnavailableException, 116 IpSecManager.SpiUnavailableException { 117 switch (status) { 118 case IpSecManager.Status.OK: 119 return; 120 // TODO: Pass Error string back from bundle so that errors can be more specific 121 case IpSecManager.Status.RESOURCE_UNAVAILABLE: 122 throw new IpSecManager.ResourceUnavailableException( 123 "Failed to allocate a new IpSecTransform"); 124 case IpSecManager.Status.SPI_UNAVAILABLE: 125 Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved"); 126 // Fall through 127 default: 128 throw new IllegalStateException( 129 "Failed to Create a Transform with status code " + status); 130 } 131 } 132 133 private IpSecTransform activate() 134 throws IOException, IpSecManager.ResourceUnavailableException, 135 IpSecManager.SpiUnavailableException { 136 synchronized (this) { 137 try { 138 IIpSecService svc = getIpSecService(); 139 IpSecTransformResponse result = 140 svc.createTransportModeTransform(mConfig, new Binder()); 141 int status = result.status; 142 checkResultStatusAndThrow(status); 143 mResourceId = result.resourceId; 144 145 /* Keepalive will silently fail if not needed by the config; but, if needed and 146 * it fails to start, we need to bail because a transform will not be reliable 147 * to use if keepalive is expected to offload and fails. 148 */ 149 // FIXME: if keepalive fails, we need to fail spectacularly 150 startKeepalive(mContext); 151 Log.d(TAG, "Added Transform with Id " + mResourceId); 152 mCloseGuard.open("build"); 153 } catch (RemoteException e) { 154 throw e.rethrowAsRuntimeException(); 155 } 156 } 157 158 return this; 159 } 160 161 /** 162 * Deactivate an IpSecTransform and free all resources for that transform that are managed by 163 * the system for this Transform. 164 * 165 * <p>Deactivating a transform while it is still applied to any Socket will result in sockets 166 * refusing to send or receive data. This method will silently succeed if the specified 167 * transform has already been removed; thus, it is always safe to attempt cleanup when a 168 * transform is no longer needed. 169 */ 170 public void close() { 171 Log.d(TAG, "Removing Transform with Id " + mResourceId); 172 173 // Always safe to attempt cleanup 174 if (mResourceId == INVALID_RESOURCE_ID) { 175 mCloseGuard.close(); 176 return; 177 } 178 try { 179 /* Order matters here because the keepalive is best-effort but could fail in some 180 * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we 181 * still want to clear out the transform. 182 */ 183 IIpSecService svc = getIpSecService(); 184 svc.deleteTransportModeTransform(mResourceId); 185 stopKeepalive(); 186 } catch (RemoteException e) { 187 throw e.rethrowAsRuntimeException(); 188 } finally { 189 mResourceId = INVALID_RESOURCE_ID; 190 mCloseGuard.close(); 191 } 192 } 193 194 @Override 195 protected void finalize() throws Throwable { 196 if (mCloseGuard != null) { 197 mCloseGuard.warnIfOpen(); 198 } 199 close(); 200 } 201 202 /* Package */ 203 IpSecConfig getConfig() { 204 return mConfig; 205 } 206 207 private final IpSecConfig mConfig; 208 private int mResourceId; 209 private final Context mContext; 210 private final CloseGuard mCloseGuard = CloseGuard.get(); 211 private ConnectivityManager.PacketKeepalive mKeepalive; 212 private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; 213 private Object mKeepaliveSyncLock = new Object(); 214 private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback = 215 new ConnectivityManager.PacketKeepaliveCallback() { 216 217 @Override 218 public void onStarted() { 219 synchronized (mKeepaliveSyncLock) { 220 mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS; 221 mKeepaliveSyncLock.notifyAll(); 222 } 223 } 224 225 @Override 226 public void onStopped() { 227 synchronized (mKeepaliveSyncLock) { 228 mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; 229 mKeepaliveSyncLock.notifyAll(); 230 } 231 } 232 233 @Override 234 public void onError(int error) { 235 synchronized (mKeepaliveSyncLock) { 236 mKeepaliveStatus = error; 237 mKeepaliveSyncLock.notifyAll(); 238 } 239 } 240 }; 241 242 /* Package */ 243 void startKeepalive(Context c) { 244 // FIXME: NO_KEEPALIVE needs to be a constant 245 if (mConfig.getNattKeepaliveInterval() == 0) { 246 return; 247 } 248 249 ConnectivityManager cm = 250 (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); 251 252 if (mKeepalive != null) { 253 Log.wtf(TAG, "Keepalive already started for this IpSecTransform."); 254 return; 255 } 256 257 synchronized (mKeepaliveSyncLock) { 258 mKeepalive = 259 cm.startNattKeepalive( 260 mConfig.getNetwork(), 261 mConfig.getNattKeepaliveInterval(), 262 mKeepaliveCallback, 263 mConfig.getLocalAddress(), 264 0x1234, /* FIXME: get the real port number again, 265 which we need to retrieve from the provided 266 EncapsulationSocket, and which isn't currently 267 stashed in IpSecConfig */ 268 mConfig.getRemoteAddress()); 269 try { 270 // FIXME: this is still a horrible way to fudge the synchronous callback 271 mKeepaliveSyncLock.wait(2000); 272 } catch (InterruptedException e) { 273 } 274 } 275 if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) { 276 throw new UnsupportedOperationException("Packet Keepalive cannot be started"); 277 } 278 } 279 280 /* Package */ 281 int getResourceId() { 282 return mResourceId; 283 } 284 285 /* Package */ 286 void stopKeepalive() { 287 if (mKeepalive == null) { 288 return; 289 } 290 mKeepalive.stop(); 291 synchronized (mKeepaliveSyncLock) { 292 if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) { 293 try { 294 mKeepaliveSyncLock.wait(2000); 295 } catch (InterruptedException e) { 296 } 297 } 298 } 299 } 300 301 /** 302 * Builder object to facilitate the creation of IpSecTransform objects. 303 * 304 * <p>Apply additional properties to the transform and then call a build() method to return an 305 * IpSecTransform object. 306 * 307 * @see Builder#buildTransportModeTransform(InetAddress) 308 */ 309 public static class Builder { 310 private Context mContext; 311 private IpSecConfig mConfig; 312 313 /** 314 * Add an encryption algorithm to the transform for the given direction. 315 * 316 * <p>If encryption is set for a given direction without also providing an SPI for that 317 * direction, creation of an IpSecTransform will fail upon calling a build() method. 318 * 319 * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} 320 * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. 321 */ 322 public IpSecTransform.Builder setEncryption( 323 @TransformDirection int direction, IpSecAlgorithm algo) { 324 mConfig.flow[direction].encryption = algo; 325 return this; 326 } 327 328 /** 329 * Add an authentication/integrity algorithm to the transform. 330 * 331 * <p>If authentication is set for a given direction without also providing an SPI for that 332 * direction, creation of an IpSecTransform will fail upon calling a build() method. 333 * 334 * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} 335 * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. 336 */ 337 public IpSecTransform.Builder setAuthentication( 338 @TransformDirection int direction, IpSecAlgorithm algo) { 339 mConfig.flow[direction].authentication = algo; 340 return this; 341 } 342 343 /** 344 * Set the SPI, which uniquely identifies a particular IPsec session from others. Because 345 * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a 346 * given destination address. 347 * 348 * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as 349 * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int, 350 * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being 351 * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}. 352 * 353 * <p>Unless an SPI is set for a given direction, traffic in that direction will be 354 * sent/received without any IPsec applied. 355 * 356 * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} 357 * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed 358 * traffic 359 */ 360 public IpSecTransform.Builder setSpi( 361 @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { 362 // TODO: convert to using the resource Id of the SPI. Then build() can validate 363 // the owner in the IpSecService 364 mConfig.flow[direction].spiResourceId = spi.getResourceId(); 365 return this; 366 } 367 368 /** 369 * Specify the network on which this transform will emit its traffic; (otherwise it will 370 * emit on the default network). 371 * 372 * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in 373 * tunnel mode. 374 * 375 * @hide 376 */ 377 @SystemApi 378 public IpSecTransform.Builder setUnderlyingNetwork(Network net) { 379 mConfig.network = net; 380 return this; 381 } 382 383 /** 384 * Add UDP encapsulation to an IPv4 transform 385 * 386 * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for 387 * details on how UDP should be applied to IPsec. 388 * 389 * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and 390 * receiving encapsulating traffic. 391 * @param remotePort the UDP port number of the remote that will send and receive 392 * encapsulated traffic. In the case of IKE, this is likely port 4500. 393 */ 394 public IpSecTransform.Builder setIpv4Encapsulation( 395 IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { 396 // TODO: check encap type is valid. 397 mConfig.encapType = ENCAP_ESPINUDP; 398 mConfig.encapLocalPortResourceId = localSocket.getResourceId(); 399 mConfig.encapRemotePort = remotePort; 400 return this; 401 } 402 403 // TODO: Decrease the minimum keepalive to maybe 10? 404 // TODO: Probably a better exception to throw for NATTKeepalive failure 405 // TODO: Specify the needed NATT keepalive permission. 406 /** 407 * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded 408 * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot 409 * be activated, then the transform will fail to activate and throw an IOException. 410 * 411 * @param intervalSeconds the maximum number of seconds between keepalive packets, no less 412 * than 20s and no more than 3600s. 413 * @hide 414 */ 415 @SystemApi 416 public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) { 417 mConfig.nattKeepaliveInterval = intervalSeconds; 418 return this; 419 } 420 421 /** 422 * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform. 423 * Some parameters have interdependencies that are checked at build time. If a well-formed 424 * transform cannot be created from the supplied parameters, this method will throw an 425 * Exception. 426 * 427 * <p>Upon a successful return from this call, the provided IpSecTransform will be active 428 * and may be applied to sockets. If too many IpSecTransform objects are active for a given 429 * user this operation will fail and throw ResourceUnavailableException. To avoid these 430 * exceptions, unused Transform objects must be cleaned up by calling {@link 431 * IpSecTransform#close()} when they are no longer needed. 432 * 433 * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this 434 * socket will cause the transform to be applied. 435 * <p>Note that an active transform will not impact any network traffic until it has 436 * been applied to one or more Sockets. Calling this method is a necessary precondition 437 * for applying it to a socket, but is not sufficient to actually apply IPsec. 438 * @throws IllegalArgumentException indicating that a particular combination of transform 439 * properties is invalid. 440 * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms 441 * may be allocated 442 * @throws SpiUnavailableException if the SPI collides with an existing transform 443 * (unlikely). 444 * @throws ResourceUnavailableException if the current user currently has exceeded the 445 * number of allowed active transforms. 446 */ 447 public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) 448 throws IpSecManager.ResourceUnavailableException, 449 IpSecManager.SpiUnavailableException, IOException { 450 //FIXME: argument validation here 451 //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation"); 452 mConfig.mode = MODE_TRANSPORT; 453 mConfig.remoteAddress = remoteAddress; 454 return new IpSecTransform(mContext, mConfig).activate(); 455 } 456 457 /** 458 * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some 459 * parameters have interdependencies that are checked at build time. 460 * 461 * @param localAddress the {@link InetAddress} that provides the local endpoint for this 462 * IPsec tunnel. This is almost certainly an address belonging to the {@link Network} 463 * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}. 464 * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this 465 * IPsec tunnel. 466 * @throws IllegalArgumentException indicating that a particular combination of transform 467 * properties is invalid. 468 * @hide 469 */ 470 public IpSecTransform buildTunnelModeTransform( 471 InetAddress localAddress, InetAddress remoteAddress) { 472 //FIXME: argument validation here 473 //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation"); 474 mConfig.localAddress = localAddress; 475 mConfig.remoteAddress = remoteAddress; 476 mConfig.mode = MODE_TUNNEL; 477 return new IpSecTransform(mContext, mConfig); 478 } 479 480 /** 481 * Create a new IpSecTransform.Builder to construct an IpSecTransform 482 * 483 * @param context current Context 484 */ 485 public Builder(@NonNull Context context) { 486 Preconditions.checkNotNull(context); 487 mContext = context; 488 mConfig = new IpSecConfig(); 489 } 490 } 491 } 492