• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                     // &lt;linux_src&gt;/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