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