diff options
author | Yan Yan <evitayan@google.com> | 2020-04-02 01:00:54 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-04-02 01:00:54 +0000 |
commit | b22a56fd3db12293b0db12313fc5552ae2fea346 (patch) | |
tree | 300342a9ce6065b887b927e788f9aced9779b365 | |
parent | fc562f6f1d986fcef2217ddaff956f8a8f6ea7b5 (diff) | |
parent | ebdb40bd6c55a424bd276afe3a036c87d552ce6c (diff) | |
download | ike-b22a56fd3db12293b0db12313fc5552ae2fea346.tar.gz |
Merge "Create IkeNattKeepalive to manage hardware and software keepalive"
3 files changed, 374 insertions, 0 deletions
diff --git a/src/java/com/android/internal/net/ipsec/ike/keepalive/HardwareKeepaliveImpl.java b/src/java/com/android/internal/net/ipsec/ike/keepalive/HardwareKeepaliveImpl.java new file mode 100644 index 00000000..bb0abe58 --- /dev/null +++ b/src/java/com/android/internal/net/ipsec/ike/keepalive/HardwareKeepaliveImpl.java @@ -0,0 +1,127 @@ +/* + * 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.SocketKeepalive.ERROR_HARDWARE_ERROR; +import static android.net.SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES; +import static android.net.SocketKeepalive.ERROR_INVALID_INTERVAL; +import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; +import static android.net.SocketKeepalive.ERROR_INVALID_LENGTH; +import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK; +import static android.net.SocketKeepalive.ERROR_INVALID_PORT; +import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET; +import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE; +import static android.net.SocketKeepalive.ERROR_UNSUPPORTED; +import static android.net.ipsec.ike.IkeManager.getIkeLog; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.Network; +import android.net.SocketKeepalive; + +import java.io.IOException; +import java.net.Inet4Address; +import java.util.concurrent.Executors; + +/** This class provides methods to manage hardware offload NAT-T keepalive. */ +public class HardwareKeepaliveImpl implements IkeNattKeepalive.NattKeepalive { + private static final String TAG = "HardwareKeepaliveImpl"; + + private final int mKeepaliveDelaySeconds; + private final SocketKeepalive mSocketKeepalive; + private final HardwareKeepaliveCallback mHardwareKeepaliveCb; + + /** Construct an instance of HardwareKeepaliveImpl */ + public HardwareKeepaliveImpl( + Context context, + int keepaliveDelaySeconds, + Inet4Address src, + Inet4Address dest, + UdpEncapsulationSocket socket, + Network network, + HardwareKeepaliveCallback hardwareKeepaliveCb) + throws IOException { + // Setup for hardware offload keepalive. Fail to create mSocketKeepalive will cause + // MySocketKeepaliveCb#onError to be fired + mKeepaliveDelaySeconds = keepaliveDelaySeconds; + mHardwareKeepaliveCb = hardwareKeepaliveCb; + + ConnectivityManager connMgr = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mSocketKeepalive = + connMgr.createSocketKeepalive( + network, + socket, + src, + dest, + Executors.newSingleThreadExecutor(), + new MySocketKeepaliveCb()); + } + + @Override + public void start() { + mSocketKeepalive.start(mKeepaliveDelaySeconds); + } + + @Override + public void stop() { + mSocketKeepalive.stop(); + } + + @Override + public void onAlarmFired() { + // Do thing. Should never be called + } + + /** Callback interface to receive states change of hardware keepalive */ + public interface HardwareKeepaliveCallback { + /** Called when there is a hardware error for keepalive. */ + void onHardwareOffloadError(); + + /** + * Called when there is a network or configuration error which cause sending keepalive + * packet to fail + */ + void onNetworkError(); + } + + private class MySocketKeepaliveCb extends SocketKeepalive.Callback { + @Override + public void onError(int error) { + getIkeLog().d(TAG, "Hardware offload failed on error: " + error); + switch (error) { + case ERROR_INVALID_NETWORK: // fallthrough + case ERROR_INVALID_IP_ADDRESS: // fallthrough + case ERROR_INVALID_PORT: // fallthrough + case ERROR_INVALID_LENGTH: // fallthrough + case ERROR_INVALID_INTERVAL: // fallthrough + case ERROR_INVALID_SOCKET: // fallthrough + case ERROR_SOCKET_NOT_IDLE: // fallthrough + mHardwareKeepaliveCb.onNetworkError(); + return; + case ERROR_UNSUPPORTED: // fallthrough + case ERROR_HARDWARE_ERROR: // fallthrough + case ERROR_INSUFFICIENT_RESOURCES: + mHardwareKeepaliveCb.onHardwareOffloadError(); + return; + default: + mHardwareKeepaliveCb.onNetworkError(); + } + } + } +} diff --git a/src/java/com/android/internal/net/ipsec/ike/keepalive/IkeNattKeepalive.java b/src/java/com/android/internal/net/ipsec/ike/keepalive/IkeNattKeepalive.java new file mode 100644 index 00000000..129a1f05 --- /dev/null +++ b/src/java/com/android/internal/net/ipsec/ike/keepalive/IkeNattKeepalive.java @@ -0,0 +1,140 @@ +/* + * 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.app.PendingIntent; +import android.content.Context; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.Network; + +import java.io.IOException; +import java.net.Inet4Address; + +/** + * 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 NattKeepalive mNattKeepalive; + + /** Construct an instance of IkeNattKeepalive */ + public IkeNattKeepalive( + Context context, + int keepaliveDelaySeconds, + Inet4Address src, + Inet4Address dest, + UdpEncapsulationSocket socket, + Network network, + PendingIntent keepAliveAlarmIntent) + throws IOException { + mNattKeepalive = + new HardwareKeepaliveImpl( + context, + keepaliveDelaySeconds, + src, + dest, + socket, + network, + new HardwareKeepaliveCb( + context, + keepaliveDelaySeconds, + dest, + socket, + keepAliveAlarmIntent)); + } + + /** 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(); + } + + /** 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(); + } + + private class HardwareKeepaliveCb implements HardwareKeepaliveImpl.HardwareKeepaliveCallback { + private final Context mContext; + private final int mKeepaliveDelaySeconds; + private final Inet4Address mDest; + private final UdpEncapsulationSocket mSocket; + private final PendingIntent mKeepAliveAlarmIntent; + + HardwareKeepaliveCb( + Context context, + int keepaliveDelaySeconds, + Inet4Address dest, + UdpEncapsulationSocket socket, + PendingIntent keepAliveAlarmIntent) { + mContext = context; + mKeepaliveDelaySeconds = keepaliveDelaySeconds; + mDest = dest; + mSocket = socket; + mKeepAliveAlarmIntent = keepAliveAlarmIntent; + } + + @Override + public void onHardwareOffloadError() { + getIkeLog().d(TAG, "Switch to software keepalive"); + mNattKeepalive.stop(); + + mNattKeepalive = + new SoftwareKeepaliveImpl( + mContext, + mKeepaliveDelaySeconds, + mDest, + mSocket, + mKeepAliveAlarmIntent); + 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 extral risk. When there is a network + // error, IkeSessionStateMachine will eventually hit the max request retransmission + // times and be terminated anyway. + stop(); + } + } +} diff --git a/src/java/com/android/internal/net/ipsec/ike/keepalive/SoftwareKeepaliveImpl.java b/src/java/com/android/internal/net/ipsec/ike/keepalive/SoftwareKeepaliveImpl.java new file mode 100644 index 00000000..af39d6e3 --- /dev/null +++ b/src/java/com/android/internal/net/ipsec/ike/keepalive/SoftwareKeepaliveImpl.java @@ -0,0 +1,107 @@ +/* + * 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.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.os.SystemClock; +import android.system.ErrnoException; +import android.system.Os; + +import com.android.internal.net.ipsec.ike.IkeSocket; + +import java.net.Inet4Address; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; + +/** This class provides methods to schedule and send keepalive packet. */ +public final class SoftwareKeepaliveImpl implements IkeNattKeepalive.NattKeepalive { + private static final String TAG = "SoftwareKeepaliveImpl"; + + // NAT-Keepalive packet payload as per RFC 3948 + private static final byte[] NATT_KEEPALIVE_PAYLOAD = new byte[] {(byte) 0xff}; + + private final long mKeepaliveDelayMs; + private final UdpEncapsulationSocket mSocket; + private final Inet4Address mDestAddress; + private final AlarmManager mAlarmMgr; + private final PendingIntent mKeepaliveIntent; + + /** + * Construct an instance of SoftwareKeepaliveImpl + * + * <p>Caller that provides keepAliveAlarmIntent is responsible for handling the alarm. + */ + public SoftwareKeepaliveImpl( + Context context, + int keepaliveDelaySeconds, + Inet4Address dest, + UdpEncapsulationSocket socket, + PendingIntent keepAliveAlarmIntent) { + mKeepaliveDelayMs = TimeUnit.SECONDS.toMillis(keepaliveDelaySeconds); + mSocket = socket; + + mAlarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + mDestAddress = dest; + mKeepaliveIntent = keepAliveAlarmIntent; + } + + @Override + public void start() { + sendKeepaliveAndScheduleNext(); + } + + @Override + public void stop() { + mAlarmMgr.cancel(mKeepaliveIntent); + mKeepaliveIntent.cancel(); + } + + @Override + public void onAlarmFired() { + sendKeepaliveAndScheduleNext(); + } + + /** Send out keepalive packet and schedule next keepalive event */ + private void sendKeepaliveAndScheduleNext() { + try { + Os.sendto( + mSocket.getFileDescriptor(), + ByteBuffer.wrap(NATT_KEEPALIVE_PAYLOAD), + 0, + mDestAddress, + IkeSocket.SERVER_PORT_UDP_ENCAPSULATED); + + } catch (ErrnoException | SocketException e) { + getIkeLog().i(TAG, "Failed to keepalive packet to " + mDestAddress.getHostAddress(), e); + } + + // It is time-critical to send packets periodically to keep the dynamic NAT mapping + // alive. Thus, the alarm has to be "setExact" to avoid batching delay (can be at most 75%) + // and allowed to goes off when the device is in doze mode. There will still be a rate limit + // on firing alarms. Please check AlarmManager#setExactAndAllowWhileIdle for more details. + mAlarmMgr.setExactAndAllowWhileIdle( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + mKeepaliveDelayMs, + mKeepaliveIntent); + } +} |