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 package com.android.server.connectivity; 17 18 import static android.net.SocketKeepalive.DATA_RECEIVED; 19 import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS; 20 import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET; 21 import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE; 22 import static android.net.SocketKeepalive.ERROR_UNSUPPORTED; 23 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; 24 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; 25 import static android.system.OsConstants.ENOPROTOOPT; 26 import static android.system.OsConstants.FIONREAD; 27 import static android.system.OsConstants.IPPROTO_IP; 28 import static android.system.OsConstants.IPPROTO_TCP; 29 import static android.system.OsConstants.IP_TOS; 30 import static android.system.OsConstants.IP_TTL; 31 import static android.system.OsConstants.TIOCOUTQ; 32 33 import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; 34 35 import android.annotation.NonNull; 36 import android.net.InvalidPacketException; 37 import android.net.NetworkUtils; 38 import android.net.SocketKeepalive.InvalidSocketException; 39 import android.net.TcpKeepalivePacketData; 40 import android.net.TcpKeepalivePacketDataParcelable; 41 import android.net.TcpRepairWindow; 42 import android.os.Handler; 43 import android.os.MessageQueue; 44 import android.os.Messenger; 45 import android.system.ErrnoException; 46 import android.system.Os; 47 import android.util.Log; 48 import android.util.SparseArray; 49 50 import com.android.internal.annotations.GuardedBy; 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.net.module.util.IpUtils; 53 import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo; 54 55 import java.io.FileDescriptor; 56 import java.net.InetAddress; 57 import java.net.InetSocketAddress; 58 import java.net.SocketAddress; 59 import java.net.SocketException; 60 import java.net.UnknownHostException; 61 import java.nio.ByteBuffer; 62 import java.nio.ByteOrder; 63 64 /** 65 * Manage tcp socket which offloads tcp keepalive. 66 * 67 * The input socket will be changed to repair mode and the application 68 * will not have permission to read/write data. If the application wants 69 * to write data, it must stop tcp keepalive offload to leave repair mode 70 * first. If a remote packet arrives, repair mode will be turned off and 71 * offload will be stopped. The application will receive a callback to know 72 * it can start reading data. 73 * 74 * {start,stop}SocketMonitor are thread-safe, but care must be taken in the 75 * order in which they are called. Please note that while calling 76 * {@link #startSocketMonitor(FileDescriptor, Messenger, int)} multiple times 77 * with either the same slot or the same FileDescriptor without stopping it in 78 * between will result in an exception, calling {@link #stopSocketMonitor(int)} 79 * multiple times with the same int is explicitly a no-op. 80 * Please also note that switching the socket to repair mode is not synchronized 81 * with either of these operations and has to be done in an orderly fashion 82 * with stopSocketMonitor. Take care in calling these in the right order. 83 * @hide 84 */ 85 public class TcpKeepaliveController { 86 private static final String TAG = "TcpKeepaliveController"; 87 private static final boolean DBG = false; 88 89 private final MessageQueue mFdHandlerQueue; 90 91 private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; 92 93 private static final int TCP_HEADER_LENGTH = 20; 94 95 // Reference include/uapi/linux/tcp.h 96 private static final int TCP_REPAIR = 19; 97 private static final int TCP_REPAIR_QUEUE = 20; 98 private static final int TCP_QUEUE_SEQ = 21; 99 private static final int TCP_NO_QUEUE = 0; 100 private static final int TCP_RECV_QUEUE = 1; 101 private static final int TCP_SEND_QUEUE = 2; 102 private static final int TCP_REPAIR_OFF = 0; 103 private static final int TCP_REPAIR_ON = 1; 104 // Reference include/uapi/linux/sockios.h 105 private static final int SIOCINQ = FIONREAD; 106 private static final int SIOCOUTQ = TIOCOUTQ; 107 108 /** 109 * Keeps track of packet listeners. 110 * Key: slot number of keepalive offload. 111 * Value: {@link FileDescriptor} being listened to. 112 */ 113 @GuardedBy("mListeners") 114 private final SparseArray<FileDescriptor> mListeners = new SparseArray<>(); 115 TcpKeepaliveController(final Handler connectivityServiceHandler)116 public TcpKeepaliveController(final Handler connectivityServiceHandler) { 117 mFdHandlerQueue = connectivityServiceHandler.getLooper().getQueue(); 118 } 119 120 /** Build tcp keepalive packet. */ getTcpKeepalivePacket(@onNull FileDescriptor fd)121 public static TcpKeepalivePacketData getTcpKeepalivePacket(@NonNull FileDescriptor fd) 122 throws InvalidPacketException, InvalidSocketException { 123 try { 124 final TcpKeepalivePacketDataParcelable tcpDetails = switchToRepairMode(fd); 125 // TODO: consider building a TcpKeepalivePacketData directly from switchToRepairMode 126 return fromStableParcelable(tcpDetails); 127 // Use separate catch blocks: a combined catch would get wrongly optimized by R8 128 // (b/226127213). 129 } catch (InvalidSocketException e) { 130 switchOutOfRepairMode(fd); 131 throw e; 132 } catch (InvalidPacketException e) { 133 switchOutOfRepairMode(fd); 134 throw e; 135 } 136 } 137 138 /** 139 * Factory method to create tcp keepalive packet structure. 140 */ 141 @VisibleForTesting fromStableParcelable( TcpKeepalivePacketDataParcelable tcpDetails)142 public static TcpKeepalivePacketData fromStableParcelable( 143 TcpKeepalivePacketDataParcelable tcpDetails) throws InvalidPacketException { 144 final byte[] packet; 145 try { 146 if ((tcpDetails.srcAddress != null) && (tcpDetails.dstAddress != null) 147 && (tcpDetails.srcAddress.length == 4 /* V4 IP length */) 148 && (tcpDetails.dstAddress.length == 4 /* V4 IP length */)) { 149 packet = buildV4Packet(tcpDetails); 150 } else { 151 // TODO: support ipv6 152 throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); 153 } 154 return new TcpKeepalivePacketData( 155 InetAddress.getByAddress(tcpDetails.srcAddress), 156 tcpDetails.srcPort, 157 InetAddress.getByAddress(tcpDetails.dstAddress), 158 tcpDetails.dstPort, 159 packet, 160 tcpDetails.seq, tcpDetails.ack, tcpDetails.rcvWnd, tcpDetails.rcvWndScale, 161 tcpDetails.tos, tcpDetails.ttl); 162 } catch (UnknownHostException e) { 163 throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); 164 } 165 } 166 167 /** 168 * Build ipv4 tcp keepalive packet, not including the link-layer header. 169 */ 170 // TODO : if this code is ever moved to the network stack, factorize constants with the ones 171 // over there. 172 // TODO: consider using Ipv4Utils.buildTcpv4Packet() instead buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails)173 private static byte[] buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails) { 174 final int length = IPV4_HEADER_MIN_LEN + TCP_HEADER_LENGTH; 175 ByteBuffer buf = ByteBuffer.allocate(length); 176 buf.order(ByteOrder.BIG_ENDIAN); 177 buf.put((byte) 0x45); // IP version and IHL 178 buf.put((byte) tcpDetails.tos); // TOS 179 buf.putShort((short) length); 180 buf.putInt(0x00004000); // ID, flags=DF, offset 181 buf.put((byte) tcpDetails.ttl); // TTL 182 buf.put((byte) IPPROTO_TCP); 183 final int ipChecksumOffset = buf.position(); 184 buf.putShort((short) 0); // IP checksum 185 buf.put(tcpDetails.srcAddress); 186 buf.put(tcpDetails.dstAddress); 187 buf.putShort((short) tcpDetails.srcPort); 188 buf.putShort((short) tcpDetails.dstPort); 189 buf.putInt(tcpDetails.seq); // Sequence Number 190 buf.putInt(tcpDetails.ack); // ACK 191 buf.putShort((short) 0x5010); // TCP length=5, flags=ACK 192 buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale)); // Window size 193 final int tcpChecksumOffset = buf.position(); 194 buf.putShort((short) 0); // TCP checksum 195 // URG is not set therefore the urgent pointer is zero. 196 buf.putShort((short) 0); // Urgent pointer 197 198 buf.putShort(ipChecksumOffset, com.android.net.module.util.IpUtils.ipChecksum(buf, 0)); 199 buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum( 200 buf, 0, IPV4_HEADER_MIN_LEN, TCP_HEADER_LENGTH)); 201 202 return buf.array(); 203 } 204 205 /** 206 * Switch the tcp socket to repair mode and query detail tcp information. 207 * 208 * @param fd the fd of socket on which to use keepalive offload. 209 * @return a {@link TcpKeepalivePacketDataParcelable} object for current 210 * tcp/ip information. 211 */ switchToRepairMode(FileDescriptor fd)212 private static TcpKeepalivePacketDataParcelable switchToRepairMode(FileDescriptor fd) 213 throws InvalidSocketException { 214 if (DBG) Log.i(TAG, "switchToRepairMode to start tcp keepalive : " + fd); 215 final TcpKeepalivePacketDataParcelable tcpDetails = new TcpKeepalivePacketDataParcelable(); 216 final SocketAddress srcSockAddr; 217 final SocketAddress dstSockAddr; 218 final TcpRepairWindow trw; 219 220 // Query source address and port. 221 try { 222 srcSockAddr = Os.getsockname(fd); 223 } catch (ErrnoException e) { 224 Log.e(TAG, "Get sockname fail: ", e); 225 throw new InvalidSocketException(ERROR_INVALID_SOCKET, e); 226 } 227 if (srcSockAddr instanceof InetSocketAddress) { 228 tcpDetails.srcAddress = getAddress((InetSocketAddress) srcSockAddr); 229 tcpDetails.srcPort = getPort((InetSocketAddress) srcSockAddr); 230 } else { 231 Log.e(TAG, "Invalid or mismatched SocketAddress"); 232 throw new InvalidSocketException(ERROR_INVALID_SOCKET); 233 } 234 // Query destination address and port. 235 try { 236 dstSockAddr = Os.getpeername(fd); 237 } catch (ErrnoException e) { 238 Log.e(TAG, "Get peername fail: ", e); 239 throw new InvalidSocketException(ERROR_INVALID_SOCKET, e); 240 } 241 if (dstSockAddr instanceof InetSocketAddress) { 242 tcpDetails.dstAddress = getAddress((InetSocketAddress) dstSockAddr); 243 tcpDetails.dstPort = getPort((InetSocketAddress) dstSockAddr); 244 } else { 245 Log.e(TAG, "Invalid or mismatched peer SocketAddress"); 246 throw new InvalidSocketException(ERROR_INVALID_SOCKET); 247 } 248 249 // Query sequence and ack number 250 dropAllIncomingPackets(fd, true); 251 try { 252 // Switch to tcp repair mode. 253 Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_ON); 254 255 // Check if socket is idle. 256 if (!isSocketIdle(fd)) { 257 Log.e(TAG, "Socket is not idle"); 258 throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE); 259 } 260 // Query write sequence number from SEND_QUEUE. 261 Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE); 262 tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ); 263 // Query read sequence number from RECV_QUEUE. 264 Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE); 265 tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ); 266 // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode. 267 Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE); 268 // Finally, check if socket is still idle. TODO : this check needs to move to 269 // after starting polling to prevent a race. 270 if (!isReceiveQueueEmpty(fd)) { 271 Log.e(TAG, "Fatal: receive queue of this socket is not empty"); 272 throw new InvalidSocketException(ERROR_INVALID_SOCKET); 273 } 274 if (!isSendQueueEmpty(fd)) { 275 Log.e(TAG, "Socket is not idle"); 276 throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE); 277 } 278 279 // Query tcp window size. 280 trw = NetworkUtils.getTcpRepairWindow(fd); 281 tcpDetails.rcvWnd = trw.rcvWnd; 282 tcpDetails.rcvWndScale = trw.rcvWndScale; 283 if (tcpDetails.srcAddress.length == 4 /* V4 address length */) { 284 // Query TOS. 285 tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS); 286 // Query TTL. 287 tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL); 288 } 289 } catch (ErrnoException e) { 290 Log.e(TAG, "Exception reading TCP state from socket", e); 291 if (e.errno == ENOPROTOOPT) { 292 // ENOPROTOOPT may happen in kernel version lower than 4.8. 293 // Treat it as ERROR_UNSUPPORTED. 294 throw new InvalidSocketException(ERROR_UNSUPPORTED, e); 295 } else { 296 throw new InvalidSocketException(ERROR_INVALID_SOCKET, e); 297 } 298 } finally { 299 dropAllIncomingPackets(fd, false); 300 } 301 302 // Keepalive sequence number is last sequence number - 1. If it couldn't be retrieved, 303 // then it must be set to -1, so decrement in all cases. 304 tcpDetails.seq = tcpDetails.seq - 1; 305 306 return tcpDetails; 307 } 308 309 /** 310 * Switch the tcp socket out of repair mode. 311 * 312 * @param fd the fd of socket to switch back to normal. 313 */ switchOutOfRepairMode(@onNull final FileDescriptor fd)314 private static void switchOutOfRepairMode(@NonNull final FileDescriptor fd) { 315 try { 316 Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF); 317 } catch (ErrnoException e) { 318 Log.e(TAG, "Cannot switch socket out of repair mode", e); 319 // Well, there is not much to do here to recover 320 } 321 } 322 323 /** 324 * Start monitoring incoming packets. 325 * 326 * @param fd socket fd to monitor. 327 * @param ki a {@link KeepaliveInfo} that tracks information about a socket keepalive. 328 * @param slot keepalive slot. 329 */ startSocketMonitor(@onNull final FileDescriptor fd, @NonNull final KeepaliveInfo ki, final int slot)330 public void startSocketMonitor(@NonNull final FileDescriptor fd, 331 @NonNull final KeepaliveInfo ki, final int slot) 332 throws IllegalArgumentException, InvalidSocketException { 333 synchronized (mListeners) { 334 if (null != mListeners.get(slot)) { 335 throw new IllegalArgumentException("This slot is already taken"); 336 } 337 for (int i = 0; i < mListeners.size(); ++i) { 338 if (fd.equals(mListeners.valueAt(i))) { 339 Log.e(TAG, "This fd is already registered."); 340 throw new InvalidSocketException(ERROR_INVALID_SOCKET); 341 } 342 } 343 mFdHandlerQueue.addOnFileDescriptorEventListener(fd, FD_EVENTS, (readyFd, events) -> { 344 // This can't be called twice because the queue guarantees that once the listener 345 // is unregistered it can't be called again, even for a message that arrived 346 // before it was unregistered. 347 final int reason; 348 if (0 != (events & EVENT_ERROR)) { 349 reason = ERROR_INVALID_SOCKET; 350 } else { 351 reason = DATA_RECEIVED; 352 } 353 ki.onFileDescriptorInitiatedStop(reason); 354 // The listener returns the new set of events to listen to. Because 0 means no 355 // event, the listener gets unregistered. 356 return 0; 357 }); 358 mListeners.put(slot, fd); 359 } 360 } 361 362 /** Stop socket monitor */ 363 // This slot may have been stopped automatically already because the socket received data, 364 // was closed on the other end or otherwise suffered some error. In this case, this function 365 // is a no-op. stopSocketMonitor(final int slot)366 public void stopSocketMonitor(final int slot) { 367 final FileDescriptor fd; 368 synchronized (mListeners) { 369 fd = mListeners.get(slot); 370 if (null == fd) return; 371 mListeners.remove(slot); 372 } 373 mFdHandlerQueue.removeOnFileDescriptorEventListener(fd); 374 if (DBG) Log.d(TAG, "Moving socket out of repair mode for stop : " + fd); 375 switchOutOfRepairMode(fd); 376 } 377 getAddress(InetSocketAddress inetAddr)378 private static byte [] getAddress(InetSocketAddress inetAddr) { 379 return inetAddr.getAddress().getAddress(); 380 } 381 getPort(InetSocketAddress inetAddr)382 private static int getPort(InetSocketAddress inetAddr) { 383 return inetAddr.getPort(); 384 } 385 isSocketIdle(FileDescriptor fd)386 private static boolean isSocketIdle(FileDescriptor fd) throws ErrnoException { 387 return isReceiveQueueEmpty(fd) && isSendQueueEmpty(fd); 388 } 389 isReceiveQueueEmpty(FileDescriptor fd)390 private static boolean isReceiveQueueEmpty(FileDescriptor fd) 391 throws ErrnoException { 392 final int result = Os.ioctlInt(fd, SIOCINQ); 393 if (result != 0) { 394 Log.e(TAG, "Read queue has data"); 395 return false; 396 } 397 return true; 398 } 399 isSendQueueEmpty(FileDescriptor fd)400 private static boolean isSendQueueEmpty(FileDescriptor fd) 401 throws ErrnoException { 402 final int result = Os.ioctlInt(fd, SIOCOUTQ); 403 if (result != 0) { 404 Log.e(TAG, "Write queue has data"); 405 return false; 406 } 407 return true; 408 } 409 dropAllIncomingPackets(FileDescriptor fd, boolean enable)410 private static void dropAllIncomingPackets(FileDescriptor fd, boolean enable) 411 throws InvalidSocketException { 412 try { 413 if (enable) { 414 NetworkUtils.attachDropAllBPFFilter(fd); 415 } else { 416 NetworkUtils.detachBPFFilter(fd); 417 } 418 } catch (SocketException e) { 419 Log.e(TAG, "Socket Exception: ", e); 420 throw new InvalidSocketException(ERROR_INVALID_SOCKET, e); 421 } 422 } 423 } 424