aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/ike/ikev2/IkeSocket.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/com/android/ike/ikev2/IkeSocket.java')
-rw-r--r--src/java/com/android/ike/ikev2/IkeSocket.java268
1 files changed, 268 insertions, 0 deletions
diff --git a/src/java/com/android/ike/ikev2/IkeSocket.java b/src/java/com/android/ike/ikev2/IkeSocket.java
new file mode 100644
index 00000000..e235ab88
--- /dev/null
+++ b/src/java/com/android/ike/ikev2/IkeSocket.java
@@ -0,0 +1,268 @@
+/*
+ * 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 com.android.ike.ikev2;
+
+import static android.system.OsConstants.F_SETFL;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_NONBLOCK;
+
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.util.PacketReader;
+import android.os.Handler;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import com.android.ike.ikev2.exceptions.IkeException;
+import com.android.ike.ikev2.message.IkeHeader;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * IkeSocket sends and receives IKE packets via the user provided {@link UdpEncapsulationSocket}.
+ *
+ * <p>One UdpEncapsulationSocket instance can only be bound to one IkeSocket instance. IkeSocket
+ * maintains a static map to cache all bound UdpEncapsulationSockets and their IkeSocket instances.
+ * It returns the existing IkeSocket when it has been bound with user provided {@link
+ * UdpEncapsulationSocket}.
+ *
+ * <p>As a packet receiver, IkeSocket registers a file descriptor with a thread's Looper and handles
+ * read events (and errors). Users can expect a call life-cycle like the following:
+ *
+ * <pre>
+ * [1] when user gets a new initiated IkeSocket, start() is called and followed by createFd().
+ * [2] yield, waiting for a read event which will invoke handlePacket()
+ * [3] when user closes this IkeSocket, its reference count decreases. Then stop() is called when
+ * there is no reference of this instance.
+ * </pre>
+ *
+ * <p>IkeSocket is constructed and called only on a single IKE working thread by {@link
+ * IkeSessionStateMachine}. Since all {@link IkeSessionStateMachine}s run on the same working
+ * thread, there will not be concurrent modification problems.
+ */
+public final class IkeSocket extends PacketReader implements AutoCloseable {
+ private static final String TAG = "IkeSocket";
+
+ // TODO: b/129358324 Consider supporting IKE exchange without UDP Encapsulation.
+ // UDP-encapsulated IKE packets MUST be sent to 4500.
+ @VisibleForTesting static final int IKE_SERVER_PORT = 4500;
+
+ // A Non-ESP marker helps the recipient to distinguish IKE packets from ESP packets.
+ @VisibleForTesting static final int NON_ESP_MARKER_LEN = 4;
+ @VisibleForTesting static final byte[] NON_ESP_MARKER = new byte[NON_ESP_MARKER_LEN];
+
+ // Map from UdpEncapsulationSocket to IkeSocket instances.
+ private static Map<UdpEncapsulationSocket, IkeSocket> sFdToIkeSocketMap = new HashMap<>();
+
+ private static IPacketReceiver sPacketReceiver = new PacketReceiver();
+
+ // Package private map from locally generated IKE SPI to IkeSessionStateMachine instances.
+ @VisibleForTesting
+ final LongSparseArray<IkeSessionStateMachine> mSpiToIkeSession =
+ new LongSparseArray<>();
+ // UdpEncapsulationSocket for sending and receving IKE packet.
+ private final UdpEncapsulationSocket mUdpEncapSocket;
+
+ /** Package private */
+ @VisibleForTesting
+ int mRefCount;
+
+ private IkeSocket(UdpEncapsulationSocket udpEncapSocket, Handler handler) {
+ super(handler);
+ mRefCount = 1;
+ mUdpEncapSocket = udpEncapSocket;
+ }
+
+ /**
+ * Get an IkeSocket instance.
+ *
+ * <p>Return the existing IkeSocket instance if it has been created for the input
+ * udpEncapSocket. Otherwise, create and return a new IkeSocket instance.
+ *
+ * @param udpEncapSocket user provided UdpEncapsulationSocket
+ * @return an IkSocket instance
+ */
+ public static IkeSocket getIkeSocket(UdpEncapsulationSocket udpEncapSocket)
+ throws ErrnoException {
+ FileDescriptor fd = udpEncapSocket.getFileDescriptor();
+ // All created IkeSocket has modified its FileDescriptor to non-blocking type for handling
+ // read events in a non-blocking way.
+ Os.fcntlInt(fd, F_SETFL, SOCK_DGRAM | SOCK_NONBLOCK);
+
+ if (sFdToIkeSocketMap.containsKey(udpEncapSocket)) {
+ IkeSocket ikeSocket = sFdToIkeSocketMap.get(udpEncapSocket);
+ ikeSocket.mRefCount++;
+ return ikeSocket;
+ } else {
+ IkeSocket ikeSocket = new IkeSocket(udpEncapSocket, new Handler());
+ // Create and register FileDescriptor for receiving IKE packet on current thread.
+ ikeSocket.start();
+
+ sFdToIkeSocketMap.put(udpEncapSocket, ikeSocket);
+ return ikeSocket;
+ }
+ }
+
+ /**
+ * Get FileDecriptor of mUdpEncapSocket.
+ *
+ * <p>PacketReader registers a listener for this file descriptor on the thread where IkeSocket
+ * is constructed. When there is a read event, this listener is invoked and then calls {@link
+ * handlePacket} to handle the received packet.
+ */
+ @Override
+ protected FileDescriptor createFd() {
+ return mUdpEncapSocket.getFileDescriptor();
+ }
+
+ /**
+ * IPacketReceiver provides a package private interface for handling received packet.
+ *
+ * <p>IPacketReceiver exists so that the interface is injectable for testing.
+ */
+ interface IPacketReceiver {
+ void handlePacket(byte[] recvbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession);
+ }
+
+ /** Package private */
+ @VisibleForTesting
+ static final class PacketReceiver implements IPacketReceiver {
+ public void handlePacket(
+ byte[] recvbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession) {
+ // TODO: b/129708574 Consider only logging the error some % of the time it happens, or
+ // only logging the error the first time it happens and then keep a count to prevent
+ // logspam.
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf);
+
+ // Check the existence of the Non-ESP Marker. A received packet can be either an IKE
+ // packet starts with 4 zero-valued bytes Non-ESP Marker or an ESP packet starts with 4
+ // bytes ESP SPI. ESP SPI value can never be zero.
+ byte[] espMarker = new byte[NON_ESP_MARKER_LEN];
+ byteBuffer.get(espMarker);
+ if (!Arrays.equals(NON_ESP_MARKER, espMarker)) {
+ // Drop the received ESP packet.
+ Log.e(TAG, "Receive an ESP packet.");
+ return;
+ }
+
+ try {
+ // Re-direct IKE packet to IkeSessionStateMachine according to the locally generated
+ // IKE SPI.
+ byte[] ikePacketBytes = new byte[byteBuffer.remaining()];
+ byteBuffer.get(ikePacketBytes);
+ IkeHeader ikeHeader = new IkeHeader(ikePacketBytes);
+
+ long localGeneratedSpi =
+ ikeHeader.fromIkeInitiator
+ ? ikeHeader.ikeResponderSpi
+ : ikeHeader.ikeInitiatorSpi;
+
+ IkeSessionStateMachine ikeStateMachine = spiToIkeSession.get(localGeneratedSpi);
+ if (ikeStateMachine == null) {
+ Log.e(TAG, "Unrecognized IKE SPI.");
+ // TODO: Handle invalid IKE SPI error
+ } else {
+ ikeStateMachine.receiveIkePacket(ikeHeader, ikePacketBytes);
+ }
+ } catch (IkeException e) {
+ // Handle invalid IKE header
+ Log.e(TAG, "Can't parse malformed IKE packet header.");
+ }
+ }
+ }
+
+ /** Package private */
+ @VisibleForTesting
+ static void setPacketReceiver(IPacketReceiver receiver) {
+ sPacketReceiver = receiver;
+ }
+
+ /**
+ * Handle received IKE packet. Invoked when there is a read event. Any desired copies of
+ * |recvbuf| should be made in here, as the underlying byte array is reused across all reads.
+ */
+ @Override
+ protected void handlePacket(byte[] recvbuf, int length) {
+ sPacketReceiver.handlePacket(Arrays.copyOfRange(recvbuf, 0, length), mSpiToIkeSession);
+ }
+
+ /**
+ * Send encoded IKE packet to destination address
+ *
+ * @param ikePacket encoded IKE packet
+ * @param serverAddress IP address of remote server
+ */
+ public void sendIkePacket(byte[] ikePacket, InetAddress serverAddress) {
+ try {
+ ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER_LEN + ikePacket.length);
+
+ // Build outbound UDP Encapsulation packet body for sending IKE message.
+ buffer.put(NON_ESP_MARKER).put(ikePacket);
+ buffer.rewind();
+
+ // Use unconnected UDP socket because one {@UdpEncapsulationSocket} may be shared by
+ // multiple IKE sessions that send messages to different destinations.
+ Os.sendto(
+ mUdpEncapSocket.getFileDescriptor(), buffer, 0, serverAddress, IKE_SERVER_PORT);
+ } catch (ErrnoException | IOException e) {
+ // TODO: Handle exception
+ }
+ }
+
+ /**
+ * Register new created IKE SA
+ *
+ * @param spi the locally generated IKE SPI
+ * @param ikeSession the IKE session this IKE SA belongs to
+ */
+ public void registerIke(long spi, IkeSessionStateMachine ikeSession) {
+ mSpiToIkeSession.put(spi, ikeSession);
+ }
+
+ /**
+ * Unregister a deleted IKE SA
+ *
+ * @param spi the locally generated IKE SPI
+ */
+ public void unregisterIke(long spi) {
+ mSpiToIkeSession.remove(spi);
+ }
+
+ /** Release reference of current IkeSocket when the IKE session is closed. */
+ public void releaseReference() {
+ mRefCount--;
+ if (mRefCount == 0) close();
+ }
+
+ /** Implement {@link AutoCloseable#close()} */
+ @Override
+ public void close() {
+ sFdToIkeSocketMap.remove(mUdpEncapSocket);
+ // PackeReader unregisters file descriptor on thread with which the Handler constructor
+ // argument is associated.
+ stop();
+ }
+}