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