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.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; 23 import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; 24 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; 25 import static android.system.OsConstants.AF_INET; 26 import static android.system.OsConstants.AF_INET6; 27 import static android.system.OsConstants.SOL_SOCKET; 28 import static android.system.OsConstants.SO_SNDTIMEO; 29 30 import static com.android.net.module.util.FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED; 31 import static com.android.net.module.util.NetworkStackConstants.DNS_OVER_TLS_PORT; 32 import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE; 33 import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE; 34 import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY; 35 import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE; 36 import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS; 37 import static com.android.networkstack.util.NetworkStackUtils.IGNORE_TCP_INFO_FOR_BLOCKED_UIDS; 38 import static com.android.networkstack.util.NetworkStackUtils.SKIP_TCP_POLL_IN_LIGHT_DOZE; 39 40 import android.annotation.TargetApi; 41 import android.content.BroadcastReceiver; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.content.IntentFilter; 45 import android.net.ConnectivityManager; 46 import android.net.INetd; 47 import android.net.LinkProperties; 48 import android.net.MarkMaskParcel; 49 import android.net.Network; 50 import android.net.NetworkCapabilities; 51 import android.os.AsyncTask; 52 import android.os.Build; 53 import android.os.IBinder; 54 import android.os.PowerManager; 55 import android.os.RemoteException; 56 import android.os.SystemClock; 57 import android.provider.DeviceConfig; 58 import android.system.ErrnoException; 59 import android.system.Os; 60 import android.system.StructTimeval; 61 import android.util.ArraySet; 62 import android.util.Log; 63 import android.util.LongSparseArray; 64 import android.util.SparseArray; 65 66 import androidx.annotation.NonNull; 67 import androidx.annotation.Nullable; 68 69 import com.android.internal.annotations.GuardedBy; 70 import com.android.internal.annotations.VisibleForTesting; 71 import com.android.modules.utils.build.SdkLevel; 72 import com.android.net.module.util.DeviceConfigUtils; 73 import com.android.net.module.util.SocketUtils; 74 import com.android.net.module.util.netlink.InetDiagMessage; 75 import com.android.net.module.util.netlink.NetlinkUtils; 76 import com.android.net.module.util.netlink.StructInetDiagMsg; 77 import com.android.net.module.util.netlink.StructNlAttr; 78 import com.android.net.module.util.netlink.StructNlMsgHdr; 79 import com.android.networkstack.apishim.NetworkShimImpl; 80 import com.android.networkstack.apishim.common.UnsupportedApiLevelException; 81 82 import java.io.FileDescriptor; 83 import java.io.InterruptedIOException; 84 import java.net.SocketException; 85 import java.nio.BufferUnderflowException; 86 import java.nio.ByteBuffer; 87 import java.util.ArrayList; 88 import java.util.Base64; 89 import java.util.List; 90 91 /** 92 * Class for NetworkStack to send a SockDiag request and parse the returned tcp info. 93 * 94 * This is not thread-safe. This should be only accessed from one thread. 95 */ 96 public class TcpSocketTracker { 97 private static final String TAG = TcpSocketTracker.class.getSimpleName(); 98 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 99 private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET}; 100 private static final int END_OF_PARSING = -1; 101 102 /** 103 * Gather the socket info. 104 * 105 * Key: The idiag_cookie value of the socket. See struct inet_diag_sockid in 106 * <linux_src>/include/uapi/linux/inet_diag.h 107 * Value: See {@Code SocketInfo} 108 */ 109 private final LongSparseArray<SocketInfo> mSocketInfos = new LongSparseArray<>(); 110 // Number of packets sent since the last received packet 111 private int mSentSinceLastRecv; 112 // The latest fail rate calculated by the latest tcp info. 113 private int mLatestPacketFailPercentage; 114 // Number of packets received in the latest polling cycle. 115 private int mLatestReceivedCount; 116 // Uids in the latest polling cycle. 117 private final ArraySet<Integer> mLatestReportedUids = new ArraySet<>(); 118 119 /** 120 * Request to send to kernel to request tcp info. 121 * 122 * Key: Ip family type. 123 * Value: Bytes array represent the {@Code inetDiagReqV2}. 124 */ 125 private final SparseArray<byte[]> mSockDiagMsg = new SparseArray<>(); 126 private final Dependencies mDependencies; 127 private final INetd mNetd; 128 private final Network mNetwork; 129 // The fwmark value of {@code mNetwork}. 130 private final int mNetworkMark; 131 // The network id mask of fwmark. 132 private final int mNetworkMask; 133 private int mMinPacketsThreshold = DEFAULT_DATA_STALL_MIN_PACKETS_THRESHOLD; 134 private int mTcpPacketsFailRateThreshold = DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE; 135 136 // TODO: Remove doze mode solution since uid networking blocked traffic is filtered out by 137 // the info provided by bpf maps. 138 private final Object mDozeModeLock = new Object(); 139 @GuardedBy("mDozeModeLock") 140 private boolean mInDozeMode = false; 141 142 // These variables are initialized when the NetworkMonitor enters DefaultState, 143 // and can only be accessed on the NetworkMonitor state machine thread after 144 // the NetworkMonitor state machine has been started. 145 private boolean mInOpportunisticMode; 146 @NonNull 147 private LinkProperties mLinkProperties; 148 @NonNull 149 private NetworkCapabilities mNetworkCapabilities; 150 151 private final boolean mShouldDisableInDeepDoze; 152 private final boolean mShouldDisableInLightDoze; 153 private final boolean mShouldIgnoreTcpInfoForBlockedUids; 154 private final ConnectivityManager mCm; 155 156 @VisibleForTesting 157 protected final DeviceConfig.OnPropertiesChangedListener mConfigListener = 158 new DeviceConfig.OnPropertiesChangedListener() { 159 @Override 160 public void onPropertiesChanged(DeviceConfig.Properties properties) { 161 mMinPacketsThreshold = mDependencies.getDeviceConfigPropertyInt( 162 NAMESPACE_CONNECTIVITY, 163 CONFIG_MIN_PACKETS_THRESHOLD, 164 DEFAULT_DATA_STALL_MIN_PACKETS_THRESHOLD); 165 mTcpPacketsFailRateThreshold = mDependencies.getDeviceConfigPropertyInt( 166 NAMESPACE_CONNECTIVITY, 167 CONFIG_TCP_PACKETS_FAIL_PERCENTAGE, 168 DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE); 169 } 170 }; 171 isDeviceIdleModeChangedAction(Intent intent)172 private boolean isDeviceIdleModeChangedAction(Intent intent) { 173 return mShouldDisableInDeepDoze 174 && ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction()); 175 } 176 177 @TargetApi(Build.VERSION_CODES.TIRAMISU) isDeviceLightIdleModeChangedAction(Intent intent)178 private boolean isDeviceLightIdleModeChangedAction(Intent intent) { 179 return mShouldDisableInLightDoze 180 && ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(intent.getAction()); 181 } 182 183 final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() { 184 @Override 185 @TargetApi(Build.VERSION_CODES.TIRAMISU) 186 public void onReceive(Context context, Intent intent) { 187 if (intent == null) return; 188 189 if (isDeviceIdleModeChangedAction(intent) 190 || isDeviceLightIdleModeChangedAction(intent)) { 191 final PowerManager powerManager = context.getSystemService(PowerManager.class); 192 // For tcp polling mechanism, there is no difference between deep doze mode and 193 // light doze mode. The deep doze mode and light doze mode block networking 194 // for uids in the same way, use single variable to control. 195 final boolean deviceIdle = (mShouldDisableInDeepDoze 196 && powerManager.isDeviceIdleMode()) 197 || (mShouldDisableInLightDoze && powerManager.isDeviceLightIdleMode()); 198 setDozeMode(deviceIdle); 199 } 200 } 201 }; 202 TcpSocketTracker(@onNull final Dependencies dps, @NonNull final Network network)203 public TcpSocketTracker(@NonNull final Dependencies dps, @NonNull final Network network) { 204 mDependencies = dps; 205 mNetwork = network; 206 mNetd = mDependencies.getNetd(); 207 mShouldIgnoreTcpInfoForBlockedUids = mDependencies.shouldIgnoreTcpInfoForBlockedUids(); 208 209 // Previous workarounds can be disabled if the device supports ignore blocked uids feature. 210 // To prevent inconsistencies and issues like broadcast receiver leaks, the feature flags 211 // are fixed after being read. 212 // TODO: Remove these workarounds when pre-T devices are no longer supported. 213 mShouldDisableInLightDoze = mDependencies.shouldDisableInLightDoze( 214 mShouldIgnoreTcpInfoForBlockedUids); 215 mShouldDisableInDeepDoze = !mShouldIgnoreTcpInfoForBlockedUids; 216 217 // If the parcel is null, nothing should be matched which is achieved by the combination of 218 // {@code NetlinkUtils#NULL_MASK} and {@code NetlinkUtils#UNKNOWN_MARK}. 219 final MarkMaskParcel parcel = getNetworkMarkMask(); 220 mNetworkMark = (parcel != null) ? parcel.mark : NetlinkUtils.UNKNOWN_MARK; 221 mNetworkMask = (parcel != null) ? parcel.mask : NetlinkUtils.NULL_MASK; 222 223 // Build SocketDiag messages. 224 for (final int family : ADDRESS_FAMILIES) { 225 mSockDiagMsg.put( 226 family, InetDiagMessage.buildInetDiagReqForAliveTcpSockets(family)); 227 } 228 mDependencies.addDeviceConfigChangedListener(mConfigListener); 229 mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver, mShouldDisableInDeepDoze, 230 mShouldDisableInLightDoze); 231 mCm = mDependencies.getContext().getSystemService(ConnectivityManager.class); 232 } 233 234 @Nullable getNetworkMarkMask()235 private MarkMaskParcel getNetworkMarkMask() { 236 try { 237 final int netId = NetworkShimImpl.newInstance(mNetwork).getNetId(); 238 return mNetd.getFwmarkForNetwork(netId); 239 } catch (UnsupportedApiLevelException e) { 240 logd("Get netId is not available in this API level."); 241 } catch (RemoteException e) { 242 loge("Error getting fwmark for network, ", e); 243 } 244 return null; 245 } 246 247 /** 248 * Request to send a SockDiag Netlink request. Receive and parse the returned message. This 249 * function is not thread-safe and should only be called from only one thread. 250 * 251 * @Return if this polling request is sent to kernel and executes successfully or not. 252 */ pollSocketsInfo()253 public boolean pollSocketsInfo() { 254 // Traffic will be restricted in doze mode. TCP info may not reflect the correct network 255 // behavior. 256 // TODO: Traffic may be restricted by other reason. Get the restriction info from bpf in T+. 257 synchronized (mDozeModeLock) { 258 if (mInDozeMode) return false; 259 } 260 261 FileDescriptor fd = null; 262 263 try { 264 final long time = SystemClock.elapsedRealtime(); 265 fd = mDependencies.connectToKernel(); 266 267 final ArrayList<SocketInfo> newSocketInfoList = new ArrayList<>(); 268 for (final int family : ADDRESS_FAMILIES) { 269 mDependencies.sendPollingRequest(fd, mSockDiagMsg.get(family)); 270 while (parseMessage(mDependencies.recvMessage(fd), 271 family, newSocketInfoList, time)) { 272 logd("Pending info exist. Attempt to read more"); 273 } 274 } 275 276 // Append TcpStats based on previous and current socket info. 277 final TcpStat stat = new TcpStat(); 278 final ArrayList<Integer> skippedBlockedUids = new ArrayList<>(); 279 mLatestReportedUids.clear(); 280 for (final SocketInfo newInfo : newSocketInfoList) { 281 final TcpStat diff = calculateLatestPacketsStat(newInfo, 282 mSocketInfos.get(newInfo.cookie)); 283 mSocketInfos.put(newInfo.cookie, newInfo); 284 285 // When in Opportunistic Mode, exclude destination port 853 for private DNS to 286 // avoid misleading data stall signals if the probing is not done. 287 // In private DNS opportunistic mode, the resolver will try to establish 288 // TCP connections with the DNS servers. However, if the target DNS server 289 // does not support private DNS, this would result in no response traffic, 290 // which could trigger a false alarm of data stall. 291 // TODO: Fix the false alarms where the private DNS servers are validated by 292 // DoH instead of DoT. In this case the DoT probing traffic should be ignored. 293 if (newInfo.dstPort == DNS_OVER_TLS_PORT && mInOpportunisticMode && 294 !areAllPrivateDnsServersValidated(mLinkProperties)) { 295 continue; 296 } 297 298 if (mShouldIgnoreTcpInfoForBlockedUids) { 299 // For backward-compatibility, NET_CAPABILITY_TEMPORARILY_NOT_METERED 300 // is not referenced when deciding meteredness in NetworkPolicyManagerService. 301 // Thus, whether to block metered networking should only be judged with 302 // NET_CAPABILITY_NOT_METERED. 303 final boolean metered = !mNetworkCapabilities.hasCapability( 304 NetworkCapabilities.NET_CAPABILITY_NOT_METERED); 305 final boolean uidBlocked = mCm.isUidNetworkingBlocked(newInfo.uid, metered); 306 if (uidBlocked) { 307 skippedBlockedUids.add(newInfo.uid); 308 continue; 309 } 310 } 311 312 if (diff != null) { 313 mLatestReportedUids.add(newInfo.uid); 314 stat.accumulate(diff); 315 } 316 } 317 if (!skippedBlockedUids.isEmpty()) { 318 logd("Skip blocked uids: " + skippedBlockedUids); 319 } 320 321 // Calculate mLatestReceiveCount, mSentSinceLastRecv and mLatestPacketFailPercentage. 322 mSentSinceLastRecv = (stat.receivedCount == 0) 323 ? (mSentSinceLastRecv + stat.sentCount) : 0; 324 mLatestReceivedCount = stat.receivedCount; 325 mLatestPacketFailPercentage = ((stat.sentCount != 0) 326 ? (stat.retransCount * 100 / stat.sentCount) : 0); 327 328 // Remove out-of-date socket info. 329 cleanupSocketInfo(time); 330 return true; 331 } catch (ErrnoException | SocketException | InterruptedIOException e) { 332 loge("Fail to get TCP info via netlink.", e); 333 } finally { 334 SocketUtils.closeSocketQuietly(fd); 335 } 336 337 return false; 338 } 339 areAllPrivateDnsServersValidated(@onNull LinkProperties lp)340 private static boolean areAllPrivateDnsServersValidated(@NonNull LinkProperties lp) { 341 return lp.getDnsServers().size() == lp.getValidatedPrivateDnsServers().size(); 342 } 343 344 // Return true if there are more pending messages to read 345 @VisibleForTesting parseMessage(ByteBuffer bytes, int family, ArrayList<SocketInfo> outputSocketInfoList, long time)346 boolean parseMessage(ByteBuffer bytes, int family, 347 ArrayList<SocketInfo> outputSocketInfoList, long time) { 348 if (!NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)) { 349 // This is unlikely to happen in real cases. Check this first for testing. 350 loge("Size is less than header size. Ignored."); 351 return false; 352 } 353 354 // Messages are composed with the following format. Stop parsing when receiving 355 // message with nlmsg_type NLMSG_DONE. 356 // +------------------+---------------+--------------+--------+ 357 // | Netlink Header | Family Header | Attributes | rtattr | 358 // | struct nlmsghdr | struct rtmsg | struct rtattr| data | 359 // +------------------+---------------+--------------+--------+ 360 // : : : 361 // +------------------+---------------+--------------+--------+ 362 // | Netlink Header | Family Header | Attributes | rtattr | 363 // | struct nlmsghdr | struct rtmsg | struct rtattr| data | 364 // +------------------+---------------+--------------+--------+ 365 try { 366 do { 367 final int nlmsgLen = getLengthAndVerifyMsgHeader(bytes, family); 368 if (nlmsgLen == END_OF_PARSING) return false; 369 370 if (!isValidInetDiagMsgSize(nlmsgLen)) { 371 throw new IllegalStateException("Invalid netlink message length: " + nlmsgLen); 372 } 373 // Get the socket cookie value and uid from inet_diag_msg struct. 374 final StructInetDiagMsg inetDiagMsg = StructInetDiagMsg.parse(bytes); 375 if (inetDiagMsg == null) { 376 throw new IllegalStateException("Failed to parse StructInetDiagMsg"); 377 } 378 final SocketInfo info = parseSockInfo(bytes, family, nlmsgLen, time, 379 inetDiagMsg.idiag_uid, inetDiagMsg.id.cookie, 380 inetDiagMsg.id.remSocketAddress.getPort()); 381 outputSocketInfoList.add(info); 382 } while (NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)); 383 } catch (IllegalArgumentException | BufferUnderflowException e) { 384 logwtf("Unexpected socket info parsing, family " + family 385 + " buffer:" + bytes + " " 386 + Base64.getEncoder().encodeToString(bytes.array()), e); 387 return false; 388 } catch (IllegalStateException e) { 389 loge("Unexpected socket info parsing, family " + family 390 + " buffer:" + bytes + " " 391 + Base64.getEncoder().encodeToString(bytes.array()), e); 392 return false; 393 } 394 395 return true; 396 } 397 getLengthAndVerifyMsgHeader(@onNull ByteBuffer bytes, int family)398 private int getLengthAndVerifyMsgHeader(@NonNull ByteBuffer bytes, int family) { 399 final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes); 400 if (nlmsghdr == null) { 401 loge("Badly formatted data."); 402 return END_OF_PARSING; 403 } 404 405 logd("pollSocketsInfo: nlmsghdr=" + nlmsghdr + ", limit=" + bytes.limit()); 406 // End of the message. Stop parsing. 407 if (nlmsghdr.nlmsg_type == NLMSG_DONE) { 408 return END_OF_PARSING; 409 } 410 411 if (nlmsghdr.nlmsg_type != SOCK_DIAG_BY_FAMILY) { 412 loge("Expect to get family " + family 413 + " SOCK_DIAG_BY_FAMILY message but get " 414 + nlmsghdr.nlmsg_type); 415 return END_OF_PARSING; 416 } 417 418 return nlmsghdr.nlmsg_len; 419 } 420 cleanupSocketInfo(final long time)421 private void cleanupSocketInfo(final long time) { 422 final int size = mSocketInfos.size(); 423 final List<Long> toRemove = new ArrayList<Long>(); 424 for (int i = 0; i < size; i++) { 425 final long key = mSocketInfos.keyAt(i); 426 if (mSocketInfos.get(key).updateTime < time) { 427 toRemove.add(key); 428 } 429 } 430 for (final Long key : toRemove) { 431 mSocketInfos.remove(key); 432 } 433 } 434 435 /** Parse a {@code SocketInfo} from the given position of the given byte buffer. */ 436 @NonNull parseSockInfo(@onNull final ByteBuffer bytes, final int family, final int nlmsgLen, final long time, final int uid, final long cookie, final int dstPort)437 private SocketInfo parseSockInfo(@NonNull final ByteBuffer bytes, final int family, 438 final int nlmsgLen, final long time, final int uid, final long cookie, 439 final int dstPort) { 440 final int remainingDataSize = bytes.position() + nlmsgLen - SOCKDIAG_MSG_HEADER_SIZE; 441 TcpInfo tcpInfo = null; 442 int mark = NetlinkUtils.INIT_MARK_VALUE; 443 // Get a tcp_info. 444 while (bytes.position() < remainingDataSize) { 445 final StructNlAttr nlattr = StructNlAttr.parse(bytes); 446 if (nlattr == null) break; 447 448 if (nlattr.nla_type == NetlinkUtils.INET_DIAG_MARK) { 449 mark = nlattr.getValueAsInteger(); 450 } else if (nlattr.nla_type == NetlinkUtils.INET_DIAG_INFO) { 451 tcpInfo = TcpInfo.parse(nlattr.getValueAsByteBuffer(), nlattr.getAlignedLength()); 452 } 453 } 454 final SocketInfo info = new SocketInfo(tcpInfo, family, mark, time, uid, cookie, dstPort); 455 logd("parseSockInfo, " + info); 456 return info; 457 } 458 459 /** 460 * Return if data stall is suspected or not by checking the latest tcp connection fail rate. 461 * Expect to check after polling the latest status. This function should only be called from 462 * statemachine thread of NetworkMonitor. 463 */ isDataStallSuspected()464 public boolean isDataStallSuspected() { 465 // Skip checking data stall since the traffic will be restricted and it will not be real 466 // network stall. 467 // TODO: Traffic may be restricted by other reason. Get the restriction info from bpf in T+. 468 synchronized (mDozeModeLock) { 469 if (mInDozeMode) return false; 470 } 471 final boolean ret = (getLatestPacketFailPercentage() >= getTcpPacketsFailRateThreshold()); 472 if (ret) { 473 log("data stall suspected, uids: " + mLatestReportedUids.toString()); 474 } 475 return ret; 476 } 477 478 /** Calculate the change between the {@param current} and {@param previous}. */ 479 @Nullable calculateLatestPacketsStat(@onNull final SocketInfo current, @Nullable final SocketInfo previous)480 private TcpStat calculateLatestPacketsStat(@NonNull final SocketInfo current, 481 @Nullable final SocketInfo previous) { 482 final TcpStat stat = new TcpStat(); 483 // Ignore non-target network sockets. 484 if ((current.fwmark & mNetworkMask) != mNetworkMark) { 485 return null; 486 } 487 488 if (current.tcpInfo == null) { 489 logd("Current tcpInfo is null."); 490 return null; 491 } 492 493 stat.sentCount = current.tcpInfo.mSegsOut; 494 stat.receivedCount = current.tcpInfo.mSegsIn; 495 stat.retransCount = current.tcpInfo.mTotalRetrans; 496 497 if (previous != null && previous.tcpInfo != null) { 498 stat.sentCount -= previous.tcpInfo.mSegsOut; 499 stat.receivedCount -= previous.tcpInfo.mSegsIn; 500 stat.retransCount -= previous.tcpInfo.mTotalRetrans; 501 } 502 logd("calculateLatestPacketsStat, stat:" + stat); 503 return stat; 504 } 505 506 /** 507 * Get tcp connection fail rate based on packet lost and retransmission count. 508 * 509 * @return the latest packet fail percentage. -1 denotes that there is no available data. 510 */ getLatestPacketFailPercentage()511 public int getLatestPacketFailPercentage() { 512 // Only return fail rate if device sent enough packets. 513 if (getSentSinceLastRecv() < getMinPacketsThreshold()) return -1; 514 return mLatestPacketFailPercentage; 515 } 516 517 /** 518 * Return the number of packets sent since last received. Note that this number is calculated 519 * between each polling period, not an accurate number. 520 */ getSentSinceLastRecv()521 public int getSentSinceLastRecv() { 522 return mSentSinceLastRecv; 523 } 524 525 /** Return the number of the packets received in the latest polling cycle. */ getLatestReceivedCount()526 public int getLatestReceivedCount() { 527 return mLatestReceivedCount; 528 } 529 isValidInetDiagMsgSize(final int nlMsgLen)530 private static boolean isValidInetDiagMsgSize(final int nlMsgLen) { 531 return nlMsgLen >= SOCKDIAG_MSG_HEADER_SIZE; 532 } 533 getMinPacketsThreshold()534 private int getMinPacketsThreshold() { 535 return mMinPacketsThreshold; 536 } 537 getTcpPacketsFailRateThreshold()538 private int getTcpPacketsFailRateThreshold() { 539 return mTcpPacketsFailRateThreshold; 540 } 541 logd(final String str)542 private void logd(final String str) { 543 if (DBG) log(str); 544 } 545 log(final String s)546 private void log(final String s) { 547 Log.d(TAG + "/" + mNetwork.toString(), s); 548 } 549 loge(final String str)550 private void loge(final String str) { 551 loge(str, null /* tr */); 552 } 553 loge(final String str, @Nullable Throwable tr)554 private void loge(final String str, @Nullable Throwable tr) { 555 Log.e(TAG + "/" + mNetwork.toString(), str, tr); 556 } 557 logwtf(final String str, @Nullable Throwable tr)558 private void logwtf(final String str, @Nullable Throwable tr) { 559 Log.wtf(TAG + "/" + mNetwork.toString(), str, tr); 560 } 561 562 /** Stops monitoring and releases resources. */ quit()563 public void quit() { 564 mDependencies.removeDeviceConfigChangedListener(mConfigListener); 565 mDependencies.removeBroadcastReceiver(mDeviceIdleReceiver, 566 mShouldDisableInDeepDoze, mShouldDisableInLightDoze); 567 } 568 569 /** 570 * Data class for keeping the socket info. 571 */ 572 @VisibleForTesting 573 static class SocketInfo { 574 @Nullable 575 public final TcpInfo tcpInfo; 576 // One of {@code AF_INET6, AF_INET}. 577 public final int ipFamily; 578 // "fwmark" value of the socket queried from native. 579 public final int fwmark; 580 // Socket information updated elapsed real time. 581 public final long updateTime; 582 // Uid which associated with this Socket. 583 public final int uid; 584 // Cookie which associated with this Socket. 585 public final long cookie; 586 // Destination port number of this Socket. 587 public final int dstPort; 588 SocketInfo(@ullable final TcpInfo info, final int family, final int mark, final long time, final int uid, final long cookie, final int dstPort)589 SocketInfo(@Nullable final TcpInfo info, final int family, final int mark, 590 final long time, final int uid, final long cookie, final int dstPort) { 591 tcpInfo = info; 592 ipFamily = family; 593 updateTime = time; 594 fwmark = mark; 595 this.uid = uid; 596 this.cookie = cookie; 597 this.dstPort = dstPort; 598 } 599 600 @Override toString()601 public String toString() { 602 return "SocketInfo {Type:" + ipTypeToString(ipFamily) + ", uid:" + uid 603 + ", cookie:" + cookie + ", " + tcpInfo + ", mark:" + fwmark 604 + ", dstPort:" + dstPort + " updated at " + updateTime + "}"; 605 } 606 ipTypeToString(final int type)607 private String ipTypeToString(final int type) { 608 if (type == AF_INET) { 609 return "IP"; 610 } else if (type == AF_INET6) { 611 return "IPV6"; 612 } else { 613 return "UNKNOWN"; 614 } 615 } 616 } 617 618 /** 619 * private data class only for storing the Tcp statistic for calculating the fail rate and sent 620 * count 621 * */ 622 private class TcpStat { 623 public int sentCount; 624 public int receivedCount; 625 public int retransCount; 626 accumulate(@ullable final TcpStat stat)627 void accumulate(@Nullable final TcpStat stat) { 628 if (stat == null) return; 629 630 sentCount += stat.sentCount; 631 receivedCount += stat.receivedCount; 632 retransCount += stat.retransCount; 633 } 634 635 @Override toString()636 public String toString() { 637 return "TcpStat {sent=" + sentCount + ", retransCount=" + retransCount 638 + ", received=" + receivedCount + "}"; 639 } 640 } 641 setDozeMode(boolean isEnabled)642 private void setDozeMode(boolean isEnabled) { 643 synchronized (mDozeModeLock) { 644 if (mInDozeMode == isEnabled) return; 645 mInDozeMode = isEnabled; 646 logd("Doze mode enabled=" + mInDozeMode); 647 } 648 } 649 setOpportunisticMode(boolean isEnabled)650 public void setOpportunisticMode(boolean isEnabled) { 651 if (mInOpportunisticMode == isEnabled) return; 652 mInOpportunisticMode = isEnabled; 653 654 logd("Private DNS Opportunistic mode enabled=" + mInOpportunisticMode); 655 } 656 setLinkProperties(@onNull LinkProperties lp)657 public void setLinkProperties(@NonNull LinkProperties lp) { 658 mLinkProperties = lp; 659 } 660 setNetworkCapabilities(@onNull NetworkCapabilities caps)661 public void setNetworkCapabilities(@NonNull NetworkCapabilities caps) { 662 mNetworkCapabilities = caps; 663 } 664 665 /** 666 * Dependencies class for testing. 667 */ 668 @VisibleForTesting 669 public static class Dependencies { 670 private final Context mContext; 671 Dependencies(final Context context)672 public Dependencies(final Context context) { 673 mContext = context; 674 } 675 676 /** 677 * Connect to kernel via netlink socket. 678 * 679 * @return fd the fileDescriptor of the socket. 680 * Throw ErrnoException, SocketException if the exception is thrown. 681 */ connectToKernel()682 public FileDescriptor connectToKernel() throws ErrnoException, SocketException { 683 final FileDescriptor fd = NetlinkUtils.createNetLinkInetDiagSocket(); 684 NetlinkUtils.connectToKernel(fd); 685 Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, 686 StructTimeval.fromMillis(IO_TIMEOUT_MS)); 687 return fd; 688 } 689 690 /** 691 * Send composed message request to kernel. 692 * @param fd see {@Code FileDescriptor} 693 * @param msg the byte array represent the request message to write to kernel. 694 * 695 * Throw ErrnoException or InterruptedIOException if the exception is thrown. 696 */ sendPollingRequest(@onNull final FileDescriptor fd, @NonNull final byte[] msg)697 public void sendPollingRequest(@NonNull final FileDescriptor fd, @NonNull final byte[] msg) 698 throws ErrnoException, InterruptedIOException { 699 Os.write(fd, msg, 0 /* byteOffset */, msg.length); 700 } 701 702 /** 703 * Look up the value of a property in DeviceConfig. 704 * @param namespace The namespace containing the property to look up. 705 * @param name The name of the property to look up. 706 * @param defaultValue The value to return if the property does not exist or has no non-null 707 * value. 708 * @return the corresponding value, or defaultValue if none exists. 709 */ getDeviceConfigPropertyInt(@onNull final String namespace, @NonNull final String name, final int defaultValue)710 public int getDeviceConfigPropertyInt(@NonNull final String namespace, 711 @NonNull final String name, final int defaultValue) { 712 return DeviceConfigUtils.getDeviceConfigPropertyInt(namespace, name, defaultValue); 713 } 714 715 /** 716 * Receive the request message from kernel via given fd. 717 */ recvMessage(@onNull final FileDescriptor fd)718 public ByteBuffer recvMessage(@NonNull final FileDescriptor fd) 719 throws ErrnoException, InterruptedIOException { 720 return NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS); 721 } 722 getContext()723 public Context getContext() { 724 return mContext; 725 } 726 727 /** 728 * Get an INetd connector. 729 */ getNetd()730 public INetd getNetd() { 731 return INetd.Stub.asInterface( 732 (IBinder) mContext.getSystemService(Context.NETD_SERVICE)); 733 } 734 735 /** Add device config change listener */ addDeviceConfigChangedListener( @onNull final DeviceConfig.OnPropertiesChangedListener listener)736 public void addDeviceConfigChangedListener( 737 @NonNull final DeviceConfig.OnPropertiesChangedListener listener) { 738 DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CONNECTIVITY, 739 AsyncTask.THREAD_POOL_EXECUTOR, listener); 740 } 741 742 /** Remove device config change listener */ removeDeviceConfigChangedListener( @onNull final DeviceConfig.OnPropertiesChangedListener listener)743 public void removeDeviceConfigChangedListener( 744 @NonNull final DeviceConfig.OnPropertiesChangedListener listener) { 745 DeviceConfig.removeOnPropertiesChangedListener(listener); 746 } 747 748 /** Add receiver for detecting doze mode change to control TCP detection. */ 749 @TargetApi(Build.VERSION_CODES.TIRAMISU) addDeviceIdleReceiver(@onNull final BroadcastReceiver receiver, boolean shouldDisableInDeepDoze, boolean shouldDisableInLightDoze)750 public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver, 751 boolean shouldDisableInDeepDoze, boolean shouldDisableInLightDoze) { 752 // No need to register receiver if no related feature is enabled. 753 if (!shouldDisableInDeepDoze && !shouldDisableInLightDoze) return; 754 755 final IntentFilter intentFilter = new IntentFilter(); 756 if (shouldDisableInDeepDoze) { 757 intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); 758 } 759 if (shouldDisableInLightDoze) { 760 intentFilter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED); 761 } 762 mContext.registerReceiver(receiver, intentFilter); 763 } 764 765 /** Remove broadcast receiver. */ removeBroadcastReceiver(@onNull final BroadcastReceiver receiver, boolean shouldDisableInDeepDoze, boolean shouldDisableInLightDoze)766 public void removeBroadcastReceiver(@NonNull final BroadcastReceiver receiver, 767 boolean shouldDisableInDeepDoze, boolean shouldDisableInLightDoze) { 768 if (!shouldDisableInDeepDoze && !shouldDisableInLightDoze) return; 769 mContext.unregisterReceiver(receiver); 770 } 771 772 /** 773 * Get whether polling should be disabled in light doze mode. This method should 774 * only be called once in the constructor, to ensure that the code does not need 775 * to deal with flag values changing at runtime. 776 */ 777 @TargetApi(Build.VERSION_CODES.TIRAMISU) shouldDisableInLightDoze(boolean ignoreBlockedUidsSupported)778 public boolean shouldDisableInLightDoze(boolean ignoreBlockedUidsSupported) { 779 // Light doze mode status checking API is only available at T or later releases. 780 if (!SdkLevel.isAtLeastT()) return false; 781 782 // Disable light doze mode design is replaced by ignoring blocked uids design. 783 if (ignoreBlockedUidsSupported) return false; 784 785 return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut( 786 mContext, SKIP_TCP_POLL_IN_LIGHT_DOZE); 787 } 788 789 /** 790 * Get whether the ignore Tcp info for blocked uids is supported. This method should 791 * only be called once in the constructor, to ensure that the code does not need 792 * to deal with flag values changing at runtime. 793 */ shouldIgnoreTcpInfoForBlockedUids()794 public boolean shouldIgnoreTcpInfoForBlockedUids() { 795 // Note b/326143935 - can trigger crash due to kernel bug / missing 796 // feature on some T devices. 797 return SdkLevel.isAtLeastU() && DeviceConfigUtils.isFeatureSupported( 798 mContext, FEATURE_IS_UID_NETWORKING_BLOCKED) 799 && DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(mContext, 800 IGNORE_TCP_INFO_FOR_BLOCKED_UIDS); 801 } 802 } 803 } 804