diff options
author | evitayan <evitayan@google.com> | 2019-03-26 21:21:40 -0700 |
---|---|---|
committer | evitayan <evitayan@google.com> | 2019-04-01 14:59:34 -0700 |
commit | d8375f932ab0ad5b8a144e9259cadf3a4f619d41 (patch) | |
tree | e19f89855c004da39d9e52cb9cc5425b362d7a39 | |
parent | 676849a795522632f5c951c6a6d8ace982b2e6c1 (diff) | |
download | ike-d8375f932ab0ad5b8a144e9259cadf3a4f619d41.tar.gz |
Receive and demultiplex IKE packet
This commit:
- Supports demultiplexing received IKE packets and re-direct them
to corresponding IkeSessionStateMachine
- Adds interface in IkeSessionStateMachine to receive IKE packet
Bug: 112043692
Test: atest FrameworksIkeTests:IkeSocketTest
Change-Id: I29ae81be7e8df35f7bb477a77754fe05a2bb2c59
4 files changed, 169 insertions, 18 deletions
diff --git a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java index c887ecb0..31d15e5e 100644 --- a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java +++ b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java @@ -18,7 +18,6 @@ package com.android.ike.ikev2; import android.os.Looper; import android.os.Message; import android.util.LongSparseArray; -import android.util.Pair; import android.util.SparseArray; import com.android.ike.ikev2.SaRecord.IkeSaRecord; @@ -214,6 +213,19 @@ public class IkeSessionStateMachine extends StateMachine { } /** + * Receive IKE packet from remote server. + * + * <p>This method is called synchronously from IkeSocket. It proxies the synchronous call as an + * asynchronous job to the IkeSessionStateMachine handler. + * + * @param ikeHeader the decoded IKE header. + * @param ikePacketBytes the byte array of the entire received IKE packet. + */ + public void receiveIkePacket(IkeHeader ikeHeader, byte[] ikePacketBytes) { + sendMessage(CMD_RECEIVE_IKE_PACKET, new ReceivedIkePacket(ikeHeader, ikePacketBytes)); + } + + /** * ReceivedIkePacket is a package private data container consists of decoded IkeHeader and * encoded IKE packet in a byte array. */ @@ -223,9 +235,9 @@ public class IkeSessionStateMachine extends StateMachine { /** Entire encoded IKE message including IKE header */ public final byte[] ikePacketBytes; - ReceivedIkePacket(Pair<IkeHeader, byte[]> ikePacketPair) { - ikeHeader = ikePacketPair.first; - ikePacketBytes = ikePacketPair.second; + ReceivedIkePacket(IkeHeader ikeHeader, byte[] ikePacketBytes) { + this.ikeHeader = ikeHeader; + this.ikePacketBytes = ikePacketBytes; } } diff --git a/src/java/com/android/ike/ikev2/IkeSocket.java b/src/java/com/android/ike/ikev2/IkeSocket.java index 71daddd2..0f1b9e65 100644 --- a/src/java/com/android/ike/ikev2/IkeSocket.java +++ b/src/java/com/android/ike/ikev2/IkeSocket.java @@ -25,8 +25,11 @@ 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; @@ -60,11 +63,15 @@ import java.util.Map; * 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]; // Package private map from UdpEncapsulationSocket to IkeSocket instances. static Map<UdpEncapsulationSocket, IkeSocket> sFdToIkeSocketMap = new HashMap<>(); @@ -138,10 +145,50 @@ public final class IkeSocket extends PacketReader implements AutoCloseable { } /** Package private */ + @VisibleForTesting static final class PacketReceiver implements IPacketReceiver { public void handlePacket( byte[] recvbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession) { - // TODO: Decode IKE header and demultiplex IKE packet + // 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."); + } } } @@ -171,7 +218,7 @@ public final class IkeSocket extends PacketReader implements AutoCloseable { ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER_LEN + ikePacket.length); // Build outbound UDP Encapsulation packet body for sending IKE message. - buffer.put(new byte[NON_ESP_MARKER_LEN]).put(ikePacket); + buffer.put(NON_ESP_MARKER).put(ikePacket); buffer.rewind(); // Use unconnected UDP socket because one {@UdpEncapsulationSocket} may be shared by diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java index 02aaa4c3..e3ad450a 100644 --- a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java +++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java @@ -27,7 +27,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.test.TestLooper; -import android.util.Pair; import com.android.ike.ikev2.ChildSessionStateMachineFactory.ChildSessionFactoryHelper; import com.android.ike.ikev2.ChildSessionStateMachineFactory.IChildSessionFactoryHelper; @@ -67,12 +66,12 @@ public final class IkeSessionStateMachineTest { private ReceivedIkePacket makeDummyUnencryptedReceivedIkePacket(int packetType) throws Exception { IkeMessage dummyIkeMessage = makeDummyIkeMessageForTest(0, 0, false, false); - Pair<IkeHeader, byte[]> dummyIkePacketPair = - new Pair<>(dummyIkeMessage.ikeHeader, new byte[0]); - when(mMockIkeMessageHelper.decode(dummyIkePacketPair.first, dummyIkePacketPair.second)) + byte[] dummyIkePacketBytes = new byte[0]; + + when(mMockIkeMessageHelper.decode(dummyIkeMessage.ikeHeader, dummyIkePacketBytes)) .thenReturn(dummyIkeMessage); when(mMockIkeMessageHelper.getMessageType(dummyIkeMessage)).thenReturn(packetType); - return new ReceivedIkePacket(dummyIkePacketPair); + return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes); } private ReceivedIkePacket makeDummyEncryptedReceivedIkePacket( @@ -81,16 +80,16 @@ public final class IkeSessionStateMachineTest { IkeMessage dummyIkeMessage = makeDummyIkeMessageForTest( ikeSaRecord.initiatorSpi, ikeSaRecord.responderSpi, fromIkeInit, true); - Pair<IkeHeader, byte[]> dummyIkePacketPair = - new Pair<>(dummyIkeMessage.ikeHeader, new byte[0]); + byte[] dummyIkePacketBytes = new byte[0]; + when(mMockIkeMessageHelper.decode( mMockIkeSessionOptions, ikeSaRecord, - dummyIkePacketPair.first, - dummyIkePacketPair.second)) + dummyIkeMessage.ikeHeader, + dummyIkePacketBytes)) .thenReturn(dummyIkeMessage); when(mMockIkeMessageHelper.getMessageType(dummyIkeMessage)).thenReturn(packetType); - return new ReceivedIkePacket(dummyIkePacketPair); + return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes); } private IkeMessage makeDummyIkeMessageForTest( diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeSocketTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeSocketTest.java index 2dd44096..5f15f554 100644 --- a/tests/iketests/src/java/com/android/ike/ikev2/IkeSocketTest.java +++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeSocketTest.java @@ -20,6 +20,11 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.content.Context; import android.net.IpSecManager; @@ -34,9 +39,14 @@ import android.util.LongSparseArray; import androidx.test.InstrumentationRegistry; +import com.android.ike.ikev2.IkeSocket.PacketReceiver; +import com.android.ike.ikev2.message.IkeHeader; +import com.android.ike.ikev2.message.TestUtils; + import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.io.FileDescriptor; import java.net.InetAddress; @@ -50,6 +60,26 @@ public final class IkeSocketTest { private static final int REMOTE_RECV_BUFF_SIZE = 2048; private static final int TIMEOUT = 1000; + private static final String NON_ESP_MARKER_HEX_STRING = "00000000"; + private static final String IKE_REQ_MESSAGE_HEX_STRING = + "5f54bf6d8b48e6e100000000000000002120220800000000" + + "00000150220000300000002c010100040300000c0100000c" + + "800e00800300000803000002030000080400000200000008" + + "020000022800008800020000b4a2faf4bb54878ae21d6385" + + "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656" + + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37" + + "534036040610ebdd92f46bef84f0be7db860351843858f8a" + + "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb" + + "0b278fd4b7b0a4c26bbeb08214c707137607958729000024" + + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7" + + "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb" + + "881eab2051d8663f421d10b02b00001c00004005d915368c" + + "a036004cb578ae3e3fb268509aeab1900000002069936922" + + "8741c6d4ca094c93e242c9de19e7b7c60000000500000500"; + + private static final String LOCAL_SPI = "0000000000000000"; + private static final String REMOTE_SPI = "5f54bf6d8b48e6e1"; + private static final String DATA_ONE = "one 1"; private static final String DATA_TWO = "two 2"; @@ -58,10 +88,18 @@ public final class IkeSocketTest { private byte[] mDataOne; private byte[] mDataTwo; + private long mLocalSpi; + private long mRemoteSpi; + + private LongSparseArray mSpiToIkeStateMachineMap; + private PacketReceiver mPacketReceiver; + private UdpEncapsulationSocket mClientUdpEncapSocket; private InetAddress mLocalAddress; private FileDescriptor mDummyRemoteServerFd; + private IkeSessionStateMachine mMockIkeSessionStateMachine; + @Before public void setUp() throws Exception { Context context = InstrumentationRegistry.getContext(); @@ -73,12 +111,24 @@ public final class IkeSocketTest { mDataOne = DATA_ONE.getBytes("UTF-8"); mDataTwo = DATA_TWO.getBytes("UTF-8"); + + ByteBuffer localSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(LOCAL_SPI)); + mLocalSpi = localSpiBuffer.getLong(); + ByteBuffer remoteSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(REMOTE_SPI)); + mRemoteSpi = remoteSpiBuffer.getLong(); + + mMockIkeSessionStateMachine = mock(IkeSessionStateMachine.class); + + mSpiToIkeStateMachineMap = new LongSparseArray<IkeSessionStateMachine>(); + mSpiToIkeStateMachineMap.put(mLocalSpi, mMockIkeSessionStateMachine); + + mPacketReceiver = new IkeSocket.PacketReceiver(); } @After public void tearDown() throws Exception { mClientUdpEncapSocket.close(); - IkeSocket.setPacketReceiver(new IkeSocket.PacketReceiver()); + IkeSocket.setPacketReceiver(mPacketReceiver); Os.close(mDummyRemoteServerFd); } @@ -120,7 +170,7 @@ public final class IkeSocketTest { // Verify received data ByteBuffer expectedBuffer = ByteBuffer.allocate(IkeSocket.NON_ESP_MARKER_LEN + mDataOne.length); - expectedBuffer.put(new byte[IkeSocket.NON_ESP_MARKER_LEN]).put(mDataOne); + expectedBuffer.put(IkeSocket.NON_ESP_MARKER).put(mDataOne); assertArrayEquals(expectedBuffer.array(), receivedData); @@ -187,6 +237,49 @@ public final class IkeSocketTest { mIkeThread.quitSafely(); } + @Test + public void testHandlePacket() throws Exception { + byte[] recvBuf = + TestUtils.hexStringToByteArray( + NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING); + + mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap); + + byte[] expectedIkePacketBytes = TestUtils.hexStringToByteArray(IKE_REQ_MESSAGE_HEX_STRING); + ArgumentCaptor<IkeHeader> ikeHeaderCaptor = ArgumentCaptor.forClass(IkeHeader.class); + verify(mMockIkeSessionStateMachine) + .receiveIkePacket(ikeHeaderCaptor.capture(), eq(expectedIkePacketBytes)); + + IkeHeader capturedIkeHeader = ikeHeaderCaptor.getValue(); + assertEquals(mRemoteSpi, capturedIkeHeader.ikeInitiatorSpi); + assertEquals(mLocalSpi, capturedIkeHeader.ikeResponderSpi); + } + + @Test + public void testHandleEspPacket() throws Exception { + byte[] recvBuf = + TestUtils.hexStringToByteArray( + NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING); + // Modify Non-ESP Marker + recvBuf[0] = 1; + + mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap); + + verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any()); + } + + @Test + public void testHandlePacketWithMalformedHeader() throws Exception { + String malformedIkePacketHexString = "5f54bf6d8b48e6e100000000000000002120220800000000"; + byte[] recvBuf = + TestUtils.hexStringToByteArray( + NON_ESP_MARKER_HEX_STRING + malformedIkePacketHexString); + + mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap); + + verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any()); + } + private byte[] receive(FileDescriptor mfd) throws Exception { byte[] receiveBuffer = new byte[REMOTE_RECV_BUFF_SIZE]; AtomicInteger bytesRead = new AtomicInteger(-1); |