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