blob: 11bb93402b8b9bf6e555307affeec4968f88bd4b [file] [log] [blame]
/*
* 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;
import static android.net.ipsec.ike.IkeManager.getIkeLog;
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;
import android.net.IpSecManager.ResourceUnavailableException;
import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.Network;
import android.os.Handler;
import android.os.Looper;
import android.system.ErrnoException;
import android.system.Os;
import android.util.LongSparseArray;
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;
/**
* IkeUdpEncapSocket uses an {@link UdpEncapsulationSocket} to send and receive IKE packets.
*
* <p>Caller MUST provide one {@link Network} when trying to get an instance of IkeUdpEncapSocket.
* Each {@link Network} can only be bound with one IkeUdpEncapSocket instance. When caller requests
* for IkeUdpEncapSocket with an already bound {@link Network}, an existing instance will be
* returned.
*/
public final class IkeUdpEncapSocket extends IkeSocket {
private static final String TAG = "IkeUdpEncapSocket";
// 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 Network to IkeSocket instances.
private static Map<Network, IkeUdpEncapSocket> sNetworkToIkeSocketMap = new HashMap<>();
private static IPacketReceiver sPacketReceiver = new PacketReceiver();
// UdpEncapsulationSocket for sending and receving IKE packet.
private final UdpEncapsulationSocket mUdpEncapSocket;
private IkeUdpEncapSocket(
UdpEncapsulationSocket udpEncapSocket, Network network, Handler handler) {
super(network, handler);
mUdpEncapSocket = udpEncapSocket;
}
/**
* Get an IkeUdpEncapSocket instance.
*
* <p>Return the existing IkeUdpEncapSocket instance if it has been created for the input
* Network. Otherwise, create and return a new IkeUdpEncapSocket instance.
*
* @param network the Network this socket will be bound to
* @param ipsecManager for creating {@link UdpEncapsulationSocket}
* @param ikeSession the IkeSessionStateMachine that is requesting an IkeUdpEncapSocket.
* @return an IkeUdpEncapSocket instance
*/
public static IkeUdpEncapSocket getIkeUdpEncapSocket(
Network network,
IpSecManager ipsecManager,
IkeSessionStateMachine ikeSession,
Looper looper)
throws ErrnoException, IOException, ResourceUnavailableException {
IkeUdpEncapSocket ikeSocket = sNetworkToIkeSocketMap.get(network);
if (ikeSocket == null) {
UdpEncapsulationSocket udpEncapSocket = ipsecManager.openUdpEncapsulationSocket();
FileDescriptor fd = udpEncapSocket.getFileDescriptor();
// {@link PacketReader} requires non-blocking I/O access. Set SOCK_NONBLOCK here.
Os.fcntlInt(fd, F_SETFL, SOCK_DGRAM | SOCK_NONBLOCK);
network.bindSocket(fd);
ikeSocket = new IkeUdpEncapSocket(udpEncapSocket, network, new Handler(looper));
// Create and register FileDescriptor for receiving IKE packet on current thread.
ikeSocket.start();
sNetworkToIkeSocketMap.put(network, ikeSocket);
}
ikeSocket.mAliveIkeSessions.add(ikeSession);
return ikeSocket;
}
/**
* Returns the {@link UdpEncapsulationSocket}
*
* @return the {@link UdpEncapsulationSocket} for sending and receiving IKE packets.
*/
public UdpEncapsulationSocket getUdpEncapsulationSocket() {
return mUdpEncapSocket;
}
/** Get FileDescriptor for use with the packet reader polling loop */
@Override
protected FileDescriptor getFd() {
return mUdpEncapSocket.getFileDescriptor(); // Returns cached FD
}
/** Package private */
@VisibleForTesting
static final class PacketReceiver implements IkeSocket.IPacketReceiver {
public void handlePacket(
byte[] recvbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession) {
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.
getIkeLog().e(TAG, "Receive an ESP packet.");
return;
}
// Re-direct IKE packet to IkeSessionStateMachine according to the locally generated
// IKE SPI.
byte[] ikePacketBytes = new byte[byteBuffer.remaining()];
byteBuffer.get(ikePacketBytes);
parseAndDemuxIkePacket(ikePacketBytes, spiToIkeSession, TAG);
}
}
/** Package private */
@VisibleForTesting
static void setPacketReceiver(IkeSocket.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
*/
@Override
public void sendIkePacket(byte[] ikePacket, InetAddress serverAddress) {
getIkeLog()
.d(
TAG,
"Send packet to "
+ serverAddress.getHostAddress()
+ "( "
+ ikePacket.length
+ " bytes)");
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,
SERVER_PORT_UDP_ENCAPSULATED);
} catch (ErrnoException | IOException e) {
// TODO: Handle exception
}
}
@Override
public int getIkeServerPort() {
return SERVER_PORT_UDP_ENCAPSULATED;
}
/** Implement {@link AutoCloseable#close()} */
@Override
public void close() {
sNetworkToIkeSocketMap.remove(getNetwork());
try {
mUdpEncapSocket.close();
} catch (IOException e) {
getIkeLog()
.e(
TAG,
"Failed to close UDP Encapsulation Socket with Port= "
+ mUdpEncapSocket.getPort());
}
// PacketReader unregisters file descriptor on thread with which the Handler constructor
// argument is associated.
super.close();
}
}