aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/ike/ikev2/IkeSocket.java
blob: d973119dadc2672a09aab724445c63bee2cd2128 (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
/*
 * 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 android.system.OsConstants.F_SETFL;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_NONBLOCK;

import static com.android.ike.ikev2.IkeManager.getIkeLog;

import android.net.IpSecManager.UdpEncapsulationSocket;
import android.os.Handler;
import android.system.ErrnoException;
import android.system.Os;
import android.util.LongSparseArray;

import com.android.ike.ikev2.exceptions.IkeProtocolException;
import com.android.ike.ikev2.message.IkeHeader;
import com.android.ike.ikev2.utils.PacketReader;
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.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * IkeSocket sends and receives IKE packets via the user provided {@link UdpEncapsulationSocket}.
 *
 * <p>One UdpEncapsulationSocket instance can only be bound to one IkeSocket instance. IkeSocket
 * maintains a static map to cache all bound UdpEncapsulationSockets and their IkeSocket instances.
 * It returns the existing IkeSocket when it has been bound with user provided {@link
 * UdpEncapsulationSocket}.
 *
 * <p>As a packet receiver, IkeSocket registers a file descriptor with a thread's Looper and handles
 * read events (and errors). Users can expect a call life-cycle like the following:
 *
 * <pre>
 * [1] when user gets a new initiated IkeSocket, start() is called and followed by createFd().
 * [2] yield, waiting for a read event which will invoke handlePacket()
 * [3] when user closes this IkeSocket, its reference count decreases. Then stop() is called when
 *     there is no reference of this instance.
 * </pre>
 *
 * <p>IkeSocket is constructed and called only on a single IKE working thread by {@link
 * IkeSessionStateMachine}. Since all {@link IkeSessionStateMachine}s run on the same working
 * 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];

    // Map from UdpEncapsulationSocket to IkeSocket instances.
    private static Map<UdpEncapsulationSocket, IkeSocket> sFdToIkeSocketMap = new HashMap<>();

    private static IPacketReceiver sPacketReceiver = new PacketReceiver();

    // Package private map from locally generated IKE SPI to IkeSessionStateMachine instances.
    @VisibleForTesting
    final LongSparseArray<IkeSessionStateMachine> mSpiToIkeSession = new LongSparseArray<>();

    // Package private set to store all running IKE Sessions that are using this IkeSocket instance.
    @VisibleForTesting final Set<IkeSessionStateMachine> mAliveIkeSessions = new HashSet<>();

    // UdpEncapsulationSocket for sending and receving IKE packet.
    private final UdpEncapsulationSocket mUdpEncapSocket;

    private IkeSocket(UdpEncapsulationSocket udpEncapSocket, Handler handler) {
        super(handler);
        mUdpEncapSocket = udpEncapSocket;
    }

    /**
     * Get an IkeSocket instance.
     *
     * <p>Return the existing IkeSocket instance if it has been created for the input
     * udpEncapSocket. Otherwise, create and return a new IkeSocket instance.
     *
     * @param udpEncapSocket user provided UdpEncapsulationSocket
     * @param ikeSession the IkeSessionStateMachine that is requesting an IkeSocket.
     * @return an IkeSocket instance
     */
    public static IkeSocket getIkeSocket(
            UdpEncapsulationSocket udpEncapSocket, IkeSessionStateMachine ikeSession)
            throws ErrnoException {
        FileDescriptor fd = udpEncapSocket.getFileDescriptor();
        // All created IkeSocket has modified its FileDescriptor to non-blocking type for handling
        // read events in a non-blocking way.
        Os.fcntlInt(fd, F_SETFL, SOCK_DGRAM | SOCK_NONBLOCK);

        IkeSocket ikeSocket = null;
        if (sFdToIkeSocketMap.containsKey(udpEncapSocket)) {
            ikeSocket = sFdToIkeSocketMap.get(udpEncapSocket);

        } else {
            ikeSocket = new IkeSocket(udpEncapSocket, new Handler());
            // Create and register FileDescriptor for receiving IKE packet on current thread.
            ikeSocket.start();

            sFdToIkeSocketMap.put(udpEncapSocket, ikeSocket);
        }

        ikeSocket.mAliveIkeSessions.add(ikeSession);
        return ikeSocket;
    }

    /**
     * Get FileDecriptor of mUdpEncapSocket.
     *
     * <p>PacketReader registers a listener for this file descriptor on the thread where IkeSocket
     * is constructed. When there is a read event, this listener is invoked and then calls {@link
     * handlePacket} to handle the received packet.
     */
    @Override
    protected FileDescriptor createFd() {
        return mUdpEncapSocket.getFileDescriptor();
    }

    /**
     * IPacketReceiver provides a package private interface for handling received packet.
     *
     * <p>IPacketReceiver exists so that the interface is injectable for testing.
     */
    interface IPacketReceiver {
        void handlePacket(byte[] recvbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession);
    }

    /** Package private */
    @VisibleForTesting
    static final class PacketReceiver implements 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;
            }

            try {
                // Re-direct IKE packet to IkeSessionStateMachine according to the locally generated
                // IKE SPI.
                byte[] ikePacketBytes = new byte[byteBuffer.remaining()];
                byteBuffer.get(ikePacketBytes);

                // TODO: Retrieve and log the source address
                getIkeLog().d(TAG, "Receive packet of " + ikePacketBytes.length + " bytes)");
                getIkeLog().d(TAG, getIkeLog().pii(ikePacketBytes));

                IkeHeader ikeHeader = new IkeHeader(ikePacketBytes);

                long localGeneratedSpi =
                        ikeHeader.fromIkeInitiator
                                ? ikeHeader.ikeResponderSpi
                                : ikeHeader.ikeInitiatorSpi;

                IkeSessionStateMachine ikeStateMachine = spiToIkeSession.get(localGeneratedSpi);
                if (ikeStateMachine == null) {
                    getIkeLog().w(TAG, "Unrecognized IKE SPI.");
                    // TODO: Handle invalid IKE SPI error
                } else {
                    ikeStateMachine.receiveIkePacket(ikeHeader, ikePacketBytes);
                }
            } catch (IkeProtocolException e) {
                // Handle invalid IKE header
                getIkeLog().i(TAG, "Can't parse malformed IKE packet header.");
            }
        }
    }

    /** Package private */
    @VisibleForTesting
    static void setPacketReceiver(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
     */
    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, IKE_SERVER_PORT);
        } catch (ErrnoException | IOException e) {
            // TODO: Handle exception
        }
    }

    /**
     * Register new created IKE SA
     *
     * @param spi the locally generated IKE SPI
     * @param ikeSession the IKE session this IKE SA belongs to
     */
    public void registerIke(long spi, IkeSessionStateMachine ikeSession) {
        mSpiToIkeSession.put(spi, ikeSession);
    }

    /**
     * Unregister a deleted IKE SA
     *
     * @param spi the locally generated IKE SPI
     */
    public void unregisterIke(long spi) {
        mSpiToIkeSession.remove(spi);
    }

    /** Release reference of current IkeSocket when the IKE session is closed. */
    public void releaseReference(IkeSessionStateMachine ikeSession) {
        mAliveIkeSessions.remove(ikeSession);
        if (mAliveIkeSessions.isEmpty()) close();
    }

    /** Implement {@link AutoCloseable#close()} */
    @Override
    public void close() {
        sFdToIkeSocketMap.remove(mUdpEncapSocket);
        // PackeReader unregisters file descriptor on thread with which the Handler constructor
        // argument is associated.
        stop();
    }
}