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