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