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.networkstack.netlink; 17 18 import static android.net.util.DataStallUtils.CONFIG_MIN_PACKETS_THRESHOLD; 19 import static android.net.util.DataStallUtils.CONFIG_TCP_PACKETS_FAIL_PERCENTAGE; 20 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_MIN_PACKETS_THRESHOLD; 21 import static android.net.util.DataStallUtils.DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE; 22 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; 23 import static android.system.OsConstants.AF_INET; 24 import static android.system.OsConstants.AF_INET6; 25 import static android.system.OsConstants.SOL_SOCKET; 26 import static android.system.OsConstants.SO_SNDTIMEO; 27 28 import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE; 29 import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE; 30 import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY; 31 import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE; 32 import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS; 33 34 import android.content.BroadcastReceiver; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.IntentFilter; 38 import android.net.INetd; 39 import android.net.MarkMaskParcel; 40 import android.net.Network; 41 import android.os.AsyncTask; 42 import android.os.Build; 43 import android.os.IBinder; 44 import android.os.PowerManager; 45 import android.os.RemoteException; 46 import android.os.SystemClock; 47 import android.provider.DeviceConfig; 48 import android.system.ErrnoException; 49 import android.system.Os; 50 import android.system.StructTimeval; 51 import android.util.Log; 52 import android.util.LongSparseArray; 53 import android.util.SparseArray; 54 55 import androidx.annotation.NonNull; 56 import androidx.annotation.Nullable; 57 58 import com.android.internal.annotations.GuardedBy; 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.net.module.util.DeviceConfigUtils; 61 import com.android.net.module.util.SocketUtils; 62 import com.android.net.module.util.netlink.InetDiagMessage; 63 import com.android.net.module.util.netlink.NetlinkConstants; 64 import com.android.net.module.util.netlink.NetlinkUtils; 65 import com.android.net.module.util.netlink.StructInetDiagMsg; 66 import com.android.net.module.util.netlink.StructNlMsgHdr; 67 import com.android.networkstack.apishim.NetworkShimImpl; 68 import com.android.networkstack.apishim.common.ShimUtils; 69 import com.android.networkstack.apishim.common.UnsupportedApiLevelException; 70 71 import java.io.FileDescriptor; 72 import java.io.InterruptedIOException; 73 import java.net.SocketException; 74 import java.nio.BufferUnderflowException; 75 import java.nio.ByteBuffer; 76 import java.util.ArrayList; 77 import java.util.Base64; 78 import java.util.List; 79 80 /** 81 * Class for NetworkStack to send a SockDiag request and parse the returned tcp info. 82 * 83 * This is not thread-safe. This should be only accessed from one thread. 84 */ 85 public class TcpSocketTracker { 86 private static final String TAG = TcpSocketTracker.class.getSimpleName(); 87 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 88 private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET}; 89 90 /** Cookie offset of an InetMagMessage header. */ 91 private static final int IDIAG_COOKIE_OFFSET = 44; 92 private static final int END_OF_PARSING = -1; 93 /** 94 * Gather the socket info. 95 * 96 * Key: The idiag_cookie value of the socket. See struct inet_diag_sockid in 97 * <linux_src>/include/uapi/linux/inet_diag.h 98 * Value: See {@Code SocketInfo} 99 */ 100 private final LongSparseArray<SocketInfo> mSocketInfos = new LongSparseArray<>(); 101 // Number of packets sent since the last received packet 102 private int mSentSinceLastRecv; 103 // The latest fail rate calculated by the latest tcp info. 104 private int mLatestPacketFailPercentage; 105 // Number of packets received in the latest polling cycle. 106 private int mLatestReceivedCount; 107 /** 108 * Request to send to kernel to request tcp info. 109 * 110 * Key: Ip family type. 111 * Value: Bytes array represent the {@Code inetDiagReqV2}. 112 */ 113 private final SparseArray<byte[]> mSockDiagMsg = new SparseArray<>(); 114 private final Dependencies mDependencies; 115 private final INetd mNetd; 116 private final Network mNetwork; 117 // The fwmark value of {@code mNetwork}. 118 private final int mNetworkMark; 119 // The network id mask of fwmark. 120 private final int mNetworkMask; 121 private int mMinPacketsThreshold = DEFAULT_DATA_STALL_MIN_PACKETS_THRESHOLD; 122 private int mTcpPacketsFailRateThreshold = DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE; 123 124 private final Object mDozeModeLock = new Object(); 125 @GuardedBy("mDozeModeLock") 126 private boolean mInDozeMode = false; 127 @VisibleForTesting 128 protected final DeviceConfig.OnPropertiesChangedListener mConfigListener = 129 new DeviceConfig.OnPropertiesChangedListener() { 130 @Override 131 public void onPropertiesChanged(DeviceConfig.Properties properties) { 132 mMinPacketsThreshold = mDependencies.getDeviceConfigPropertyInt( 133 NAMESPACE_CONNECTIVITY, 134 CONFIG_MIN_PACKETS_THRESHOLD, 135 DEFAULT_DATA_STALL_MIN_PACKETS_THRESHOLD); 136 mTcpPacketsFailRateThreshold = mDependencies.getDeviceConfigPropertyInt( 137 NAMESPACE_CONNECTIVITY, 138 CONFIG_TCP_PACKETS_FAIL_PERCENTAGE, 139 DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE); 140 } 141 }; 142 143 final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() { 144 @Override 145 public void onReceive(Context context, Intent intent) { 146 if (intent == null) return; 147 148 if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) { 149 final PowerManager powerManager = context.getSystemService(PowerManager.class); 150 final boolean deviceIdle = powerManager.isDeviceIdleMode(); 151 setDozeMode(deviceIdle); 152 } 153 } 154 }; 155 TcpSocketTracker(@onNull final Dependencies dps, @NonNull final Network network)156 public TcpSocketTracker(@NonNull final Dependencies dps, @NonNull final Network network) { 157 mDependencies = dps; 158 mNetwork = network; 159 mNetd = mDependencies.getNetd(); 160 161 // If the parcel is null, nothing should be matched which is achieved by the combination of 162 // {@code NetlinkUtils#NULL_MASK} and {@code NetlinkUtils#UNKNOWN_MARK}. 163 final MarkMaskParcel parcel = getNetworkMarkMask(); 164 mNetworkMark = (parcel != null) ? parcel.mark : NetlinkUtils.UNKNOWN_MARK; 165 mNetworkMask = (parcel != null) ? parcel.mask : NetlinkUtils.NULL_MASK; 166 167 // Request tcp info from NetworkStack directly needs extra SELinux permission added after Q 168 // release. 169 if (!mDependencies.isTcpInfoParsingSupported()) return; 170 // Build SocketDiag messages. 171 for (final int family : ADDRESS_FAMILIES) { 172 mSockDiagMsg.put( 173 family, InetDiagMessage.buildInetDiagReqForAliveTcpSockets(family)); 174 } 175 mDependencies.addDeviceConfigChangedListener(mConfigListener); 176 mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver); 177 } 178 179 @Nullable getNetworkMarkMask()180 private MarkMaskParcel getNetworkMarkMask() { 181 try { 182 final int netId = NetworkShimImpl.newInstance(mNetwork).getNetId(); 183 return mNetd.getFwmarkForNetwork(netId); 184 } catch (UnsupportedApiLevelException e) { 185 log("Get netId is not available in this API level."); 186 } catch (RemoteException e) { 187 Log.e(TAG, "Error getting fwmark for network, ", e); 188 } 189 return null; 190 } 191 192 /** 193 * Request to send a SockDiag Netlink request. Receive and parse the returned message. This 194 * function is not thread-safe and should only be called from only one thread. 195 * 196 * @Return if this polling request is sent to kernel and executes successfully or not. 197 */ pollSocketsInfo()198 public boolean pollSocketsInfo() { 199 if (!mDependencies.isTcpInfoParsingSupported()) return false; 200 // Traffic will be restricted in doze mode. TCP info may not reflect the correct network 201 // behavior. 202 // TODO: Traffic may be restricted by other reason. Get the restriction info from bpf in T+. 203 synchronized (mDozeModeLock) { 204 if (mInDozeMode) return false; 205 } 206 207 FileDescriptor fd = null; 208 209 try { 210 final long time = SystemClock.elapsedRealtime(); 211 fd = mDependencies.connectToKernel(); 212 213 final TcpStat stat = new TcpStat(); 214 for (final int family : ADDRESS_FAMILIES) { 215 mDependencies.sendPollingRequest(fd, mSockDiagMsg.get(family)); 216 217 while (parseMessage(mDependencies.recvMessage(fd), family, stat, time)) { 218 log("Pending info exist. Attempt to read more"); 219 } 220 } 221 222 // Calculate mLatestReceiveCount, mSentSinceLastRecv and mLatestPacketFailPercentage. 223 mSentSinceLastRecv = (stat.receivedCount == 0) 224 ? (mSentSinceLastRecv + stat.sentCount) : 0; 225 mLatestReceivedCount = stat.receivedCount; 226 mLatestPacketFailPercentage = ((stat.sentCount != 0) 227 ? ((stat.retransmitCount + stat.lostCount) * 100 / stat.sentCount) : 0); 228 229 // Remove out-of-date socket info. 230 cleanupSocketInfo(time); 231 return true; 232 } catch (ErrnoException | SocketException | InterruptedIOException e) { 233 Log.e(TAG, "Fail to get TCP info via netlink.", e); 234 } finally { 235 SocketUtils.closeSocketQuietly(fd); 236 } 237 238 return false; 239 } 240 241 // Return true if there are more pending messages to read parseMessage(ByteBuffer bytes, int family, TcpStat stat, long time)242 private boolean parseMessage(ByteBuffer bytes, int family, TcpStat stat, long time) { 243 if (!NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)) { 244 // This is unlikely to happen in real cases. Check this first for testing. 245 Log.e(TAG, "Size is less than header size. Ignored."); 246 return false; 247 } 248 249 // Messages are composed with the following format. Stop parsing when receiving 250 // message with nlmsg_type NLMSG_DONE. 251 // +------------------+---------------+--------------+--------+ 252 // | Netlink Header | Family Header | Attributes | rtattr | 253 // | struct nlmsghdr | struct rtmsg | struct rtattr| data | 254 // +------------------+---------------+--------------+--------+ 255 // : : : 256 // +------------------+---------------+--------------+--------+ 257 // | Netlink Header | Family Header | Attributes | rtattr | 258 // | struct nlmsghdr | struct rtmsg | struct rtattr| data | 259 // +------------------+---------------+--------------+--------+ 260 try { 261 do { 262 final int nlmsgLen = getLengthAndVerifyMsgHeader(bytes, family); 263 if (nlmsgLen == END_OF_PARSING) return false; 264 265 if (isValidInetDiagMsgSize(nlmsgLen)) { 266 // Get the socket cookie value. Composed by two Integers value. 267 // Corresponds to inet_diag_sockid in 268 // <linux_src>/include/uapi/linux/inet_diag.h 269 bytes.position(bytes.position() + IDIAG_COOKIE_OFFSET); 270 271 // It's stored in native with 2 int. Parse it as long for 272 // convenience. 273 final long cookie = bytes.getLong(); 274 log("cookie=" + cookie); 275 // Skip the rest part of StructInetDiagMsg. 276 bytes.position(bytes.position() 277 + StructInetDiagMsg.STRUCT_SIZE - IDIAG_COOKIE_OFFSET 278 - Long.BYTES); 279 final SocketInfo info = parseSockInfo(bytes, family, nlmsgLen, 280 time); 281 // Update TcpStats based on previous and current socket info. 282 stat.accumulate( 283 calculateLatestPacketsStat(info, mSocketInfos.get(cookie))); 284 mSocketInfos.put(cookie, info); 285 } 286 } while (NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)); 287 } catch (IllegalArgumentException | BufferUnderflowException e) { 288 Log.wtf(TAG, "Unexpected socket info parsing, family " + family 289 + " buffer:" + bytes + " " 290 + Base64.getEncoder().encodeToString(bytes.array()), e); 291 return false; 292 } 293 294 return true; 295 } 296 getLengthAndVerifyMsgHeader(@onNull ByteBuffer bytes, int family)297 private int getLengthAndVerifyMsgHeader(@NonNull ByteBuffer bytes, int family) { 298 final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes); 299 if (nlmsghdr == null) { 300 Log.e(TAG, "Badly formatted data."); 301 return END_OF_PARSING; 302 } 303 304 log("pollSocketsInfo: nlmsghdr=" + nlmsghdr + ", limit=" + bytes.limit()); 305 // End of the message. Stop parsing. 306 if (nlmsghdr.nlmsg_type == NLMSG_DONE) { 307 return END_OF_PARSING; 308 } 309 310 if (nlmsghdr.nlmsg_type != SOCK_DIAG_BY_FAMILY) { 311 Log.e(TAG, "Expect to get family " + family 312 + " SOCK_DIAG_BY_FAMILY message but get " 313 + nlmsghdr.nlmsg_type); 314 return END_OF_PARSING; 315 } 316 317 return nlmsghdr.nlmsg_len; 318 } 319 cleanupSocketInfo(final long time)320 private void cleanupSocketInfo(final long time) { 321 final int size = mSocketInfos.size(); 322 final List<Long> toRemove = new ArrayList<Long>(); 323 for (int i = 0; i < size; i++) { 324 final long key = mSocketInfos.keyAt(i); 325 if (mSocketInfos.get(key).updateTime < time) { 326 toRemove.add(key); 327 } 328 } 329 for (final Long key : toRemove) { 330 mSocketInfos.remove(key); 331 } 332 } 333 334 /** Parse a {@code SocketInfo} from the given position of the given byte buffer. */ 335 @VisibleForTesting 336 @NonNull parseSockInfo(@onNull final ByteBuffer bytes, final int family, final int nlmsgLen, final long time)337 SocketInfo parseSockInfo(@NonNull final ByteBuffer bytes, final int family, 338 final int nlmsgLen, final long time) { 339 final int remainingDataSize = bytes.position() + nlmsgLen - SOCKDIAG_MSG_HEADER_SIZE; 340 TcpInfo tcpInfo = null; 341 int mark = NetlinkUtils.INIT_MARK_VALUE; 342 // Get a tcp_info. 343 while (bytes.position() < remainingDataSize) { 344 final RoutingAttribute rtattr = 345 new RoutingAttribute(bytes.getShort(), bytes.getShort()); 346 final short dataLen = rtattr.getDataLength(); 347 if (rtattr.rtaType == NetlinkUtils.INET_DIAG_INFO) { 348 tcpInfo = TcpInfo.parse(bytes, dataLen); 349 } else if (rtattr.rtaType == NetlinkUtils.INET_DIAG_MARK) { 350 mark = bytes.getInt(); 351 } else { 352 // Data provided by kernel will include both valid data and padding data. The data 353 // len provided from kernel indicates the valid data size. Readers must deduce the 354 // alignment by themselves. 355 skipRemainingAttributesBytesAligned(bytes, dataLen); 356 } 357 } 358 final SocketInfo info = new SocketInfo(tcpInfo, family, mark, time); 359 log("parseSockInfo, " + info); 360 return info; 361 } 362 363 /** 364 * Return if data stall is suspected or not by checking the latest tcp connection fail rate. 365 * Expect to check after polling the latest status. This function should only be called from 366 * statemachine thread of NetworkMonitor. 367 */ isDataStallSuspected()368 public boolean isDataStallSuspected() { 369 if (!mDependencies.isTcpInfoParsingSupported()) return false; 370 371 // Skip checking data stall since the traffic will be restricted and it will not be real 372 // network stall. 373 // TODO: Traffic may be restricted by other reason. Get the restriction info from bpf in T+. 374 synchronized (mDozeModeLock) { 375 if (mInDozeMode) return false; 376 } 377 378 return (getLatestPacketFailPercentage() >= getTcpPacketsFailRateThreshold()); 379 } 380 381 /** Calculate the change between the {@param current} and {@param previous}. */ 382 @Nullable calculateLatestPacketsStat(@onNull final SocketInfo current, @Nullable final SocketInfo previous)383 private TcpStat calculateLatestPacketsStat(@NonNull final SocketInfo current, 384 @Nullable final SocketInfo previous) { 385 final TcpStat stat = new TcpStat(); 386 // Ignore non-target network sockets. 387 if ((current.fwmark & mNetworkMask) != mNetworkMark) { 388 return null; 389 } 390 391 if (current.tcpInfo == null) { 392 log("Current tcpInfo is null."); 393 return null; 394 } 395 396 stat.sentCount = current.tcpInfo.mSegsOut; 397 stat.receivedCount = current.tcpInfo.mSegsIn; 398 stat.lostCount = current.tcpInfo.mLost; 399 stat.retransmitCount = current.tcpInfo.mRetransmits; 400 401 if (previous != null && previous.tcpInfo != null) { 402 stat.sentCount -= previous.tcpInfo.mSegsOut; 403 stat.receivedCount -= previous.tcpInfo.mSegsIn; 404 stat.lostCount -= previous.tcpInfo.mLost; 405 stat.retransmitCount -= previous.tcpInfo.mRetransmits; 406 } 407 log("calculateLatestPacketsStat, stat:" + stat); 408 return stat; 409 } 410 411 /** 412 * Get tcp connection fail rate based on packet lost and retransmission count. 413 * 414 * @return the latest packet fail percentage. -1 denotes that there is no available data. 415 */ getLatestPacketFailPercentage()416 public int getLatestPacketFailPercentage() { 417 if (!mDependencies.isTcpInfoParsingSupported()) return -1; 418 // Only return fail rate if device sent enough packets. 419 if (getSentSinceLastRecv() < getMinPacketsThreshold()) return -1; 420 return mLatestPacketFailPercentage; 421 } 422 423 /** 424 * Return the number of packets sent since last received. Note that this number is calculated 425 * between each polling period, not an accurate number. 426 */ getSentSinceLastRecv()427 public int getSentSinceLastRecv() { 428 if (!mDependencies.isTcpInfoParsingSupported()) return -1; 429 return mSentSinceLastRecv; 430 } 431 432 /** Return the number of the packets received in the latest polling cycle. */ getLatestReceivedCount()433 public int getLatestReceivedCount() { 434 if (!mDependencies.isTcpInfoParsingSupported()) return -1; 435 return mLatestReceivedCount; 436 } 437 isValidInetDiagMsgSize(final int nlMsgLen)438 private static boolean isValidInetDiagMsgSize(final int nlMsgLen) { 439 return nlMsgLen >= SOCKDIAG_MSG_HEADER_SIZE; 440 } 441 getMinPacketsThreshold()442 private int getMinPacketsThreshold() { 443 return mMinPacketsThreshold; 444 } 445 getTcpPacketsFailRateThreshold()446 private int getTcpPacketsFailRateThreshold() { 447 return mTcpPacketsFailRateThreshold; 448 } 449 450 /** 451 * Method to skip the remaining attributes bytes. 452 * Corresponds to NLMSG_NEXT in bionic/libc/kernel/uapi/linux/netlink.h. 453 * 454 * @param buffer the target ByteBuffer 455 * @param len the remaining length to skip. 456 */ skipRemainingAttributesBytesAligned(@onNull final ByteBuffer buffer, final short len)457 private void skipRemainingAttributesBytesAligned(@NonNull final ByteBuffer buffer, 458 final short len) { 459 // Data in {@Code RoutingAttribute} is followed after header with size {@Code NLA_ALIGNTO} 460 // bytes long for each block. Next attribute will start after the padding bytes if any. 461 // If all remaining bytes after header are valid in a data block, next attr will just start 462 // after valid bytes. 463 // 464 // E.g. With NLA_ALIGNTO(4), an attr struct with length 5 means 1 byte valid data remains 465 // after header and 3(4-1) padding bytes. Next attr with length 8 will start after the 466 // padding bytes and contain 4(8-4) valid bytes of data. The next attr start after the 467 // valid bytes, like: 468 // 469 // [HEADER(L=5)][ 4-Bytes DATA ][ HEADER(L=8) ][4 bytes DATA][Next attr] 470 // [ 5 valid bytes ][3 padding bytes ][ 8 valid bytes ] ... 471 final int cur = buffer.position(); 472 buffer.position(cur + NetlinkConstants.alignedLengthOf(len)); 473 } 474 log(final String str)475 private void log(final String str) { 476 if (DBG) Log.d(TAG, str); 477 } 478 479 /** Stops monitoring and releases resources. */ quit()480 public void quit() { 481 // Do not need to unregister receiver and listener since registration is skipped 482 // in the constructor. 483 if (!mDependencies.isTcpInfoParsingSupported()) return; 484 485 mDependencies.removeDeviceConfigChangedListener(mConfigListener); 486 mDependencies.removeBroadcastReceiver(mDeviceIdleReceiver); 487 } 488 489 /** 490 * Corresponds to {@code struct rtattr} from bionic/libc/kernel/uapi/linux/rtnetlink.h 491 * 492 * struct rtattr { 493 * unsigned short rta_len; // Length of option 494 * unsigned short rta_type; // Type of option 495 * // Data follows 496 * }; 497 */ 498 class RoutingAttribute { 499 public static final int HEADER_LENGTH = 4; 500 501 public final short rtaLen; // The whole valid size of the struct. 502 public final short rtaType; 503 RoutingAttribute(final short len, final short type)504 RoutingAttribute(final short len, final short type) { 505 rtaLen = len; 506 rtaType = type; 507 } getDataLength()508 public short getDataLength() { 509 return (short) (rtaLen - HEADER_LENGTH); 510 } 511 } 512 513 /** 514 * Data class for keeping the socket info. 515 */ 516 @VisibleForTesting 517 class SocketInfo { 518 @Nullable 519 public final TcpInfo tcpInfo; 520 // One of {@code AF_INET6, AF_INET}. 521 public final int ipFamily; 522 // "fwmark" value of the socket queried from native. 523 public final int fwmark; 524 // Socket information updated elapsed real time. 525 public final long updateTime; 526 SocketInfo(@ullable final TcpInfo info, final int family, final int mark, final long time)527 SocketInfo(@Nullable final TcpInfo info, final int family, final int mark, 528 final long time) { 529 tcpInfo = info; 530 ipFamily = family; 531 updateTime = time; 532 fwmark = mark; 533 } 534 535 @Override toString()536 public String toString() { 537 return "SocketInfo {Type:" + ipTypeToString(ipFamily) + ", " 538 + tcpInfo + ", mark:" + fwmark + " updated at " + updateTime + "}"; 539 } 540 ipTypeToString(final int type)541 private String ipTypeToString(final int type) { 542 if (type == AF_INET) { 543 return "IP"; 544 } else if (type == AF_INET6) { 545 return "IPV6"; 546 } else { 547 return "UNKNOWN"; 548 } 549 } 550 } 551 552 /** 553 * private data class only for storing the Tcp statistic for calculating the fail rate and sent 554 * count 555 * */ 556 private class TcpStat { 557 public int sentCount; 558 public int lostCount; 559 public int retransmitCount; 560 public int receivedCount; 561 accumulate(@ullable final TcpStat stat)562 void accumulate(@Nullable final TcpStat stat) { 563 if (stat == null) return; 564 565 sentCount += stat.sentCount; 566 lostCount += stat.lostCount; 567 receivedCount += stat.receivedCount; 568 retransmitCount += stat.retransmitCount; 569 } 570 571 @Override toString()572 public String toString() { 573 return "TcpStat {sent=" + sentCount + ", lost=" + lostCount 574 + ", retransmit=" + retransmitCount + ", received=" + receivedCount + "}"; 575 } 576 } 577 setDozeMode(boolean isEnabled)578 private void setDozeMode(boolean isEnabled) { 579 synchronized (mDozeModeLock) { 580 if (mInDozeMode == isEnabled) return; 581 mInDozeMode = isEnabled; 582 log("Doze mode enabled=" + mInDozeMode); 583 } 584 } 585 586 /** 587 * Dependencies class for testing. 588 */ 589 @VisibleForTesting 590 public static class Dependencies { 591 private final Context mContext; 592 Dependencies(final Context context)593 public Dependencies(final Context context) { 594 mContext = context; 595 } 596 597 /** 598 * Connect to kernel via netlink socket. 599 * 600 * @return fd the fileDescriptor of the socket. 601 * Throw ErrnoException, SocketException if the exception is thrown. 602 */ connectToKernel()603 public FileDescriptor connectToKernel() throws ErrnoException, SocketException { 604 final FileDescriptor fd = NetlinkUtils.createNetLinkInetDiagSocket(); 605 NetlinkUtils.connectSocketToNetlink(fd); 606 Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, 607 StructTimeval.fromMillis(IO_TIMEOUT_MS)); 608 return fd; 609 } 610 611 /** 612 * Send composed message request to kernel. 613 * @param fd see {@Code FileDescriptor} 614 * @param msg the byte array represent the request message to write to kernel. 615 * 616 * Throw ErrnoException or InterruptedIOException if the exception is thrown. 617 */ sendPollingRequest(@onNull final FileDescriptor fd, @NonNull final byte[] msg)618 public void sendPollingRequest(@NonNull final FileDescriptor fd, @NonNull final byte[] msg) 619 throws ErrnoException, InterruptedIOException { 620 Os.write(fd, msg, 0 /* byteOffset */, msg.length); 621 } 622 623 /** 624 * Look up the value of a property in DeviceConfig. 625 * @param namespace The namespace containing the property to look up. 626 * @param name The name of the property to look up. 627 * @param defaultValue The value to return if the property does not exist or has no non-null 628 * value. 629 * @return the corresponding value, or defaultValue if none exists. 630 */ getDeviceConfigPropertyInt(@onNull final String namespace, @NonNull final String name, final int defaultValue)631 public int getDeviceConfigPropertyInt(@NonNull final String namespace, 632 @NonNull final String name, final int defaultValue) { 633 return DeviceConfigUtils.getDeviceConfigPropertyInt(namespace, name, defaultValue); 634 } 635 636 /** 637 * Return if request tcp info via netlink socket is supported or not. 638 */ isTcpInfoParsingSupported()639 public boolean isTcpInfoParsingSupported() { 640 // Request tcp info from NetworkStack directly needs extra SELinux permission added 641 // after Q release. 642 return ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); 643 } 644 645 /** 646 * Receive the request message from kernel via given fd. 647 */ recvMessage(@onNull final FileDescriptor fd)648 public ByteBuffer recvMessage(@NonNull final FileDescriptor fd) 649 throws ErrnoException, InterruptedIOException { 650 return NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS); 651 } 652 getContext()653 public Context getContext() { 654 return mContext; 655 } 656 657 /** 658 * Get an INetd connector. 659 */ getNetd()660 public INetd getNetd() { 661 return INetd.Stub.asInterface( 662 (IBinder) mContext.getSystemService(Context.NETD_SERVICE)); 663 } 664 665 /** Add device config change listener */ addDeviceConfigChangedListener( @onNull final DeviceConfig.OnPropertiesChangedListener listener)666 public void addDeviceConfigChangedListener( 667 @NonNull final DeviceConfig.OnPropertiesChangedListener listener) { 668 DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CONNECTIVITY, 669 AsyncTask.THREAD_POOL_EXECUTOR, listener); 670 } 671 672 /** Remove device config change listener */ removeDeviceConfigChangedListener( @onNull final DeviceConfig.OnPropertiesChangedListener listener)673 public void removeDeviceConfigChangedListener( 674 @NonNull final DeviceConfig.OnPropertiesChangedListener listener) { 675 DeviceConfig.removeOnPropertiesChangedListener(listener); 676 } 677 678 /** Add receiver for detecting doze mode change to control TCP detection. */ addDeviceIdleReceiver(@onNull final BroadcastReceiver receiver)679 public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver) { 680 mContext.registerReceiver(receiver, 681 new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); 682 } 683 684 /** Remove broadcast receiver. */ removeBroadcastReceiver(@onNull final BroadcastReceiver receiver)685 public void removeBroadcastReceiver(@NonNull final BroadcastReceiver receiver) { 686 mContext.unregisterReceiver(receiver); 687 } 688 } 689 } 690