| /* |
| * Copyright (C) 2020 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 com.android.internal.net.ipsec.ike.keepalive; |
| |
| import static android.net.ipsec.ike.IkeManager.getIkeLog; |
| |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.net.IpSecManager.UdpEncapsulationSocket; |
| import android.net.Network; |
| import android.net.ipsec.ike.IkeSessionParams; |
| |
| import com.android.internal.net.ipsec.ike.IkeContext; |
| import com.android.internal.net.ipsec.ike.utils.IkeAlarm.IkeAlarmConfig; |
| |
| import java.io.IOException; |
| import java.net.Inet4Address; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * This class provides methods to manage NAT-T keepalive for a UdpEncapsulationSocket. |
| * |
| * <p>Upon calling {@link start()}, this class will start a NAT-T keepalive, using hardware offload |
| * if available. If hardware offload is not available, a software keepalive will be attempted. |
| */ |
| public class IkeNattKeepalive { |
| private static final String TAG = "IkeNattKeepalive"; |
| |
| private final Dependencies mDeps; |
| private final Context mContext; |
| private final ConnectivityManager mConnectivityManager; |
| |
| private NattKeepalive mNattKeepalive; |
| private KeepaliveConfig mNattKeepaliveConfig; |
| |
| /** |
| * The hardware keepalive that is being stopped but has not fired the onStopped callback. |
| * |
| * <p>This field is set as part of restart and is cleared when the restart is finished |
| */ |
| private HardwareKeepaliveImpl mHardwareKeepalivePendingOnStopped; |
| |
| /** Construct an instance of IkeNattKeepalive */ |
| public IkeNattKeepalive( |
| IkeContext ikeContext, |
| ConnectivityManager connectMgr, |
| KeepaliveConfig nattKeepaliveConfig) |
| throws IOException { |
| this(ikeContext, connectMgr, nattKeepaliveConfig, new Dependencies()); |
| } |
| |
| IkeNattKeepalive( |
| IkeContext ikeContext, |
| ConnectivityManager connectMgr, |
| KeepaliveConfig nattKeepaliveConfig, |
| Dependencies deps) |
| throws IOException { |
| mDeps = deps; |
| mContext = ikeContext.getContext(); |
| mConnectivityManager = connectMgr; |
| mNattKeepaliveConfig = nattKeepaliveConfig; |
| |
| mNattKeepalive = |
| mDeps.createHardwareKeepaliveImpl( |
| mContext, |
| mConnectivityManager, |
| mNattKeepaliveConfig, |
| new HardwareKeepaliveCb( |
| mContext, |
| mNattKeepaliveConfig.dest, |
| mNattKeepaliveConfig.socket, |
| mNattKeepaliveConfig.ikeAlarmConfig)); |
| } |
| |
| /** Configuration object for constructing an IkeNattKeepalive instance */ |
| public static class KeepaliveConfig { |
| public final Inet4Address src; |
| public final Inet4Address dest; |
| public final UdpEncapsulationSocket socket; |
| public final Network network; |
| @Nullable |
| public final Network underpinnedNetwork; |
| public final IkeAlarmConfig ikeAlarmConfig; |
| public final IkeSessionParams ikeParams; |
| |
| public KeepaliveConfig( |
| Inet4Address src, |
| Inet4Address dest, |
| UdpEncapsulationSocket socket, |
| Network network, |
| Network underpinnedNetwork, |
| IkeAlarmConfig ikeAlarmConfig, |
| IkeSessionParams ikeParams) { |
| this.src = src; |
| this.dest = dest; |
| this.socket = socket; |
| this.network = network; |
| this.underpinnedNetwork = underpinnedNetwork; |
| this.ikeAlarmConfig = ikeAlarmConfig; |
| this.ikeParams = ikeParams; |
| } |
| } |
| |
| /** Start keepalive */ |
| public void start() { |
| // Try keepalive using hardware offload first |
| getIkeLog().d(TAG, "Start NAT-T keepalive"); |
| mNattKeepalive.start(); |
| } |
| |
| /** Stop keepalive */ |
| public void stop() { |
| getIkeLog().d(TAG, "Stop NAT-T keepalive"); |
| |
| mNattKeepalive.stop(); |
| } |
| |
| private void finishRestartingWithNewHardwareKeepalive() { |
| mHardwareKeepalivePendingOnStopped = null; |
| |
| mNattKeepalive.stop(); |
| mNattKeepalive = |
| mDeps.createHardwareKeepaliveImpl( |
| mContext, |
| mConnectivityManager, |
| mNattKeepaliveConfig, |
| new HardwareKeepaliveCb( |
| mContext, |
| mNattKeepaliveConfig.dest, |
| mNattKeepaliveConfig.socket, |
| mNattKeepaliveConfig.ikeAlarmConfig)); |
| mNattKeepalive.start(); |
| } |
| |
| /** Update the keepalive config and restart the keepalive */ |
| public void restart(KeepaliveConfig nattKeepaliveConfig) { |
| getIkeLog().d(TAG, "restart"); |
| |
| mNattKeepaliveConfig = nattKeepaliveConfig; |
| |
| if (mNattKeepalive instanceof HardwareKeepaliveImpl) { |
| mHardwareKeepalivePendingOnStopped = (HardwareKeepaliveImpl) mNattKeepalive; |
| getIkeLog() |
| .d( |
| TAG, |
| "Wait for onStopped on " |
| + mHardwareKeepalivePendingOnStopped |
| + " before starting new hardware keepalive"); |
| } |
| |
| if (mHardwareKeepalivePendingOnStopped != null) { |
| // Start software keepalive and wait for the onStopped callback before starting new |
| // hardware offload. Each network has limited quota for hardware keepalive. Thus in |
| // this case, IKE should not start new hardware offload until the old one is stopped. |
| mNattKeepalive.stop(); |
| mNattKeepalive = |
| mDeps.createSoftwareKeepaliveImpl( |
| mContext, |
| mNattKeepaliveConfig.dest, |
| mNattKeepaliveConfig.socket, |
| mNattKeepaliveConfig.ikeAlarmConfig); |
| mNattKeepalive.start(); |
| } else { |
| finishRestartingWithNewHardwareKeepalive(); |
| } |
| } |
| |
| /** Check whether the IkeNattKeepalive is being restarted */ |
| public boolean isRestarting() { |
| return mHardwareKeepalivePendingOnStopped != null; |
| } |
| |
| /** Receive a keepalive alarm */ |
| public void onAlarmFired() { |
| mNattKeepalive.onAlarmFired(); |
| } |
| |
| /** Interface that a keepalive implementation MUST provide to support NAT-T keepalive for IKE */ |
| public interface NattKeepalive { |
| /** Start keepalive */ |
| void start(); |
| /** Stop keepalive */ |
| void stop(); |
| /** Receive a keepalive alarm */ |
| void onAlarmFired(); |
| } |
| |
| static class Dependencies { |
| SoftwareKeepaliveImpl createSoftwareKeepaliveImpl( |
| Context context, |
| Inet4Address dest, |
| UdpEncapsulationSocket socket, |
| IkeAlarmConfig alarmConfig) { |
| return new SoftwareKeepaliveImpl(context, dest, socket, alarmConfig); |
| } |
| |
| HardwareKeepaliveImpl createHardwareKeepaliveImpl( |
| Context context, |
| ConnectivityManager connectMgr, |
| KeepaliveConfig nattKeepaliveConfig, |
| HardwareKeepaliveImpl.HardwareKeepaliveCallback hardwareKeepaliveCb) { |
| final long keepaliveDelayMs = nattKeepaliveConfig.ikeAlarmConfig.delayMs; |
| return new HardwareKeepaliveImpl( |
| context, |
| connectMgr, |
| (int) TimeUnit.MILLISECONDS.toSeconds(keepaliveDelayMs), |
| nattKeepaliveConfig.ikeParams, |
| nattKeepaliveConfig.src, |
| nattKeepaliveConfig.dest, |
| nattKeepaliveConfig.socket, |
| nattKeepaliveConfig.network, |
| nattKeepaliveConfig.underpinnedNetwork, |
| hardwareKeepaliveCb); |
| } |
| } |
| |
| private class HardwareKeepaliveCb implements HardwareKeepaliveImpl.HardwareKeepaliveCallback { |
| private final Context mContext; |
| private final Inet4Address mDest; |
| private final UdpEncapsulationSocket mSocket; |
| private final IkeAlarmConfig mIkeAlarmConfig; |
| |
| HardwareKeepaliveCb( |
| Context context, |
| Inet4Address dest, |
| UdpEncapsulationSocket socket, |
| IkeAlarmConfig ikeAlarmConfig) { |
| mContext = context; |
| mDest = dest; |
| mSocket = socket; |
| mIkeAlarmConfig = ikeAlarmConfig; |
| } |
| |
| @Override |
| public void onHardwareOffloadError() { |
| getIkeLog().d(TAG, "Switch to software keepalive"); |
| mNattKeepalive.stop(); |
| |
| mNattKeepalive = |
| mDeps.createSoftwareKeepaliveImpl(mContext, mDest, mSocket, mIkeAlarmConfig); |
| mNattKeepalive.start(); |
| } |
| |
| @Override |
| public void onNetworkError() { |
| // Stop doing keepalive when getting network error since it will also fail software |
| // keepalive. Considering the only user of IkeNattKeepalive is IkeSessionStateMachine, |
| // not notifying user this error won't bring user extra risk. When there is a network |
| // error, IkeSessionStateMachine will eventually hit the max request retransmission |
| // times and be terminated anyway. |
| |
| // TODO: b/182209475 Terminate IKE Sessions when |
| // HardwareKeepaliveCallback#onNetworkError is fired |
| stop(); |
| } |
| |
| @Override |
| public void onStopped(HardwareKeepaliveImpl hardwareKeepalive) { |
| getIkeLog() |
| .d( |
| TAG, |
| "Hardware keepalive onStopped on hardwareKeepalive " |
| + hardwareKeepalive); |
| if (hardwareKeepalive == mHardwareKeepalivePendingOnStopped) { |
| finishRestartingWithNewHardwareKeepalive(); |
| } |
| } |
| } |
| } |