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