blob: e235ab8817ee2e3519e6066030b23c9b15978a1b [file] [log] [blame]
/*
* 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();
}
}