• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.net.ipsec.ike;
18 
19 import static android.net.ipsec.ike.IkeManager.getIkeLog;
20 import static android.system.OsConstants.IPPROTO_IP;
21 import static android.system.OsConstants.IPPROTO_IPV6;
22 import static android.system.OsConstants.IPV6_TCLASS;
23 import static android.system.OsConstants.IP_TOS;
24 
25 import android.net.ipsec.ike.exceptions.IkeProtocolException;
26 import android.os.Handler;
27 import android.system.ErrnoException;
28 import android.system.Os;
29 import android.util.LongSparseArray;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.internal.net.ipsec.ike.message.IkeHeader;
33 
34 import java.io.FileDescriptor;
35 import java.io.FileInputStream;
36 import java.io.IOException;
37 import java.net.InetAddress;
38 import java.net.InetSocketAddress;
39 import java.util.Arrays;
40 import java.util.HashSet;
41 import java.util.Set;
42 
43 /**
44  * IkeSocket is used for sending and receiving IKE packets for {@link IkeSessionStateMachine}s.
45  *
46  * <p>To function as a packet receiver, a subclass MUST override #createFd() and #handlePacket() so
47  * that it can register a file descriptor with a thread's Looper and handle read events (and
48  * errors). Users can expect a call life-cycle like the following:
49  *
50  * <pre>
51  * [1] when user gets a new initiated IkeSocket, start() is called and followed by createFd().
52  * [2] yield, waiting for a read event which will invoke handlePacket()
53  * [3] when user closes this IkeSocket, its reference count decreases. Then stop() is called when
54  *     there is no reference of this instance.
55  * </pre>
56  *
57  * <p>IkeSocket is constructed and called only on a single IKE working thread by {@link
58  * IkeSessionStateMachine}. Since all {@link IkeSessionStateMachine}s run on the same working
59  * thread, there will not be concurrent modification problems.
60  */
61 public abstract class IkeSocket implements AutoCloseable {
62     private static final String TAG = "IkeSocket";
63 
64     /** Non-udp-encapsulated IKE packets MUST be sent to 500. */
65     public static final int SERVER_PORT_NON_UDP_ENCAPSULATED = 500;
66     /** UDP-encapsulated IKE packets MUST be sent to 4500. */
67     public static final int SERVER_PORT_UDP_ENCAPSULATED = 4500;
68 
69     private static final int RCV_BUFFER_SIZE = 4096;
70 
71     private final IkeSocketConfig mIkeSocketConfig;
72     private final Handler mHandler;
73 
74     // Map from locally generated IKE SPI to IkeSessionStateMachine instances.
75     @VisibleForTesting
76     protected final LongSparseArray<IkeSessionStateMachine> mSpiToIkeSession =
77             new LongSparseArray<>();
78 
79     // Set to store all running IkeSessionStateMachines that are using this IkeSocket instance.
80     @VisibleForTesting
81     protected final Set<IkeSessionStateMachine> mAliveIkeSessions = new HashSet<>();
82 
IkeSocket(IkeSocketConfig sockConfig, Handler handler)83     protected IkeSocket(IkeSocketConfig sockConfig, Handler handler) {
84         mHandler = handler;
85         mIkeSocketConfig = sockConfig;
86     }
87 
parseAndDemuxIkePacket( byte[] ikePacketBytes, LongSparseArray<IkeSessionStateMachine> spiToIkeSession, String tag)88     protected static void parseAndDemuxIkePacket(
89             byte[] ikePacketBytes,
90             LongSparseArray<IkeSessionStateMachine> spiToIkeSession,
91             String tag) {
92         try {
93             // TODO: Retrieve and log the source address
94             getIkeLog().d(tag, "Receive packet of " + ikePacketBytes.length + " bytes)");
95             getIkeLog().d(tag, getIkeLog().pii(ikePacketBytes));
96 
97             IkeHeader ikeHeader = new IkeHeader(ikePacketBytes);
98 
99             long localGeneratedSpi =
100                     ikeHeader.fromIkeInitiator
101                             ? ikeHeader.ikeResponderSpi
102                             : ikeHeader.ikeInitiatorSpi;
103 
104             IkeSessionStateMachine ikeStateMachine = spiToIkeSession.get(localGeneratedSpi);
105             if (ikeStateMachine == null) {
106                 getIkeLog().w(tag, "Unrecognized IKE SPI.");
107                 // TODO(b/148479270): Handle invalid IKE SPI error
108             } else {
109                 ikeStateMachine.receiveIkePacket(ikeHeader, ikePacketBytes);
110             }
111         } catch (IkeProtocolException e) {
112             // Handle invalid IKE header
113             getIkeLog().i(tag, "Can't parse malformed IKE packet header.");
114         }
115     }
116 
117     /** Applies a socket configuration to an input socket. */
applySocketConfig( IkeSocketConfig sockConfig, FileDescriptor sock, boolean isIpv6)118     protected static void applySocketConfig(
119             IkeSocketConfig sockConfig, FileDescriptor sock, boolean isIpv6)
120             throws ErrnoException, IOException {
121         sockConfig.getNetwork().bindSocket(sock);
122         if (isIpv6) {
123             // Traffic class field consists of a 6-bit Differentiated Services Code Point (DSCP)
124             // field and a 2-bit Explicit Congestion Notification (ECN) field.
125             final int tClass = sockConfig.getDscp() << 2;
126             Os.setsockoptInt(sock, IPPROTO_IPV6, IPV6_TCLASS, tClass);
127         } else {
128             // TOS field consists of a 6-bit Differentiated Services Code Point (DSCP) field and a
129             // 2-bit Explicit Congestion Notification (ECN) field.
130             final int tos = sockConfig.getDscp() << 2;
131             Os.setsockoptInt(sock, IPPROTO_IP, IP_TOS, tos);
132         }
133     }
134 
135     /** Starts the packet reading poll-loop. */
start()136     public void start() {
137         // Start background reader thread
138         new Thread(
139                 () -> {
140                     try {
141                         // Loop will exit and thread will quit when the retrieved fd is closed.
142                         // Receiving either EOF or an exception will exit this reader loop.
143                         // FileInputStream in uninterruptable, so there's no good way to ensure that
144                         // that this thread shuts down except upon FD closure.
145                         while (true) {
146                             byte[] intercepted = receiveFromFd();
147                             if (intercepted == null) {
148                                 // Exit once we've hit EOF
149                                 return;
150                             } else if (intercepted.length > 0) {
151                                 // Only save packet if we've received any bytes.
152                                 getIkeLog()
153                                         .d(
154                                                 this.getClass().getSimpleName(),
155                                                 "Received packet");
156                                 mHandler.post(
157                                         () -> {
158                                             handlePacket(intercepted, intercepted.length);
159                                         });
160                             }
161                         }
162                     } catch (IOException ignored) {
163                         // Simply exit this reader thread
164                         return;
165                     }
166                 }).start();
167     }
168 
receiveFromFd()169     private byte[] receiveFromFd() throws IOException {
170         FileInputStream in = new FileInputStream(getFd());
171         byte[] inBytes = new byte[RCV_BUFFER_SIZE];
172         int bytesRead = in.read(inBytes);
173 
174         if (bytesRead < 0) {
175             return null; // return null for EOF
176         } else if (bytesRead >= RCV_BUFFER_SIZE) {
177             // This packet should not affect any IKE Session because it is not an authenticated IKE
178             // packet.
179             getIkeLog()
180                     .e(
181                             this.getClass().getSimpleName(),
182                             "Too big packet. Fragmentation unsupported.");
183             return new byte[0];
184         }
185         return Arrays.copyOf(inBytes, bytesRead);
186     }
187 
188     /**
189      * Returns the port that this IKE socket is listening on (bound to).
190      */
getLocalPort()191     public final int getLocalPort() throws ErrnoException {
192         InetSocketAddress localAddr = (InetSocketAddress) Os.getsockname(getFd());
193         return localAddr.getPort();
194     }
195 
getFd()196     protected abstract FileDescriptor getFd();
197 
createFd()198     protected FileDescriptor createFd() {
199         return getFd();
200     }
201 
handlePacket(byte[] recvbuf, int length)202     protected abstract void handlePacket(byte[] recvbuf, int length);
203 
204     /** Return the IkeSocketConfig */
getIkeSocketConfig()205     public final IkeSocketConfig getIkeSocketConfig() {
206         return mIkeSocketConfig;
207     }
208 
209     /**
210      * Register new created IKE SA
211      *
212      * @param spi the locally generated IKE SPI
213      * @param ikeSession the IKE session this IKE SA belongs to
214      */
registerIke(long spi, IkeSessionStateMachine ikeSession)215     public final void registerIke(long spi, IkeSessionStateMachine ikeSession) {
216         mSpiToIkeSession.put(spi, ikeSession);
217     }
218 
219     /**
220      * Unregister a deleted IKE SA
221      *
222      * @param spi the locally generated IKE SPI
223      */
unregisterIke(long spi)224     public final void unregisterIke(long spi) {
225         mSpiToIkeSession.remove(spi);
226     }
227 
228     /**
229      * Release reference of current IkeSocket when the IKE session no longer needs it.
230      *
231      * @param ikeSession IKE session that is being closed.
232      */
releaseReference(IkeSessionStateMachine ikeSession)233     public final void releaseReference(IkeSessionStateMachine ikeSession) {
234         mAliveIkeSessions.remove(ikeSession);
235         if (mAliveIkeSessions.isEmpty()) close();
236     }
237 
238     /**
239      * Send an encoded IKE packet to destination address
240      *
241      * @param ikePacket encoded IKE packet
242      * @param serverAddress IP address of remote server
243      */
sendIkePacket(byte[] ikePacket, InetAddress serverAddress)244     public abstract void sendIkePacket(byte[] ikePacket, InetAddress serverAddress);
245 
246     /**
247      * Returns port of remote IKE sever (destination port of outbound packet)
248      *
249      * @return destination port in remote IKE sever.
250      */
getIkeServerPort()251     public abstract int getIkeServerPort();
252 
253     /** Implement {@link AutoCloseable#close()} */
254     @Override
close()255     public void close() {
256         stop();
257     }
258 
259     /** Stops the packet reading loop */
stop()260     public void stop() {
261         // No additional cleanup at this time. Subclasses are in charge of closing their sockets,
262         // which will result in the packet reading poll loop exiting.
263     }
264 
265     /**
266      * IPacketReceiver provides a package private interface for handling received packet.
267      *
268      * <p>IPacketReceiver exists so that the interface is injectable for testing.
269      */
270     interface IPacketReceiver {
handlePacket(byte[] recvbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession)271         void handlePacket(byte[] recvbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession);
272     }
273 }
274