| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package android.net; |
| |
| import static android.net.IpSecManager.INVALID_RESOURCE_ID; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.SystemApi; |
| import android.content.Context; |
| import android.os.Binder; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.util.Log; |
| import com.android.internal.util.Preconditions; |
| import dalvik.system.CloseGuard; |
| import java.io.IOException; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.net.InetAddress; |
| |
| /** |
| * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec. |
| * |
| * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout |
| * the lifetime of the underlying transform. If a transform object leaves scope, the underlying |
| * transform may be disabled automatically, with likely undesirable results. |
| * |
| * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array |
| * of traffic or may represent a transport mode transform operating on a Socket or Sockets. |
| * |
| * @hide |
| */ |
| public final class IpSecTransform implements AutoCloseable { |
| private static final String TAG = "IpSecTransform"; |
| |
| /** |
| * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies |
| * to traffic towards the host. |
| */ |
| public static final int DIRECTION_IN = 0; |
| |
| /** |
| * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies |
| * to traffic from the host. |
| */ |
| public static final int DIRECTION_OUT = 1; |
| |
| /** @hide */ |
| @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface TransformDirection {} |
| |
| /** @hide */ |
| private static final int MODE_TUNNEL = 0; |
| |
| /** @hide */ |
| private static final int MODE_TRANSPORT = 1; |
| |
| /** @hide */ |
| public static final int ENCAP_NONE = 0; |
| |
| /** |
| * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad |
| * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP. |
| * |
| * @hide |
| */ |
| public static final int ENCAP_ESPINUDP_NON_IKE = 1; |
| |
| /** |
| * IpSec traffic will be encapsulated within UDP as per <a |
| * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>. |
| * |
| * @hide |
| */ |
| public static final int ENCAP_ESPINUDP = 2; |
| |
| /** @hide */ |
| @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface EncapType {} |
| |
| private IpSecTransform(Context context, IpSecConfig config) { |
| mContext = context; |
| mConfig = config; |
| mResourceId = INVALID_RESOURCE_ID; |
| } |
| |
| private IIpSecService getIpSecService() { |
| IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE); |
| if (b == null) { |
| throw new RemoteException("Failed to connect to IpSecService") |
| .rethrowAsRuntimeException(); |
| } |
| |
| return IIpSecService.Stub.asInterface(b); |
| } |
| |
| private void checkResultStatusAndThrow(int status) |
| throws IOException, IpSecManager.ResourceUnavailableException, |
| IpSecManager.SpiUnavailableException { |
| switch (status) { |
| case IpSecManager.Status.OK: |
| return; |
| // TODO: Pass Error string back from bundle so that errors can be more specific |
| case IpSecManager.Status.RESOURCE_UNAVAILABLE: |
| throw new IpSecManager.ResourceUnavailableException( |
| "Failed to allocate a new IpSecTransform"); |
| case IpSecManager.Status.SPI_UNAVAILABLE: |
| Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved"); |
| // Fall through |
| default: |
| throw new IllegalStateException( |
| "Failed to Create a Transform with status code " + status); |
| } |
| } |
| |
| private IpSecTransform activate() |
| throws IOException, IpSecManager.ResourceUnavailableException, |
| IpSecManager.SpiUnavailableException { |
| synchronized (this) { |
| try { |
| IIpSecService svc = getIpSecService(); |
| IpSecTransformResponse result = |
| svc.createTransportModeTransform(mConfig, new Binder()); |
| int status = result.status; |
| checkResultStatusAndThrow(status); |
| mResourceId = result.resourceId; |
| |
| /* Keepalive will silently fail if not needed by the config; but, if needed and |
| * it fails to start, we need to bail because a transform will not be reliable |
| * to use if keepalive is expected to offload and fails. |
| */ |
| // FIXME: if keepalive fails, we need to fail spectacularly |
| startKeepalive(mContext); |
| Log.d(TAG, "Added Transform with Id " + mResourceId); |
| mCloseGuard.open("build"); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Deactivate an IpSecTransform and free all resources for that transform that are managed by |
| * the system for this Transform. |
| * |
| * <p>Deactivating a transform while it is still applied to any Socket will result in sockets |
| * refusing to send or receive data. This method will silently succeed if the specified |
| * transform has already been removed; thus, it is always safe to attempt cleanup when a |
| * transform is no longer needed. |
| */ |
| public void close() { |
| Log.d(TAG, "Removing Transform with Id " + mResourceId); |
| |
| // Always safe to attempt cleanup |
| if (mResourceId == INVALID_RESOURCE_ID) { |
| mCloseGuard.close(); |
| return; |
| } |
| try { |
| /* Order matters here because the keepalive is best-effort but could fail in some |
| * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we |
| * still want to clear out the transform. |
| */ |
| IIpSecService svc = getIpSecService(); |
| svc.deleteTransportModeTransform(mResourceId); |
| stopKeepalive(); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } finally { |
| mResourceId = INVALID_RESOURCE_ID; |
| mCloseGuard.close(); |
| } |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| if (mCloseGuard != null) { |
| mCloseGuard.warnIfOpen(); |
| } |
| close(); |
| } |
| |
| /* Package */ |
| IpSecConfig getConfig() { |
| return mConfig; |
| } |
| |
| private final IpSecConfig mConfig; |
| private int mResourceId; |
| private final Context mContext; |
| private final CloseGuard mCloseGuard = CloseGuard.get(); |
| private ConnectivityManager.PacketKeepalive mKeepalive; |
| private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; |
| private Object mKeepaliveSyncLock = new Object(); |
| private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback = |
| new ConnectivityManager.PacketKeepaliveCallback() { |
| |
| @Override |
| public void onStarted() { |
| synchronized (mKeepaliveSyncLock) { |
| mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS; |
| mKeepaliveSyncLock.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void onStopped() { |
| synchronized (mKeepaliveSyncLock) { |
| mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; |
| mKeepaliveSyncLock.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void onError(int error) { |
| synchronized (mKeepaliveSyncLock) { |
| mKeepaliveStatus = error; |
| mKeepaliveSyncLock.notifyAll(); |
| } |
| } |
| }; |
| |
| /* Package */ |
| void startKeepalive(Context c) { |
| // FIXME: NO_KEEPALIVE needs to be a constant |
| if (mConfig.getNattKeepaliveInterval() == 0) { |
| return; |
| } |
| |
| ConnectivityManager cm = |
| (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); |
| |
| if (mKeepalive != null) { |
| Log.wtf(TAG, "Keepalive already started for this IpSecTransform."); |
| return; |
| } |
| |
| synchronized (mKeepaliveSyncLock) { |
| mKeepalive = |
| cm.startNattKeepalive( |
| mConfig.getNetwork(), |
| mConfig.getNattKeepaliveInterval(), |
| mKeepaliveCallback, |
| mConfig.getLocalAddress(), |
| 0x1234, /* FIXME: get the real port number again, |
| which we need to retrieve from the provided |
| EncapsulationSocket, and which isn't currently |
| stashed in IpSecConfig */ |
| mConfig.getRemoteAddress()); |
| try { |
| // FIXME: this is still a horrible way to fudge the synchronous callback |
| mKeepaliveSyncLock.wait(2000); |
| } catch (InterruptedException e) { |
| } |
| } |
| if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) { |
| throw new UnsupportedOperationException("Packet Keepalive cannot be started"); |
| } |
| } |
| |
| /* Package */ |
| int getResourceId() { |
| return mResourceId; |
| } |
| |
| /* Package */ |
| void stopKeepalive() { |
| if (mKeepalive == null) { |
| return; |
| } |
| mKeepalive.stop(); |
| synchronized (mKeepaliveSyncLock) { |
| if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) { |
| try { |
| mKeepaliveSyncLock.wait(2000); |
| } catch (InterruptedException e) { |
| } |
| } |
| } |
| } |
| |
| /** |
| * Builder object to facilitate the creation of IpSecTransform objects. |
| * |
| * <p>Apply additional properties to the transform and then call a build() method to return an |
| * IpSecTransform object. |
| * |
| * @see Builder#buildTransportModeTransform(InetAddress) |
| */ |
| public static class Builder { |
| private Context mContext; |
| private IpSecConfig mConfig; |
| |
| /** |
| * Add an encryption algorithm to the transform for the given direction. |
| * |
| * <p>If encryption is set for a given direction without also providing an SPI for that |
| * direction, creation of an IpSecTransform will fail upon calling a build() method. |
| * |
| * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} |
| * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. |
| */ |
| public IpSecTransform.Builder setEncryption( |
| @TransformDirection int direction, IpSecAlgorithm algo) { |
| mConfig.flow[direction].encryption = algo; |
| return this; |
| } |
| |
| /** |
| * Add an authentication/integrity algorithm to the transform. |
| * |
| * <p>If authentication is set for a given direction without also providing an SPI for that |
| * direction, creation of an IpSecTransform will fail upon calling a build() method. |
| * |
| * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} |
| * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. |
| */ |
| public IpSecTransform.Builder setAuthentication( |
| @TransformDirection int direction, IpSecAlgorithm algo) { |
| mConfig.flow[direction].authentication = algo; |
| return this; |
| } |
| |
| /** |
| * Set the SPI, which uniquely identifies a particular IPsec session from others. Because |
| * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a |
| * given destination address. |
| * |
| * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as |
| * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int, |
| * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being |
| * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}. |
| * |
| * <p>Unless an SPI is set for a given direction, traffic in that direction will be |
| * sent/received without any IPsec applied. |
| * |
| * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} |
| * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed |
| * traffic |
| */ |
| public IpSecTransform.Builder setSpi( |
| @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { |
| // TODO: convert to using the resource Id of the SPI. Then build() can validate |
| // the owner in the IpSecService |
| mConfig.flow[direction].spiResourceId = spi.getResourceId(); |
| return this; |
| } |
| |
| /** |
| * Specify the network on which this transform will emit its traffic; (otherwise it will |
| * emit on the default network). |
| * |
| * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in |
| * tunnel mode. |
| * |
| * @hide |
| */ |
| @SystemApi |
| public IpSecTransform.Builder setUnderlyingNetwork(Network net) { |
| mConfig.network = net; |
| return this; |
| } |
| |
| /** |
| * Add UDP encapsulation to an IPv4 transform |
| * |
| * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for |
| * details on how UDP should be applied to IPsec. |
| * |
| * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and |
| * receiving encapsulating traffic. |
| * @param remotePort the UDP port number of the remote that will send and receive |
| * encapsulated traffic. In the case of IKE, this is likely port 4500. |
| */ |
| public IpSecTransform.Builder setIpv4Encapsulation( |
| IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { |
| // TODO: check encap type is valid. |
| mConfig.encapType = ENCAP_ESPINUDP; |
| mConfig.encapLocalPortResourceId = localSocket.getResourceId(); |
| mConfig.encapRemotePort = remotePort; |
| return this; |
| } |
| |
| // TODO: Decrease the minimum keepalive to maybe 10? |
| // TODO: Probably a better exception to throw for NATTKeepalive failure |
| // TODO: Specify the needed NATT keepalive permission. |
| /** |
| * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded |
| * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot |
| * be activated, then the transform will fail to activate and throw an IOException. |
| * |
| * @param intervalSeconds the maximum number of seconds between keepalive packets, no less |
| * than 20s and no more than 3600s. |
| * @hide |
| */ |
| @SystemApi |
| public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) { |
| mConfig.nattKeepaliveInterval = intervalSeconds; |
| return this; |
| } |
| |
| /** |
| * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform. |
| * Some parameters have interdependencies that are checked at build time. If a well-formed |
| * transform cannot be created from the supplied parameters, this method will throw an |
| * Exception. |
| * |
| * <p>Upon a successful return from this call, the provided IpSecTransform will be active |
| * and may be applied to sockets. If too many IpSecTransform objects are active for a given |
| * user this operation will fail and throw ResourceUnavailableException. To avoid these |
| * exceptions, unused Transform objects must be cleaned up by calling {@link |
| * IpSecTransform#close()} when they are no longer needed. |
| * |
| * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this |
| * socket will cause the transform to be applied. |
| * <p>Note that an active transform will not impact any network traffic until it has |
| * been applied to one or more Sockets. Calling this method is a necessary precondition |
| * for applying it to a socket, but is not sufficient to actually apply IPsec. |
| * @throws IllegalArgumentException indicating that a particular combination of transform |
| * properties is invalid. |
| * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms |
| * may be allocated |
| * @throws SpiUnavailableException if the SPI collides with an existing transform |
| * (unlikely). |
| * @throws ResourceUnavailableException if the current user currently has exceeded the |
| * number of allowed active transforms. |
| */ |
| public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) |
| throws IpSecManager.ResourceUnavailableException, |
| IpSecManager.SpiUnavailableException, IOException { |
| //FIXME: argument validation here |
| //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation"); |
| mConfig.mode = MODE_TRANSPORT; |
| mConfig.remoteAddress = remoteAddress; |
| return new IpSecTransform(mContext, mConfig).activate(); |
| } |
| |
| /** |
| * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some |
| * parameters have interdependencies that are checked at build time. |
| * |
| * @param localAddress the {@link InetAddress} that provides the local endpoint for this |
| * IPsec tunnel. This is almost certainly an address belonging to the {@link Network} |
| * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}. |
| * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this |
| * IPsec tunnel. |
| * @throws IllegalArgumentException indicating that a particular combination of transform |
| * properties is invalid. |
| * @hide |
| */ |
| public IpSecTransform buildTunnelModeTransform( |
| InetAddress localAddress, InetAddress remoteAddress) { |
| //FIXME: argument validation here |
| //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation"); |
| mConfig.localAddress = localAddress; |
| mConfig.remoteAddress = remoteAddress; |
| mConfig.mode = MODE_TUNNEL; |
| return new IpSecTransform(mContext, mConfig); |
| } |
| |
| /** |
| * Create a new IpSecTransform.Builder to construct an IpSecTransform |
| * |
| * @param context current Context |
| */ |
| public Builder(@NonNull Context context) { |
| Preconditions.checkNotNull(context); |
| mContext = context; |
| mConfig = new IpSecConfig(); |
| } |
| } |
| } |