| /* |
| * 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 com.android.internal.util.Preconditions.checkNotNull; |
| |
| import android.annotation.NonNull; |
| import android.annotation.SystemService; |
| import android.content.Context; |
| import android.os.Binder; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.util.AndroidException; |
| import android.util.Log; |
| import dalvik.system.CloseGuard; |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.net.DatagramSocket; |
| import java.net.InetAddress; |
| import java.net.Socket; |
| |
| /** |
| * This class contains methods for managing IPsec sessions, which will perform kernel-space |
| * encryption and decryption of socket or Network traffic. |
| * |
| * @hide |
| */ |
| @SystemService(Context.IPSEC_SERVICE) |
| public final class IpSecManager { |
| private static final String TAG = "IpSecManager"; |
| |
| /** |
| * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. |
| * |
| * <p>No IPsec packet may contain an SPI of 0. |
| */ |
| public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; |
| |
| /** @hide */ |
| public interface Status { |
| public static final int OK = 0; |
| public static final int RESOURCE_UNAVAILABLE = 1; |
| public static final int SPI_UNAVAILABLE = 2; |
| } |
| |
| /** @hide */ |
| public static final int INVALID_RESOURCE_ID = 0; |
| |
| /** |
| * Indicates that the combination of remote InetAddress and SPI was non-unique for a given |
| * request. If encountered, selection of a new SPI is required before a transform may be |
| * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random |
| * or reserved using reserveSecurityParameterIndex. |
| */ |
| public static final class SpiUnavailableException extends AndroidException { |
| private final int mSpi; |
| |
| /** |
| * Construct an exception indicating that a transform with the given SPI is already in use |
| * or otherwise unavailable. |
| * |
| * @param msg Description indicating the colliding SPI |
| * @param spi the SPI that could not be used due to a collision |
| */ |
| SpiUnavailableException(String msg, int spi) { |
| super(msg + "(spi: " + spi + ")"); |
| mSpi = spi; |
| } |
| |
| /** Retrieve the SPI that caused a collision */ |
| public int getSpi() { |
| return mSpi; |
| } |
| } |
| |
| /** |
| * Indicates that the requested system resource for IPsec, such as a socket or other system |
| * resource is unavailable. If this exception is thrown, try releasing allocated objects of the |
| * type requested. |
| */ |
| public static final class ResourceUnavailableException extends AndroidException { |
| |
| ResourceUnavailableException(String msg) { |
| super(msg); |
| } |
| } |
| |
| private final IIpSecService mService; |
| |
| public static final class SecurityParameterIndex implements AutoCloseable { |
| private final IIpSecService mService; |
| private final InetAddress mRemoteAddress; |
| private final CloseGuard mCloseGuard = CloseGuard.get(); |
| private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; |
| private int mResourceId; |
| |
| /** Return the underlying SPI held by this object */ |
| public int getSpi() { |
| return mSpi; |
| } |
| |
| /** |
| * Release an SPI that was previously reserved. |
| * |
| * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is |
| * applied to an IpSecTransform, it will become unusable for future transforms but should |
| * still be closed to ensure system resources are released. |
| */ |
| @Override |
| public void close() { |
| try { |
| mService.releaseSecurityParameterIndex(mResourceId); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| mCloseGuard.close(); |
| } |
| |
| @Override |
| protected void finalize() { |
| if (mCloseGuard != null) { |
| mCloseGuard.warnIfOpen(); |
| } |
| |
| close(); |
| } |
| |
| private SecurityParameterIndex( |
| @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi) |
| throws ResourceUnavailableException, SpiUnavailableException { |
| mService = service; |
| mRemoteAddress = remoteAddress; |
| try { |
| IpSecSpiResponse result = |
| mService.reserveSecurityParameterIndex( |
| direction, remoteAddress.getHostAddress(), spi, new Binder()); |
| |
| if (result == null) { |
| throw new NullPointerException("Received null response from IpSecService"); |
| } |
| |
| int status = result.status; |
| switch (status) { |
| case Status.OK: |
| break; |
| case Status.RESOURCE_UNAVAILABLE: |
| throw new ResourceUnavailableException( |
| "No more SPIs may be allocated by this requester."); |
| case Status.SPI_UNAVAILABLE: |
| throw new SpiUnavailableException("Requested SPI is unavailable", spi); |
| default: |
| throw new RuntimeException( |
| "Unknown status returned by IpSecService: " + status); |
| } |
| mSpi = result.spi; |
| mResourceId = result.resourceId; |
| |
| if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) { |
| throw new RuntimeException("Invalid SPI returned by IpSecService: " + status); |
| } |
| |
| if (mResourceId == INVALID_RESOURCE_ID) { |
| throw new RuntimeException( |
| "Invalid Resource ID returned by IpSecService: " + status); |
| } |
| |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| mCloseGuard.open("open"); |
| } |
| |
| /** @hide */ |
| int getResourceId() { |
| return mResourceId; |
| } |
| } |
| |
| /** |
| * Reserve an SPI for traffic bound towards the specified remote address. |
| * |
| * <p>If successful, this SPI is guaranteed available until released by a call to {@link |
| * SecurityParameterIndex#close()}. |
| * |
| * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} |
| * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. |
| * @return the reserved SecurityParameterIndex |
| * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated |
| * for this user |
| * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved |
| */ |
| public SecurityParameterIndex reserveSecurityParameterIndex( |
| int direction, InetAddress remoteAddress) throws ResourceUnavailableException { |
| try { |
| return new SecurityParameterIndex( |
| mService, |
| direction, |
| remoteAddress, |
| IpSecManager.INVALID_SECURITY_PARAMETER_INDEX); |
| } catch (SpiUnavailableException unlikely) { |
| throw new ResourceUnavailableException("No SPIs available"); |
| } |
| } |
| |
| /** |
| * Reserve an SPI for traffic bound towards the specified remote address. |
| * |
| * <p>If successful, this SPI is guaranteed available until released by a call to {@link |
| * SecurityParameterIndex#close()}. |
| * |
| * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} |
| * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. |
| * @param requestedSpi the requested SPI, or '0' to allocate a random SPI. |
| * @return the reserved SecurityParameterIndex |
| * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated |
| * for this user |
| */ |
| public SecurityParameterIndex reserveSecurityParameterIndex( |
| int direction, InetAddress remoteAddress, int requestedSpi) |
| throws SpiUnavailableException, ResourceUnavailableException { |
| if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) { |
| throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI"); |
| } |
| return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi); |
| } |
| |
| /** |
| * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec |
| * encapsulation of the traffic flowing between the socket and the remote InetAddress of that |
| * transform. For security reasons, attempts to send traffic to any IP address other than the |
| * address associated with that transform will throw an IOException. In addition, if the |
| * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to |
| * send() or receive() until the transform is removed from the socket by calling {@link |
| * #removeTransportModeTransform(Socket, IpSecTransform)}; |
| * |
| * @param socket a stream socket |
| * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. |
| * @hide |
| */ |
| public void applyTransportModeTransform(Socket socket, IpSecTransform transform) |
| throws IOException { |
| try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { |
| applyTransportModeTransform(pfd, transform); |
| } |
| } |
| |
| /** |
| * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec |
| * encapsulation of the traffic flowing between the socket and the remote InetAddress of that |
| * transform. For security reasons, attempts to send traffic to any IP address other than the |
| * address associated with that transform will throw an IOException. In addition, if the |
| * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to |
| * send() or receive() until the transform is removed from the socket by calling {@link |
| * #removeTransportModeTransform(DatagramSocket, IpSecTransform)}; |
| * |
| * @param socket a datagram socket |
| * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. |
| * @hide |
| */ |
| public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform) |
| throws IOException { |
| try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { |
| applyTransportModeTransform(pfd, transform); |
| } |
| } |
| |
| /** |
| * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec |
| * encapsulation of the traffic flowing between the socket and the remote InetAddress of that |
| * transform. For security reasons, attempts to send traffic to any IP address other than the |
| * address associated with that transform will throw an IOException. In addition, if the |
| * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to |
| * send() or receive() until the transform is removed from the socket by calling {@link |
| * #removeTransportModeTransform(FileDescriptor, IpSecTransform)}; |
| * |
| * @param socket a socket file descriptor |
| * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform. |
| */ |
| public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform) |
| throws IOException { |
| // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor() |
| // constructor takes control and closes the user's FD when we exit the method |
| // This is behaviorally the same as the other versions, but the PFD constructor does not |
| // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup(). |
| try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { |
| applyTransportModeTransform(pfd, transform); |
| } |
| } |
| |
| /* Call down to activate a transform */ |
| private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { |
| try { |
| mService.applyTransportModeTransform(pfd, transform.getResourceId()); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to |
| * and from that network's interface with IPsec (applies an outer IP header and IPsec Header to |
| * all traffic, and expects an additional IP header and IPsec Header on all inbound traffic). |
| * Applications should probably not use this API directly. Instead, they should use {@link |
| * VpnService} to provide VPN capability in a more generic fashion. |
| * |
| * @param net a {@link Network} that will be tunneled via IP Sec. |
| * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform. |
| * @hide |
| */ |
| public void applyTunnelModeTransform(Network net, IpSecTransform transform) {} |
| |
| /** |
| * Remove a transform from a given stream socket. Once removed, traffic on the socket will not |
| * be encypted. This allows sockets that have been used for IPsec to be reclaimed for |
| * communication in the clear in the event socket reuse is desired. This operation will succeed |
| * regardless of the underlying state of a transform. If a transform is removed, communication |
| * on all sockets to which that transform was applied will fail until this method is called. |
| * |
| * @param socket a socket that previously had a transform applied to it. |
| * @param transform the IPsec Transform that was previously applied to the given socket |
| * @hide |
| */ |
| public void removeTransportModeTransform(Socket socket, IpSecTransform transform) |
| throws IOException { |
| try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { |
| removeTransportModeTransform(pfd, transform); |
| } |
| } |
| |
| /** |
| * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not |
| * be encypted. This allows sockets that have been used for IPsec to be reclaimed for |
| * communication in the clear in the event socket reuse is desired. This operation will succeed |
| * regardless of the underlying state of a transform. If a transform is removed, communication |
| * on all sockets to which that transform was applied will fail until this method is called. |
| * |
| * @param socket a socket that previously had a transform applied to it. |
| * @param transform the IPsec Transform that was previously applied to the given socket |
| * @hide |
| */ |
| public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) |
| throws IOException { |
| try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { |
| removeTransportModeTransform(pfd, transform); |
| } |
| } |
| |
| /** |
| * Remove a transform from a given stream socket. Once removed, traffic on the socket will not |
| * be encypted. This allows sockets that have been used for IPsec to be reclaimed for |
| * communication in the clear in the event socket reuse is desired. This operation will succeed |
| * regardless of the underlying state of a transform. If a transform is removed, communication |
| * on all sockets to which that transform was applied will fail until this method is called. |
| * |
| * @param socket a socket file descriptor that previously had a transform applied to it. |
| * @param transform the IPsec Transform that was previously applied to the given socket |
| */ |
| public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) |
| throws IOException { |
| try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { |
| removeTransportModeTransform(pfd, transform); |
| } |
| } |
| |
| /* Call down to activate a transform */ |
| private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { |
| try { |
| mService.removeTransportModeTransform(pfd, transform.getResourceId()); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of |
| * cleanup if a tunneled Network experiences a change in default route. The Network will drop |
| * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is |
| * lost, all traffic will drop. |
| * |
| * @param net a network that currently has transform applied to it. |
| * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given |
| * network |
| * @hide |
| */ |
| public void removeTunnelModeTransform(Network net, IpSecTransform transform) {} |
| |
| /** |
| * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for |
| * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic. |
| * |
| * <p>The socket provided by this class cannot be re-bound or closed via the inner |
| * FileDescriptor. Instead, disposing of this socket requires a call to close(). |
| */ |
| public static final class UdpEncapsulationSocket implements AutoCloseable { |
| private final ParcelFileDescriptor mPfd; |
| private final IIpSecService mService; |
| private final int mResourceId; |
| private final int mPort; |
| private final CloseGuard mCloseGuard = CloseGuard.get(); |
| |
| private UdpEncapsulationSocket(@NonNull IIpSecService service, int port) |
| throws ResourceUnavailableException, IOException { |
| mService = service; |
| try { |
| IpSecUdpEncapResponse result = |
| mService.openUdpEncapsulationSocket(port, new Binder()); |
| switch (result.status) { |
| case Status.OK: |
| break; |
| case Status.RESOURCE_UNAVAILABLE: |
| throw new ResourceUnavailableException( |
| "No more Sockets may be allocated by this requester."); |
| default: |
| throw new RuntimeException( |
| "Unknown status returned by IpSecService: " + result.status); |
| } |
| mResourceId = result.resourceId; |
| mPort = result.port; |
| mPfd = result.fileDescriptor; |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| mCloseGuard.open("constructor"); |
| } |
| |
| /** Access the inner UDP Encapsulation Socket */ |
| public FileDescriptor getSocket() { |
| if (mPfd == null) { |
| return null; |
| } |
| return mPfd.getFileDescriptor(); |
| } |
| |
| /** Retrieve the port number of the inner encapsulation socket */ |
| public int getPort() { |
| return mPort; |
| } |
| |
| @Override |
| /** |
| * Release the resources that have been reserved for this Socket. |
| * |
| * <p>This method closes the underlying socket, reducing a user's allocated sockets in the |
| * system. This must be done as part of cleanup following use of a socket. Failure to do so |
| * will cause the socket to count against a total allocation limit for IpSec and eventually |
| * fail due to resource limits. |
| * |
| * @param fd a file descriptor previously returned as a UDP Encapsulation socket. |
| */ |
| public void close() throws IOException { |
| try { |
| mService.closeUdpEncapsulationSocket(mResourceId); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| |
| try { |
| mPfd.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "Failed to close UDP Encapsulation Socket with Port= " + mPort); |
| throw e; |
| } |
| mCloseGuard.close(); |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| if (mCloseGuard != null) { |
| mCloseGuard.warnIfOpen(); |
| } |
| close(); |
| } |
| |
| /** @hide */ |
| int getResourceId() { |
| return mResourceId; |
| } |
| }; |
| |
| /** |
| * Open a socket that is bound to a free UDP port on the system. |
| * |
| * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by |
| * the caller. This provides safe access to a socket on a port that can later be used as a UDP |
| * Encapsulation port. |
| * |
| * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the |
| * socket port. Explicitly opening this port is only necessary if communication is desired on |
| * that port. |
| * |
| * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this |
| * method will bind to the specified port or fail. To retrieve the port number, call {@link |
| * android.system.Os#getsockname(FileDescriptor)}. |
| * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime |
| * of the object. |
| */ |
| // Returning a socket in this fashion that has been created and bound by the system |
| // is the only safe way to ensure that a socket is both accessible to the user and |
| // safely usable for Encapsulation without allowing a user to possibly unbind from/close |
| // the port, which could potentially impact the traffic of the next user who binds to that |
| // socket. |
| public UdpEncapsulationSocket openUdpEncapsulationSocket(int port) |
| throws IOException, ResourceUnavailableException { |
| /* |
| * Most range checking is done in the service, but this version of the constructor expects |
| * a valid port number, and zero cannot be checked after being passed to the service. |
| */ |
| if (port == 0) { |
| throw new IllegalArgumentException("Specified port must be a valid port number!"); |
| } |
| return new UdpEncapsulationSocket(mService, port); |
| } |
| |
| /** |
| * Open a socket that is bound to a port selected by the system. |
| * |
| * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by |
| * the caller. This provides safe access to a socket on a port that can later be used as a UDP |
| * Encapsulation port. |
| * |
| * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the |
| * socket port. Explicitly opening this port is only necessary if communication is desired on |
| * that port. |
| * |
| * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port |
| */ |
| // Returning a socket in this fashion that has been created and bound by the system |
| // is the only safe way to ensure that a socket is both accessible to the user and |
| // safely usable for Encapsulation without allowing a user to possibly unbind from/close |
| // the port, which could potentially impact the traffic of the next user who binds to that |
| // socket. |
| public UdpEncapsulationSocket openUdpEncapsulationSocket() |
| throws IOException, ResourceUnavailableException { |
| return new UdpEncapsulationSocket(mService, 0); |
| } |
| |
| /** |
| * Retrieve an instance of an IpSecManager within you application context |
| * |
| * @param context the application context for this manager |
| * @hide |
| */ |
| public IpSecManager(IIpSecService service) { |
| mService = checkNotNull(service, "missing service"); |
| } |
| } |