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