diff options
author | evitayan <evitayan@google.com> | 2020-03-30 15:55:53 -0700 |
---|---|---|
committer | evitayan <evitayan@google.com> | 2020-04-01 15:14:15 -0700 |
commit | d9ab1ad8df645b1c5d8972d307a110d66fe0b690 (patch) | |
tree | f406c8e4b6f41f9e28a29825f57806147a214a6c | |
parent | ebdb40bd6c55a424bd276afe3a036c87d552ce6c (diff) | |
download | ike-d9ab1ad8df645b1c5d8972d307a110d66fe0b690.tar.gz |
Start NATT keepalive from IkeSessionStateMachine
This commit supports IkeSessionStateMachine to do NAT-T
Keepalive when a NAT is detected. IKE libray will first
try using hardware offload if available. If it is not
available, a software keepalive will be attempted
Bug: 148794150
Test: FrameworksIkeTests
Test: Manually tested against Strongswan sever,
saw receiving isakmp-nat-keep-alive in tcpdump
Change-Id: I5407da239b8958828ff03681d2014e3b015b0d79
3 files changed, 87 insertions, 12 deletions
diff --git a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java index 8b931ffa..cd76d41a 100644 --- a/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java +++ b/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachine.java @@ -41,6 +41,7 @@ import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_DELETE_CHILD; import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_DELETE_IKE; import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_DPD; +import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_KEEPALIVE; import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_REKEY_CHILD; import static com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver.ACTION_REKEY_IKE; @@ -98,6 +99,7 @@ import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedExcepti import com.android.internal.net.ipsec.ike.exceptions.InvalidKeException; import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException; import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException; +import com.android.internal.net.ipsec.ike.keepalive.IkeNattKeepalive; import com.android.internal.net.ipsec.ike.message.IkeAuthDigitalSignPayload; import com.android.internal.net.ipsec.ike.message.IkeAuthPayload; import com.android.internal.net.ipsec.ike.message.IkeAuthPskPayload; @@ -195,6 +197,7 @@ public class IkeSessionStateMachine extends AbstractSessionStateMachine { sIntentFilter.addAction(ACTION_DPD); sIntentFilter.addAction(ACTION_REKEY_CHILD); sIntentFilter.addAction(ACTION_REKEY_IKE); + sIntentFilter.addAction(ACTION_KEEPALIVE); } private static final AtomicInteger sIkeSessionIdGenerator = new AtomicInteger(); @@ -216,6 +219,8 @@ public class IkeSessionStateMachine extends AbstractSessionStateMachine { @VisibleForTesting static final long TEMP_FAILURE_RETRY_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(5L); + @VisibleForTesting static final int NATT_KEEPALIVE_DELAY_SECONDS = 10; + // Package private IKE exchange subtypes describe the specific function of a IKE // request/response exchange. It helps IkeSessionStateMachine to do message validation according // to the subtype specific rules. @@ -288,6 +293,8 @@ public class IkeSessionStateMachine extends AbstractSessionStateMachine { static final int CMD_EAP_FINISH_EAP_AUTH = CMD_GENERAL_BASE + 14; /** Alarm goes off for a scheduled event, check {@link Message.arg2} for event type */ static final int CMD_ALARM_FIRED = CMD_GENERAL_BASE + 15; + /** Send keepalive packet */ + static final int CMD_SEND_KEEPALIVE = CMD_GENERAL_BASE + 16; /** Force state machine to a target state for testing purposes. */ static final int CMD_FORCE_TRANSITION = CMD_GENERAL_BASE + 99; @@ -370,6 +377,8 @@ public class IkeSessionStateMachine extends AbstractSessionStateMachine { @VisibleForTesting boolean mIsLocalBehindNat; /** Indicates if remote node is behind a NAT. */ @VisibleForTesting boolean mIsRemoteBehindNat; + /** NATT keepalive scheduler. Initialized when a NAT is detected */ + @VisibleForTesting IkeNattKeepalive mIkeNattKeepalive; /** Indicates if both sides support fragmentation. Set in IKE INIT */ @VisibleForTesting boolean mSupportFragment; @@ -1008,8 +1017,13 @@ public class IkeSessionStateMachine extends AbstractSessionStateMachine { } } - if (mIkeSocket == null) return; - mIkeSocket.releaseReference(this); + if (mIkeNattKeepalive != null) { + mIkeNattKeepalive.stop(); + } + + if (mIkeSocket != null) { + mIkeSocket.releaseReference(this); + } sIkeAlarmReceiver.unregisterIkeSession(mIkeSessionId); @@ -1244,6 +1258,10 @@ public class IkeSessionStateMachine extends AbstractSessionStateMachine { } } + private String getIntentIdentifier() { + return TAG + "_" + mIkeSessionId; + } + private String getIntentIdentifier(long remoteIkeSpi) { return TAG + "_" + mIkeSessionId + "_" + remoteIkeSpi; } @@ -1521,6 +1539,10 @@ public class IkeSessionStateMachine extends AbstractSessionStateMachine { protected void handleFiredAlarm(Message message) { switch (message.arg2) { + case CMD_SEND_KEEPALIVE: + // Software keepalive alarm is fired + mIkeNattKeepalive.onAlarmFired(); + return; case CMD_LOCAL_REQUEST_DELETE_CHILD: // Child SA (identified by remoteChildSpi) has hit its hard lifetime enqueueChildLocalRequest(message); @@ -2987,9 +3009,28 @@ public class IkeSessionStateMachine extends AbstractSessionStateMachine { } catch (ErrnoException | IOException | ResourceUnavailableException e) { handleIkeFatalError(e); } + + mIkeNattKeepalive = + new IkeNattKeepalive( + mContext, + NATT_KEEPALIVE_DELAY_SECONDS, + (Inet4Address) mLocalAddress, + (Inet4Address) mRemoteAddress, + ((IkeUdpEncapSocket) mIkeSocket).getUdpEncapsulationSocket(), + mIkeSocket.getNetwork(), + buildKeepaliveIntent()); + mIkeNattKeepalive.start(); } } + private PendingIntent buildKeepaliveIntent() { + return buildIkeAlarmIntent( + mContext, + ACTION_KEEPALIVE, + getIntentIdentifier(), + obtainMessage(CMD_ALARM_FIRED, mIkeSessionId, CMD_SEND_KEEPALIVE)); + } + @Override public void exitState() { super.exitState(); diff --git a/src/java/com/android/internal/net/ipsec/ike/utils/IkeAlarmReceiver.java b/src/java/com/android/internal/net/ipsec/ike/utils/IkeAlarmReceiver.java index 7a12ddd2..c37c8daf 100644 --- a/src/java/com/android/internal/net/ipsec/ike/utils/IkeAlarmReceiver.java +++ b/src/java/com/android/internal/net/ipsec/ike/utils/IkeAlarmReceiver.java @@ -41,6 +41,7 @@ public class IkeAlarmReceiver extends BroadcastReceiver { public static final String ACTION_DELETE_IKE = "IkeAlarmReceiver.ACTION_DELETE_IKE"; public static final String ACTION_REKEY_IKE = "IkeAlarmReceiver.ACTION_REKEY_IKE"; public static final String ACTION_DPD = "IkeAlarmReceiver.ACTION_DPD"; + public static final String ACTION_KEEPALIVE = "IkeAlarmReceiver.ACTION_KEEPALIVE"; private static final HashSet<String> sIkeSessionActionsSet = new HashSet<>(); @@ -76,7 +77,8 @@ public class IkeAlarmReceiver extends BroadcastReceiver { case ACTION_REKEY_CHILD: // fallthrough case ACTION_DELETE_IKE: // fallthrough case ACTION_REKEY_IKE: // fallthrough - case ACTION_DPD: + case ACTION_DPD: // fallthrough + case ACTION_KEEPALIVE: // This Message has lost its target information after being sent as a Broadcast Message message = (Message) intent.getExtras().getParcelable(PARCELABLE_NAME_IKE_SESSION_MSG); diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java index 73b6c7e7..f7ca9db7 100644 --- a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java +++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java @@ -73,10 +73,13 @@ import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.content.Context; +import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.InetAddresses; import android.net.IpSecManager; +import android.net.IpSecManager.UdpEncapsulationSocket; import android.net.Network; +import android.net.SocketKeepalive; import android.net.eap.EapSessionConfig; import android.net.ipsec.ike.ChildSaProposal; import android.net.ipsec.ike.ChildSessionCallback; @@ -93,6 +96,7 @@ import android.net.ipsec.ike.TunnelModeChildSessionParams; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeInternalException; import android.net.ipsec.ike.exceptions.IkeProtocolException; +import android.os.Handler; import android.os.test.TestLooper; import android.telephony.TelephonyManager; @@ -150,6 +154,7 @@ import com.android.internal.net.ipsec.ike.message.IkeTestUtils; import com.android.internal.net.ipsec.ike.message.IkeTsPayload; import com.android.internal.net.ipsec.ike.testutils.CertUtils; import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils; +import com.android.internal.net.ipsec.ike.utils.IkeAlarmReceiver; import com.android.internal.net.ipsec.ike.utils.Retransmitter; import com.android.internal.net.ipsec.ike.utils.Retransmitter.IBackoffTimeoutCalculator; import com.android.internal.net.ipsec.ike.utils.State; @@ -288,11 +293,12 @@ public final class IkeSessionStateMachineTest { private static final long RETRANSMIT_BACKOFF_TIMEOUT_MS = 5000L; private MockIpSecTestUtils mMockIpSecTestUtils; - private Context mContext; + private Context mSpyContext; private IpSecManager mIpSecManager; private ConnectivityManager mMockConnectManager; private Network mMockDefaultNetwork; + private SocketKeepalive mMockSocketKeepalive; private IkeUdpEncapSocket mSpyIkeUdpEncapSocket; private IkeUdp4Socket mSpyIkeUdp4Socket; private IkeUdp6Socket mSpyIkeUdp6Socket; @@ -656,7 +662,16 @@ public final class IkeSessionStateMachineTest { mMockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec(); mIpSecManager = mMockIpSecTestUtils.getIpSecManager(); - mContext = mMockIpSecTestUtils.getContext(); + + mSpyContext = spy(mMockIpSecTestUtils.getContext()); + doReturn(null) + .when(mSpyContext) + .registerReceiver( + any(IkeAlarmReceiver.class), + any(IntentFilter.class), + any(), + any(Handler.class)); + doNothing().when(mSpyContext).unregisterReceiver(any(IkeAlarmReceiver.class)); mMockConnectManager = mock(ConnectivityManager.class); mMockDefaultNetwork = mock(Network.class); @@ -666,6 +681,20 @@ public final class IkeSessionStateMachineTest { .when(mMockDefaultNetwork) .getByName(REMOTE_ADDRESS.getHostAddress()); + mMockSocketKeepalive = mock(SocketKeepalive.class); + doReturn(mMockSocketKeepalive) + .when(mMockConnectManager) + .createSocketKeepalive( + any(Network.class), + any(UdpEncapsulationSocket.class), + any(Inet4Address.class), + any(Inet4Address.class), + any(Executor.class), + any(SocketKeepalive.Callback.class)); + doReturn(mMockConnectManager) + .when(mSpyContext) + .getSystemService(Context.CONNECTIVITY_SERVICE); + mEapSessionConfig = new EapSessionConfig.Builder() .setEapSimConfig(EAP_SIM_SUB_ID, TelephonyManager.APPTYPE_USIM) @@ -758,7 +787,7 @@ public final class IkeSessionStateMachineTest { IkeSessionStateMachine ikeSession = new IkeSessionStateMachine( mLooper.getLooper(), - mContext, + mSpyContext, mIpSecManager, ikeParams, mChildSessionParams, @@ -1319,6 +1348,9 @@ public final class IkeSessionStateMachineTest { // Validate socket switched assertTrue(mIkeSessionStateMachine.mIkeSocket instanceof IkeUdpEncapSocket); verify(mSpyIkeUdp4Socket).unregisterIke(anyLong()); + + // Validate keepalive has started + verify(mMockSocketKeepalive).start(anyInt()); } @Ignore @@ -1462,7 +1494,7 @@ public final class IkeSessionStateMachineTest { // After state machine start, add to the callback->statemachine map when(mMockChildSessionFactoryHelper.makeChildSessionStateMachine( eq(mLooper.getLooper()), - eq(mContext), + eq(mSpyContext), anyInt(), any(AlarmManager.class), eq(mChildSessionParams), @@ -1519,7 +1551,7 @@ public final class IkeSessionStateMachineTest { verify(mMockChildSessionFactoryHelper) .makeChildSessionStateMachine( eq(mLooper.getLooper()), - eq(mContext), + eq(mSpyContext), anyInt(), any(AlarmManager.class), eq(mChildSessionParams), @@ -2261,7 +2293,7 @@ public final class IkeSessionStateMachineTest { verify(mMockChildSessionFactoryHelper) .makeChildSessionStateMachine( eq(mLooper.getLooper()), - eq(mContext), + eq(mSpyContext), anyInt(), any(AlarmManager.class), eq(mChildSessionParams), @@ -2532,7 +2564,7 @@ public final class IkeSessionStateMachineTest { .newEapAuthenticator( eq(mIkeSessionStateMachine.getHandler().getLooper()), captor.capture(), - eq(mContext), + eq(mSpyContext), eq(mEapSessionConfig)); return captor.getValue(); @@ -4100,7 +4132,7 @@ public final class IkeSessionStateMachineTest { verify(mMockChildSessionFactoryHelper) .makeChildSessionStateMachine( eq(mLooper.getLooper()), - eq(mContext), + eq(mSpyContext), anyInt(), any(AlarmManager.class), eq(mChildSessionParams), @@ -4183,7 +4215,7 @@ public final class IkeSessionStateMachineTest { IkeSessionStateMachine ikeSession = new IkeSessionStateMachine( mLooper.getLooper(), - mContext, + mSpyContext, mIpSecManager, mockSessionParams, mChildSessionParams, |