/* * Copyright (C) 2019 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.ipsec.ike; import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_NONE; import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_UDP; import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV4; import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV6; import static android.net.ipsec.ike.IkeSessionParams.IKE_NATT_KEEPALIVE_DELAY_SEC_MAX; import static android.net.ipsec.ike.IkeSessionParams.IKE_NATT_KEEPALIVE_DELAY_SEC_MIN; import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.content.Context; import android.content.pm.PackageManager; import android.net.IpSecManager; import android.net.Network; import android.net.ipsec.ike.exceptions.IkeException; import android.os.HandlerThread; import android.os.Looper; import android.util.CloseGuard; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.ipsec.ike.IkeSessionStateMachine; import java.io.PrintWriter; import java.util.Objects; import java.util.concurrent.Executor; /** * This class represents an IKE Session management object that allows for keying and management of * {@link android.net.IpSecTransform}s. * * <p>An IKE/Child Session represents an IKE/Child SA as well as its rekeyed successors. A Child * Session is bounded by the lifecycle of the IKE Session under which it is set up. Closing an IKE * Session implicitly closes any remaining Child Sessions under it. * * <p>An IKE procedure is one or multiple IKE message exchanges that are used to create, delete or * rekey an IKE Session or Child Session. * * <p>This class provides methods for initiating IKE procedures, such as the Creation and Deletion * of a Child Session, or the Deletion of the IKE session. All procedures (except for IKE deletion) * will be initiated sequentially after IKE Session is set up. * * @see <a href="https://tools.ietf.org/html/rfc7296">RFC 7296, Internet Key Exchange Protocol * Version 2 (IKEv2)</a> */ public final class IkeSession implements AutoCloseable { private final CloseGuard mCloseGuard = new CloseGuard(); private final Context mContext; /** * Attribution tag for IWLAN callers * * @hide */ public static final String CONTEXT_ATTRIBUTION_TAG_IWLAN = "IWLAN"; /** * Attribution tag for VCN callers * * @hide */ public static final String CONTEXT_ATTRIBUTION_TAG_VCN = "VCN"; /** * Attribution tag for VPN callers * * @hide */ public static final String CONTEXT_ATTRIBUTION_TAG_VPN = "VPN_MANAGER"; @VisibleForTesting final IkeSessionStateMachine mIkeSessionStateMachine; /** * Constructs a new IKE session. * * <p>This method will immediately return an instance of {@link IkeSession} and asynchronously * initiate the setup procedure of {@link IkeSession} as well as its first Child Session. * Callers will be notified of these two setup results via the callback arguments. * * <p>FEATURE_IPSEC_TUNNELS is required for setting up a tunnel mode Child SA. * * @param context a valid {@link Context} instance. * @param ikeSessionParams the {@link IkeSessionParams} that contains a set of valid {@link * IkeSession} configurations. * @param firstChildSessionParams the {@link ChildSessionParams} that contains a set of valid * configurations for the first Child Session. * @param userCbExecutor the {@link Executor} upon which all callbacks will be posted. For * security and consistency, the callbacks posted to this executor MUST be executed serially * and in the order they were posted, as guaranteed by executors such as {@link * java.util.concurrent.Executors#newSingleThreadExecutor()} * @param ikeSessionCallback the {@link IkeSessionCallback} interface to notify callers of state * changes within the {@link IkeSession}. * @param firstChildSessionCallback the {@link ChildSessionCallback} interface to notify callers * of state changes within the first Child Session. * @return an instance of {@link IkeSession}. */ public IkeSession( @NonNull Context context, @NonNull IkeSessionParams ikeSessionParams, @NonNull ChildSessionParams firstChildSessionParams, @NonNull Executor userCbExecutor, @NonNull IkeSessionCallback ikeSessionCallback, @NonNull ChildSessionCallback firstChildSessionCallback) { this( context, (IpSecManager) context.getSystemService(Context.IPSEC_SERVICE), ikeSessionParams, firstChildSessionParams, userCbExecutor, ikeSessionCallback, firstChildSessionCallback); } /** Package private */ @VisibleForTesting IkeSession( Context context, IpSecManager ipSecManager, IkeSessionParams ikeSessionParams, ChildSessionParams firstChildSessionParams, Executor userCbExecutor, IkeSessionCallback ikeSessionCallback, ChildSessionCallback firstChildSessionCallback) { this( IkeThreadHolder.IKE_WORKER_THREAD.getLooper(), context, ipSecManager, ikeSessionParams, firstChildSessionParams, userCbExecutor, ikeSessionCallback, firstChildSessionCallback); } /** Package private */ @VisibleForTesting IkeSession( Looper looper, Context context, IpSecManager ipSecManager, IkeSessionParams ikeSessionParams, ChildSessionParams firstChildSessionParams, Executor userCbExecutor, IkeSessionCallback ikeSessionCallback, ChildSessionCallback firstChildSessionCallback) { mContext = context; if (firstChildSessionParams instanceof TunnelModeChildSessionParams) { checkTunnelFeatureOrThrow(mContext); } mIkeSessionStateMachine = new IkeSessionStateMachine( looper, context, ipSecManager, ikeSessionParams, firstChildSessionParams, userCbExecutor, ikeSessionCallback, firstChildSessionCallback); mIkeSessionStateMachine.openSession(); mCloseGuard.open("open"); } /** @hide */ @Override public void finalize() { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); } } private void checkTunnelFeatureOrThrow(Context context) { // TODO(b/157754168): Also check if OP_MANAGE_IPSEC_TUNNELS is granted when it is exposed if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) { throw new IllegalStateException( "Cannot set up tunnel mode Child SA due to FEATURE_IPSEC_TUNNELS missing"); } } /** Initialization-on-demand holder */ private static class IkeThreadHolder { static final HandlerThread IKE_WORKER_THREAD; static { IKE_WORKER_THREAD = new HandlerThread("IkeWorkerThread"); IKE_WORKER_THREAD.start(); } } // TODO: b/133340675 Destroy the worker thread when there is no more alive {@link IkeSession}. /** * Request a new Child Session. * * <p>Users MUST provide a unique {@link ChildSessionCallback} instance for each new Child * Session. * * <p>Upon setup, {@link ChildSessionCallback#onOpened(ChildSessionConfiguration)} will be * fired. * * <p>FEATURE_IPSEC_TUNNELS is required for setting up a tunnel mode Child SA. * * @param childSessionParams the {@link ChildSessionParams} that contains the Child Session * configurations to negotiate. * @param childSessionCallback the {@link ChildSessionCallback} interface to notify users the * state changes of the Child Session. It will be posted to the callback {@link Executor} of * this {@link IkeSession}. * @throws IllegalArgumentException if the ChildSessionCallback is already in use. */ // The childSessionCallback will be called on the same executor as was passed in the constructor // for security reasons. @SuppressLint("ExecutorRegistration") public void openChildSession( @NonNull ChildSessionParams childSessionParams, @NonNull ChildSessionCallback childSessionCallback) { if (childSessionParams instanceof TunnelModeChildSessionParams) { checkTunnelFeatureOrThrow(mContext); } mIkeSessionStateMachine.openChildSession(childSessionParams, childSessionCallback); } /** * Delete a Child Session. * * <p>Upon closure, {@link ChildSessionCallback#onClosed()} will be fired. * * @param childSessionCallback The {@link ChildSessionCallback} instance that uniquely identify * the Child Session. * @throws IllegalArgumentException if no Child Session found bound with this callback. */ // The childSessionCallback will be called on the same executor as was passed in the constructor // for security reasons. @SuppressLint("ExecutorRegistration") public void closeChildSession(@NonNull ChildSessionCallback childSessionCallback) { mIkeSessionStateMachine.closeChildSession(childSessionCallback); } /** * Close the IKE session gracefully. * * <p>Implements {@link AutoCloseable#close()} * * <p>Upon closure, {@link IkeSessionCallback#onClosed()} or {@link * IkeSessionCallback#onClosedWithException(IkeException)} will be fired. * * <p>Closing an IKE Session implicitly closes any remaining Child Sessions negotiated under it. * Users SHOULD stop all outbound traffic that uses these Child Sessions ({@link * android.net.IpSecTransform} pairs) before calling this method. Otherwise IPsec packets will * be dropped due to the lack of a valid {@link android.net.IpSecTransform}. * * <p>Closure of an IKE session will take priority over, and cancel other procedures waiting in * the queue (but will wait for ongoing locally initiated procedures to complete). After sending * the Delete request, the IKE library will wait until a Delete response is received or * retransmission timeout occurs. */ @Override public void close() { mCloseGuard.close(); mIkeSessionStateMachine.closeSession(); } /** * Terminate (forcibly close) the IKE session. * * <p>Upon closing, {@link IkeSessionCallback#onClosed()} will be fired. * * <p>Closing an IKE Session implicitly closes any remaining Child Sessions negotiated under it. * Users SHOULD stop all outbound traffic that uses these Child Sessions ({@link * android.net.IpSecTransform} pairs) before calling this method. Otherwise IPsec packets will * be dropped due to the lack of a valid {@link android.net.IpSecTransform}. * * <p>Forcible closure of an IKE session will take priority over, and cancel other procedures * waiting in the queue. It will also interrupt any ongoing locally initiated procedure. */ public void kill() { mCloseGuard.close(); mIkeSessionStateMachine.killSession(); } /** * Update the IkeSession's underlying Network to use the specified Network. * * @see #setNetwork(Network, int, int) * @hide */ @SystemApi public void setNetwork(@NonNull Network network) { setNetwork(network, IkeSessionParams.ESP_IP_VERSION_AUTO, IkeSessionParams.ESP_ENCAP_TYPE_AUTO, IkeSessionParams.NATT_KEEPALIVE_INTERVAL_AUTO); } /** * Update the IkeSession's underlying Network, protocol preference and keepalive delay. * * <p>Updating the IkeSession's Network also updates the Network for any Child Sessions created * with this IkeSession. To perform the update, callers must implement: * * <ul> * <li>{@link IkeSessionCallback#onIkeSessionConnectionInfoChanged(IkeSessionConnectionInfo)}: * This call will be triggered once the IKE Session has been updated. The implementation * MUST migrate all IpSecTunnelInterface instances associated with this IkeSession via * {@link android.net.IpSecManager.IpSecTunnelInterface#setUnderlyingNetwork(Network)} * <li>{@link ChildSessionCallback#onIpSecTransformsMigrated(android.net.IpSecTransform, * android.net.IpSecTransform)}: This call will be triggered once a Child Session has been * updated. The implementation MUST re-apply the migrated transforms to the {@link * android.net.IpSecManager.IpSecTunnelInterface} associated with this * ChildSessionCallback, via {@link android.net.IpSecManager#applyTunnelModeTransform( * android.net.IpSecManager.IpSecTunnelInterface, int, android.net.IpSecTransform)}. * </ul> * * <p>In order for Network migration to be possible, the following must be true: * * <ul> * <li>the {@link IkeSessionParams} for this IkeSession must be configured with {@link * IkeSessionParams#IKE_OPTION_MOBIKE} (set via {@link * IkeSessionParams.Builder#addIkeOption(int)}), and * <li>the IkeSession must have been started with the Network specified via {@link * IkeSessionParams.Builder#setNetwork(Network)}. * </ul> * * <p>As MOBIKE support is negotiated, callers are advised to check for MOBIKE support in {@link * IkeSessionConfiguration} before calling this method to update the network. Failure to do so * may cause this call to be ignored. * * @see <a href="https://tools.ietf.org/html/rfc4555">RFC 4555, IKEv2 Mobility and Multihoming * Protocol (MOBIKE)</a> * @param network the Network to use for this IkeSession * @param ipVersion the IP version to use for ESP packets * @param encapType the encapsulation type to use for ESP packets * @param keepaliveDelaySeconds the keepalive delay in seconds, or NATT_KEEPALIVE_INTERVAL_AUTO * to choose the value automatically based on the network. * @throws IllegalStateException if {@link IkeSessionParams#IKE_OPTION_MOBIKE} is not configured * in IkeSessionParams, or if the Network was not specified in IkeSessionParams. * @throws UnsupportedOperationException if the provided option is not supported. * @see IkeSessionParams#getNattKeepAliveDelaySeconds() * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void setNetwork( @NonNull Network network, @IkeSessionParams.EspIpVersion int ipVersion, @IkeSessionParams.EspEncapType int encapType, // Is there a way to specify an intrange + a sentinel value ? @IntRange( from = IKE_NATT_KEEPALIVE_DELAY_SEC_MIN, to = IKE_NATT_KEEPALIVE_DELAY_SEC_MAX) int keepaliveDelaySeconds) { if ((ipVersion == ESP_IP_VERSION_IPV4 && encapType == ESP_ENCAP_TYPE_NONE) || (ipVersion == ESP_IP_VERSION_IPV6 && encapType == ESP_ENCAP_TYPE_UDP)) { throw new UnsupportedOperationException("Sending packets with IPv4 ESP or IPv6 UDP" + " are not supported"); } mIkeSessionStateMachine.setNetwork(Objects.requireNonNull(network), ipVersion, encapType, keepaliveDelaySeconds); } /** * Inform the session that it is used to supply the passed network. * * This will be used by the session when it needs to perform actions that depend on what * network this session is underpinning. In particular, this can be used to turn off * keepalives when there are no connections open on the underpinned network, if the * {@link IkeSessionParams#IKE_OPTION_AUTOMATIC_KEEPALIVE_ON_OFF} option is turned on. * * @param underpinnedNetwork the network underpinned by this session. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void setUnderpinnedNetwork(@NonNull Network underpinnedNetwork) { mIkeSessionStateMachine.setUnderpinnedNetwork(Objects.requireNonNull(underpinnedNetwork)); } /** * Request to check liveness of peer. * * <p>This method returns immediately and asynchronously, * * <p>The liveness check determines whether a peer is alive by executing a new on-demand DPD * task or joining an existing running task depending on the situation. * * <ul> * <li>If there is no running task when a liveness check request is called, a new on-demand * DPD task is started. The on-demand DPD (Dead Peer Detection) is used for checking * liveness of peer in this case. This method adds an on-demand DPD request to the work * queue to check that the peer is alive. The on-demand DPD uses retransmit timeouts from * {@link IkeSessionParams#getLivenessRetransmissionTimeoutsMillis}. * <li>If any IKE message is already in progress when a client requests a liveness check, the * liveness check request is joined to an existing running task. And then the liveness * check runs in the background. When a running task receives a valid IKE message packet * from a peer, it can verify that the peer is alive in the background without triggering * an on-demand DPD task. A running task uses retransmit timeouts from {@link * IkeSessionParams#getRetransmissionTimeoutsMillis}. * </ul> * * <p>The client is notified of the progress or result statuses of the liveness check via {@link * IkeSessionCallback#onLivenessStatusChanged}. These statuses are notified after a liveness * check request is started. By notifying {@link * IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_SUCCESS} or {@link * IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_FAILURE}, the liveness check request is * done and no further status notification is made until the next {@link * IkeSession#requestLivenessCheck}. The status notifications to the client are as follows. * * <ul> * <li>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_ON_DEMAND_STARTED}: This * status is called when liveness checking is started with a new on-demand DPD task. * <li>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_ON_DEMAND_ONGOING}: This * status is called when liveness checking is already running in an on-demand DPD task. * <li>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_BACKGROUND_STARTED}: This * status is called when liveness checking is started in the background and has joined an * existing running task. * <li>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_BACKGROUND_ONGOING}: This * status is called when liveness checking is already running in the background by joining * an existing running task. * <li>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_SUCCESS}: This status is * called when the peer's liveness is proven. Once this status is notified, the liveness * check request is done and no further status notification is made until the next {@link * IkeSession#requestLivenessCheck}. * <li>{@link IkeSessionCallback.LivenessStatus#LIVENESS_STATUS_FAILURE}: This state is called * when the peer is determined as dead for a liveness check request. After this status is * notified, the IkeSession will be closed immediately by calling {@link * IkeSessionCallback#onClosedWithException} with {@link * android.net.ipsec.ike.exceptions.IkeTimeoutException} in the {@link * IkeException#getCause()}. * </ul> * * <p>If a valid IKE message response is received from the peer, the IkeSession remains as * connected and periodic DPD reschedules by {@link IkeSessionParams#getDpdDelaySeconds} * * <p>If the liveness check request couldn't get any a peer's valid response in retransmission * timeout, The IkeSession will be closed. Session closing is also notified to {@link * IkeSessionCallback#onClosedWithException} with {@link * android.net.ipsec.ike.exceptions.IkeTimeoutException} cause. * * @hide */ @SystemApi @FlaggedApi("com.android.ipsec.flags.liveness_check_api") public void requestLivenessCheck() { mIkeSessionStateMachine.requestLivenessCheck(); } /** * Dumps the state of {@link IkeSession} information for the clients * * @param pw Print writer */ @FlaggedApi("com.android.ipsec.flags.dumpsys_api") public void dump(@NonNull PrintWriter pw) { // TODO(b/336409878): Add @RequiresPermission annotation. mContext.enforceCallingOrSelfPermission( android.Manifest.permission.DUMP, mContext.getAttributionTag()); // Please make sure that the dump is thread-safe // so the client won't get a crash or exception when adding codes to the dump. pw.println(); pw.println("IkeSession:"); pw.println("------------------------------"); // Dump ike state machine. if (mIkeSessionStateMachine != null) { pw.println(); mIkeSessionStateMachine.dump(pw); } pw.println("------------------------------"); } }