aboutsummaryrefslogtreecommitdiff
path: root/src/java/android/net/ipsec/ike/IkeTrafficSelector.java
blob: 65154b9decee108ca0bcd691cb494eab63e41d16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/*
 * 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 android.net.ipsec.ike;

import android.annotation.IntDef;
import android.util.ArraySet;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.Objects;

/**
 * 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_UDP})
    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_UDP = 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_UDP);
    }

    /**
     * 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;

    public static final int PORT_NUMBER_MIN = 0;
    public static final int PORT_NUMBER_MAX = 65535;

    // 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;
    }

    /**
     * Construct an instance of IkeTrafficSelector for building an outbound IKE message.
     *
     * @param tsType the Traffic Selector type.
     * @param startPort the smallest port number allowed by this Traffic Selector.
     * @param endPort the largest port number allowed by this Traffic Selector.
     * @param startingAddress the smallest address included in this Traffic Selector.
     * @param endingAddress the largest address included in this Traffic Selector.
     */
    public IkeTrafficSelector(
            @TrafficSelectorType int tsType,
            int startPort,
            int endPort,
            InetAddress startingAddress,
            InetAddress endingAddress) {

        this.tsType = tsType;
        this.ipProtocolId = IP_PROTOCOL_ID_UNSPEC;

        switch (tsType) {
            case TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE:
                this.selectorLength = TRAFFIC_SELECTOR_IPV4_LEN;

                if (!(startingAddress instanceof Inet4Address)
                        || !(endingAddress instanceof Inet4Address)) {
                    throw new IllegalArgumentException(
                            "Invalid address range: TS_IPV4_ADDR_RANGE requires IPv4 addresses.");
                }

                break;
            case TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE:
                throw new UnsupportedOperationException("Do not support IPv6 Traffic Selector.");
                // TODO: Support IPv6 Traffic Selector.
            default:
                throw new IllegalArgumentException("Unrecognized Traffic Selector type.");
        }

        if (compareInetAddressTo(startingAddress, endingAddress) > 0) {
            throw new IllegalArgumentException("Received invalid address range.");
        }

        if (!isPortRangeValid(startPort, endPort)) {
            throw new IllegalArgumentException(
                    "Invalid port range. startPort: " + startPort + " endPort: " + endPort);
        }

        this.startPort = startPort;
        this.endPort = endPort;
        this.startingAddress = startingAddress;
        this.endingAddress = endingAddress;
    }

    /**
     * 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 (!isPortRangeValid(startPort, endPort)) {
            throw new InvalidSyntaxException(
                    "Received invalid port range. startPort: "
                            + startPort
                            + " endPort: "
                            + endPort);
        }

        // 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 (compareInetAddressTo(startAddress, endAddress) > 0) {
                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 port range.
    private static boolean isPortRangeValid(int startPort, int endPort) {
        return (startPort >= PORT_NUMBER_MIN
                && startPort <= PORT_NUMBER_MAX
                && endPort >= PORT_NUMBER_MIN
                && endPort <= PORT_NUMBER_MAX
                && startPort <= endPort);
    }

    // Compare two InetAddresses. Return -1 if the first input is smaller; 1 if the second input is
    // smaller; 0 if two addresses are equal.
    // TODO: Consider moving it to the platform code in the future./
    private static int compareInetAddressTo(InetAddress leftAddress, InetAddress rightAddress) {
        byte[] leftAddrBytes = leftAddress.getAddress();
        byte[] rightAddrBytes = rightAddress.getAddress();

        if (leftAddrBytes.length != rightAddrBytes.length) {
            throw new IllegalArgumentException("Two addresses are different types.");
        }

        for (int i = 0; i < leftAddrBytes.length; i++) {
            int unsignedByteLeft = Byte.toUnsignedInt(leftAddrBytes[i]);
            int unsignedByteRight = Byte.toUnsignedInt(rightAddrBytes[i]);

            int result = Integer.compare(unsignedByteLeft, unsignedByteRight);
            if (result != 0) return result;
        }
        return 0;
    }

    /**
     * Check if the input IkeTrafficSelector is a subset of this instance.
     *
     * @param ts the provided IkeTrafficSelector to check.
     * @return true if the input IkeTrafficSelector is a subset of this instance, otherwise false.
     */
    public boolean contains(IkeTrafficSelector ts) {
        if (tsType == ts.tsType
                && ipProtocolId == ts.ipProtocolId
                && startPort <= ts.startPort
                && endPort >= ts.endPort
                && compareInetAddressTo(startingAddress, ts.startingAddress) <= 0
                && compareInetAddressTo(endingAddress, ts.endingAddress) >= 0) {
            return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                tsType,
                ipProtocolId,
                selectorLength,
                startPort,
                endPort,
                startingAddress,
                endingAddress);
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof IkeTrafficSelector)) return false;

        IkeTrafficSelector other = (IkeTrafficSelector) o;

        if (tsType != other.tsType
                || ipProtocolId != other.ipProtocolId
                || startPort != other.startPort
                || endPort != other.endPort) {
            return false;
        }

        switch (tsType) {
            case TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE:
                return (((Inet4Address) startingAddress)
                                .equals((Inet4Address) other.startingAddress)
                        && ((Inet4Address) endingAddress)
                                .equals((Inet4Address) other.endingAddress));
            case TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE:
                return (((Inet6Address) startingAddress)
                                .equals((Inet6Address) other.startingAddress)
                        && ((Inet6Address) endingAddress)
                                .equals((Inet6Address) other.endingAddress));
            default:
                throw new UnsupportedOperationException("Unrecognized TS type");
        }
    }

    /**
     * Encode traffic selector to ByteBuffer.
     *
     * <p>This method will be only called by IkeTsPayload for building an outbound IKE message.
     *
     * @param byteBuffer destination ByteBuffer that stores encoded traffic selector.
     */
    public void encodeToByteBuffer(ByteBuffer byteBuffer) {
        byteBuffer
                .put((byte) tsType)
                .put((byte) ipProtocolId)
                .putShort((short) selectorLength)
                .putShort((short) startPort)
                .putShort((short) endPort)
                .put(startingAddress.getAddress())
                .put(endingAddress.getAddress());
    }
}