blob: baebe64733d156069605be9b64ccbf38e562ac9c [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 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;
}
}