Support decoding Traffic Selector am: 68fde6e39c
am: e2304e57d3
Change-Id: Ie3f0edc4c5c9e01f49c82822cb2aad13d10bb2ab
diff --git a/src/java/com/android/ike/ikev2/IkeTrafficSelector.java b/src/java/com/android/ike/ikev2/IkeTrafficSelector.java
new file mode 100644
index 0000000..baebe64
--- /dev/null
+++ b/src/java/com/android/ike/ikev2/IkeTrafficSelector.java
@@ -0,0 +1,231 @@
+/*
+ * 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 android.annotation.IntDef;
+import android.util.ArraySet;
+
+import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * IkeTrafficSelector represents a Traffic Selector of a Child SA.
+ *
+ * <p>IkeTrafficSelector can be constructed by users for initiating Create Child exchange or be
+ * constructed from a decoded inbound Traffic Selector Payload.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.13">RFC 7296, Internet Key Exchange
+ * Protocol Version 2 (IKEv2)</a>
+ */
+public final class IkeTrafficSelector {
+
+ // IpProtocolId consists of standard IP Protocol IDs.
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({IP_PROTOCOL_ID_UNSPEC, IP_PROTOCOL_ID_ICMP, IP_PROTOCOL_ID_TCP, IP_PROTOCOL_ID_UCP})
+ public @interface IpProtocolId {}
+
+ // Zero value is re-defined by IKE to indicate that all IP protocols are acceptable.
+ @VisibleForTesting static final int IP_PROTOCOL_ID_UNSPEC = 0;
+ @VisibleForTesting static final int IP_PROTOCOL_ID_ICMP = 1;
+ @VisibleForTesting static final int IP_PROTOCOL_ID_TCP = 6;
+ @VisibleForTesting static final int IP_PROTOCOL_ID_UCP = 17;
+
+ private static final ArraySet<Integer> IP_PROTOCOL_ID_SET = new ArraySet<>();
+
+ static {
+ IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_UNSPEC);
+ IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_ICMP);
+ IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_TCP);
+ IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_UCP);
+ }
+
+ /**
+ * TrafficSelectorType consists of IKE standard Traffic Selector Types.
+ *
+ * @see <a
+ * href="https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml">Internet
+ * Key Exchange Version 2 (IKEv2) Parameters</a>
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE})
+ public @interface TrafficSelectorType {}
+
+ public static final int TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE = 7;
+ public static final int TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE = 8;
+
+ // TODO: Consider defining these constants in a central place in Connectivity.
+ private static final int IPV4_ADDR_LEN = 4;
+ private static final int IPV6_ADDR_LEN = 16;
+
+ @VisibleForTesting static final int TRAFFIC_SELECTOR_IPV4_LEN = 16;
+ @VisibleForTesting static final int TRAFFIC_SELECTOR_IPV6_LEN = 40;
+
+ public final int tsType;
+ public final int ipProtocolId;
+ public final int selectorLength;
+ public final int startPort;
+ public final int endPort;
+ public final InetAddress startingAddress;
+ public final InetAddress endingAddress;
+
+ private IkeTrafficSelector(
+ int tsType,
+ int ipProtocolId,
+ int selectorLength,
+ int startPort,
+ int endPort,
+ InetAddress startingAddress,
+ InetAddress endingAddress) {
+ this.tsType = tsType;
+ this.ipProtocolId = ipProtocolId;
+ this.selectorLength = selectorLength;
+ this.startPort = startPort;
+ this.endPort = endPort;
+ this.startingAddress = startingAddress;
+ this.endingAddress = endingAddress;
+ }
+
+ // TODO: Add a constructor for users to construct IkeTrafficSelector.
+
+ /**
+ * Decode IkeTrafficSelectors from inbound Traffic Selector Payload.
+ *
+ * <p>This method is only called by IkeTsPayload when decoding inbound IKE message.
+ *
+ * @param numTs number or Traffic Selectors
+ * @param tsBytes encoded byte array of Traffic Selectors
+ * @return an array of decoded IkeTrafficSelectors
+ * @throws InvalidSyntaxException if received bytes are malformed.
+ */
+ public static IkeTrafficSelector[] decodeIkeTrafficSelectors(int numTs, byte[] tsBytes)
+ throws InvalidSyntaxException {
+ IkeTrafficSelector[] tsArray = new IkeTrafficSelector[numTs];
+ ByteBuffer inputBuffer = ByteBuffer.wrap(tsBytes);
+
+ try {
+ for (int i = 0; i < numTs; i++) {
+ int tsType = Byte.toUnsignedInt(inputBuffer.get());
+ switch (tsType) {
+ case TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE:
+ tsArray[i] = decodeIpv4TrafficSelector(inputBuffer);
+ break;
+ case TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE:
+ // TODO: Support it.
+ throw new UnsupportedOperationException("Cannot decode this type.");
+ default:
+ throw new InvalidSyntaxException(
+ "Invalid Traffic Selector type: " + tsType);
+ }
+ }
+ } catch (BufferOverflowException e) {
+ // Throw exception if any Traffic Selector has invalid length.
+ throw new InvalidSyntaxException(e);
+ }
+
+ if (inputBuffer.remaining() != 0) {
+ throw new InvalidSyntaxException(
+ "Unexpected trailing characters of Traffic Selectors.");
+ }
+
+ return tsArray;
+ }
+
+ // Decode Traffic Selector using IPv4 address range from a ByteBuffer. A BufferOverflowException
+ // will be thrown and caught by method caller if operation reaches the input ByteBuffer's limit.
+ private static IkeTrafficSelector decodeIpv4TrafficSelector(ByteBuffer inputBuffer)
+ throws InvalidSyntaxException {
+ // Decode and validate IP Protocol ID
+ int ipProtocolId = Byte.toUnsignedInt(inputBuffer.get());
+ if (!IP_PROTOCOL_ID_SET.contains(ipProtocolId)) {
+ throw new InvalidSyntaxException("Invalid IP Protocol ID.");
+ }
+
+ // Decode and validate Selector Length
+ int tsLength = Short.toUnsignedInt(inputBuffer.getShort());
+ if (TRAFFIC_SELECTOR_IPV4_LEN != tsLength) {
+ throw new InvalidSyntaxException("Invalid Traffic Selector Length.");
+ }
+
+ // Decode and validate ports
+ int startPort = Short.toUnsignedInt(inputBuffer.getShort());
+ int endPort = Short.toUnsignedInt(inputBuffer.getShort());
+ if (startPort > endPort) {
+ throw new InvalidSyntaxException("Received invalid port range.");
+ }
+
+ // Decode and validate IPv4 addresses
+ byte[] startAddressBytes = new byte[IPV4_ADDR_LEN];
+ byte[] endAddressBytes = new byte[IPV4_ADDR_LEN];
+ inputBuffer.get(startAddressBytes);
+ inputBuffer.get(endAddressBytes);
+ try {
+ Inet4Address startAddress =
+ (Inet4Address) (Inet4Address.getByAddress(startAddressBytes));
+ Inet4Address endAddress = (Inet4Address) (Inet4Address.getByAddress(endAddressBytes));
+
+ // Validate address range.
+ if (!isInetAddressRangeValid(startAddress, endAddress)) {
+ throw new InvalidSyntaxException("Received invalid IPv4 address range.");
+ }
+
+ return new IkeTrafficSelector(
+ TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE,
+ ipProtocolId,
+ TRAFFIC_SELECTOR_IPV4_LEN,
+ startPort,
+ endPort,
+ startAddress,
+ endAddress);
+ } catch (ClassCastException | UnknownHostException | IllegalArgumentException e) {
+ throw new InvalidSyntaxException(e);
+ }
+ }
+
+ // TODO: Add a method for decoding IPv6 traffic selector.
+
+ // Validate address range. Caller must ensure two address are same types.
+ // TODO: Consider moving it to the platform code in the future.
+ private static boolean isInetAddressRangeValid(
+ InetAddress startAddress, InetAddress endAddress) {
+ byte[] startAddrBytes = startAddress.getAddress();
+ byte[] endAddrBytes = endAddress.getAddress();
+
+ if (startAddrBytes.length != endAddrBytes.length) {
+ throw new IllegalArgumentException("Two addresses are different types.");
+ }
+
+ for (int i = 0; i < startAddrBytes.length; i++) {
+ int unsignedByteStart = Byte.toUnsignedInt(startAddrBytes[i]);
+ int unsignedByteEnd = Byte.toUnsignedInt(endAddrBytes[i]);
+
+ if (unsignedByteStart < unsignedByteEnd) {
+ return true;
+ } else if (unsignedByteStart > unsignedByteEnd) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java b/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java
index ea4b55a..489eeff 100644
--- a/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java
+++ b/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java
@@ -35,4 +35,13 @@
public InvalidSyntaxException(String message) {
super(IkeNotifyPayload.NOTIFY_TYPE_INVALID_SYNTAX);
}
+
+ /**
+ * Construct a instance of InvalidSyntaxException.
+ *
+ * @param cause the reason of exception.
+ */
+ public InvalidSyntaxException(Throwable cause) {
+ super(IkeNotifyPayload.NOTIFY_TYPE_INVALID_SYNTAX, cause);
+ }
}
diff --git a/src/java/com/android/ike/ikev2/message/IkeTsPayload.java b/src/java/com/android/ike/ikev2/message/IkeTsPayload.java
index 9763af2..8231e29 100644
--- a/src/java/com/android/ike/ikev2/message/IkeTsPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeTsPayload.java
@@ -16,6 +16,7 @@
package com.android.ike.ikev2.message;
+import com.android.ike.ikev2.IkeTrafficSelector;
import com.android.ike.ikev2.exceptions.IkeException;
import java.nio.ByteBuffer;
@@ -37,6 +38,7 @@
/** Number of Traffic Selectors */
public final int numTs;
+ public final IkeTrafficSelector[] trafficSelectors;
IkeTsPayload(boolean critical, byte[] payloadBody, boolean isInitiator) throws IkeException {
super((isInitiator ? PAYLOAD_TYPE_TS_INITIATOR : PAYLOAD_TYPE_TS_RESPONDER), critical);
@@ -46,7 +48,10 @@
// Skip RESERVED byte
inputBuffer.get(new byte[TS_HEADER_RESERVED_LEN]);
- // TODO: Decode Traffic Selectors.
+ // Decode Traffic Selectors
+ byte[] tsBytes = new byte[inputBuffer.remaining()];
+ inputBuffer.get(tsBytes);
+ trafficSelectors = IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
}
/**
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java
new file mode 100644
index 0000000..1982e62
--- /dev/null
+++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
+import com.android.ike.ikev2.message.TestUtils;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.Test;
+
+import java.net.Inet4Address;
+
+public final class IkeTrafficSelectorTest {
+ private static final String TS_IPV4_ONE_HEX_STRING = "070000100010fff0c0000264c0000365";
+ private static final int TS_ONE_START_PORT = 16;
+ private static final int TS_ONE_END_PORT = 65520;
+ private static final Inet4Address TS_ONE_START_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100"));
+ private static final Inet4Address TS_ONE_END_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.3.101"));
+
+ private static final String TS_IPV4_TWO_HEX_STRING = "070000100000ffffc0000464c0000466";
+ private static final int TS_TWO_START_PORT = 0;
+ private static final int TS_TWO_END_PORT = 65535;
+ private static final Inet4Address TS_TWO_START_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.100"));
+ private static final Inet4Address TS_TWO_END_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.102"));
+
+ private static final String TX_IPV4_INVALID_PORT_RANGE_HEX_STRING =
+ "0700001022221111c0000464c0000466";
+ private static final String TX_IPV4_INVALID_ADDRESS_RANGE_HEX_STRING =
+ "070000100000ffffc0000466c0000366";
+
+ private static final int TS_TYPE_OFFSET = 0;
+ private static final int PROTOCOL_ID_OFFSET = 1;
+ private static final int TS_LENGTH_OFFSET = 2;
+
+ @Test
+ public void testDecodeIkeTrafficSelectors() throws Exception {
+ int numTs = 2;
+
+ byte[] tsBytes =
+ TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING + TS_IPV4_TWO_HEX_STRING);
+ IkeTrafficSelector[] selectors =
+ IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+
+ assertEquals(numTs, selectors.length);
+
+ // Verify first traffic selector
+ IkeTrafficSelector tsOne = selectors[0];
+
+ assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, tsOne.tsType);
+ assertEquals(IkeTrafficSelector.IP_PROTOCOL_ID_UNSPEC, tsOne.ipProtocolId);
+ assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_IPV4_LEN, tsOne.selectorLength);
+ assertEquals(TS_ONE_START_PORT, tsOne.startPort);
+ assertEquals(TS_ONE_END_PORT, tsOne.endPort);
+ assertEquals(TS_ONE_START_ADDRESS, tsOne.startingAddress);
+ assertEquals(TS_ONE_END_ADDRESS, tsOne.endingAddress);
+
+ // Verify second traffic selector
+ IkeTrafficSelector tsTwo = selectors[1];
+
+ assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, tsTwo.tsType);
+ assertEquals(IkeTrafficSelector.IP_PROTOCOL_ID_UNSPEC, tsTwo.ipProtocolId);
+ assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_IPV4_LEN, tsTwo.selectorLength);
+ assertEquals(TS_TWO_START_PORT, tsTwo.startPort);
+ assertEquals(TS_TWO_END_PORT, tsTwo.endPort);
+ assertEquals(TS_TWO_START_ADDRESS, tsTwo.startingAddress);
+ assertEquals(TS_TWO_END_ADDRESS, tsTwo.endingAddress);
+ }
+
+ @Test
+ public void testDecodeIkeTrafficSelectorWithInvalidTsType() throws Exception {
+ int numTs = 1;
+ byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING);
+ tsBytes[TS_TYPE_OFFSET] = -1;
+
+ try {
+ IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+ fail("Expected to fail due to invalid Traffic Selector Type.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeIkeTrafficSelectorWithInvalidIpProtocol() throws Exception {
+ int numTs = 1;
+ byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING);
+ tsBytes[PROTOCOL_ID_OFFSET] = -1;
+
+ try {
+ IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+ fail("Expected to fail due to invalid IP Protocol ID.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeIkeTrafficSelectorWithExpectedTrailing() throws Exception {
+ int numTs = 1;
+ byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING + "FFFF");
+
+ try {
+ IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+ fail("Expected to fail due to unexpected trailing characters.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeIkeTrafficSelectorWithInvalidTsLength() throws Exception {
+ int numTs = 1;
+ byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING);
+
+ // Traffic Selector field is two octets
+ tsBytes[TS_LENGTH_OFFSET] = 0;
+ tsBytes[TS_LENGTH_OFFSET + 1] = 0;
+
+ try {
+ IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+ fail("Expected to fail due to invalid Traffic Selector length.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeIkeTrafficSelectorWithInvalidPortRange() throws Exception {
+ int numTs = 1;
+ byte[] tsBytes = TestUtils.hexStringToByteArray(TX_IPV4_INVALID_PORT_RANGE_HEX_STRING);
+
+ try {
+ IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+ fail("Expected to fail when start port is larger than end port.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeIkeTrafficSelectorWithInvalidAddressRange() throws Exception {
+ int numTs = 1;
+ byte[] tsBytes = TestUtils.hexStringToByteArray(TX_IPV4_INVALID_ADDRESS_RANGE_HEX_STRING);
+
+ try {
+ IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+ fail("Expected to fail when starting address is larger than ending address.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java
index 67f394f..f547526 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java
@@ -25,8 +25,8 @@
public final class IkeTsPayloadTest {
private static final String TS_INITIATOR_PAYLOAD_HEX_STRING =
- "2d00001801000000070000100000ffff00000000ffffffff";
- private static final int NUMBER_OF_TS = 1;
+ "2d00002802000000070000100000ffff00000000ffffffff070000100000ffff00000001fffffffe";
+ private static final int NUMBER_OF_TS = 2;
@Test
public void testDecodeTsInitiatorPayload() throws Exception {