• 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 
17 package com.android.networkstack.netlink;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
21 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
22 import static android.net.util.DataStallUtils.CONFIG_TCP_PACKETS_FAIL_PERCENTAGE;
23 import static android.net.util.DataStallUtils.DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE;
24 import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
25 import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
26 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
27 import static android.system.OsConstants.AF_INET;
28 
29 import static com.android.net.module.util.NetworkStackConstants.DNS_OVER_TLS_PORT;
30 
31 import static junit.framework.Assert.assertEquals;
32 import static junit.framework.Assert.assertFalse;
33 import static junit.framework.Assert.assertTrue;
34 
35 import static org.junit.Assume.assumeTrue;
36 import static org.mockito.ArgumentMatchers.anyBoolean;
37 import static org.mockito.ArgumentMatchers.anyInt;
38 import static org.mockito.ArgumentMatchers.eq;
39 import static org.mockito.Mockito.any;
40 import static org.mockito.Mockito.doReturn;
41 import static org.mockito.Mockito.never;
42 import static org.mockito.Mockito.times;
43 import static org.mockito.Mockito.verify;
44 import static org.mockito.Mockito.when;
45 
46 import android.annotation.IntDef;
47 import android.content.BroadcastReceiver;
48 import android.content.Context;
49 import android.content.Intent;
50 import android.net.ConnectivityManager;
51 import android.net.INetd;
52 import android.net.InetAddresses;
53 import android.net.LinkProperties;
54 import android.net.MarkMaskParcel;
55 import android.net.Network;
56 import android.net.NetworkCapabilities;
57 import android.os.Build;
58 import android.os.PowerManager;
59 import android.util.Log;
60 import android.util.Log.TerribleFailureHandler;
61 
62 import androidx.test.filters.SmallTest;
63 import androidx.test.platform.app.InstrumentationRegistry;
64 import androidx.test.runner.AndroidJUnit4;
65 
66 import com.android.modules.utils.build.SdkLevel;
67 import com.android.net.module.util.DeviceConfigUtils;
68 import com.android.net.module.util.FeatureVersions;
69 import com.android.net.module.util.netlink.NetlinkUtils;
70 import com.android.net.module.util.netlink.StructNlMsgHdr;
71 import com.android.testutils.DevSdkIgnoreRule;
72 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
73 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
74 
75 import libcore.util.HexEncoding;
76 
77 import org.junit.After;
78 import org.junit.Before;
79 import org.junit.Rule;
80 import org.junit.Test;
81 import org.junit.runner.RunWith;
82 import org.mockito.ArgumentCaptor;
83 import org.mockito.Mock;
84 import org.mockito.MockitoAnnotations;
85 
86 import java.io.FileDescriptor;
87 import java.lang.annotation.Retention;
88 import java.lang.annotation.RetentionPolicy;
89 import java.net.InetAddress;
90 import java.nio.ByteBuffer;
91 import java.nio.ByteOrder;
92 import java.util.ArrayList;
93 
94 // TODO: Add more tests for missing coverage.
95 @RunWith(AndroidJUnit4.class)
96 @SmallTest
97 public class TcpSocketTrackerTest {
98     private static final int TEST_BUFFER_SIZE = 1024;
99     private static final String DIAG_MSG_HEX =
100             // struct nlmsghdr.
101             "10000000" +     // length = 16
102             "1400" +         // type = SOCK_DIAG_BY_FAMILY
103             "0301" +         // flags = NLM_F_REQUEST | NLM_F_DUMP
104             "00000000" +     // seqno
105             "00000000";      // pid (0 == kernel)
106     private static final byte[] SOCK_DIAG_MSG_BYTES =
107             HexEncoding.decode(DIAG_MSG_HEX.toCharArray(), false);
108     // Hexadecimal representation of a SOCK_DIAG response with tcp info.
109     private static final String SOCK_DIAG_TCP_TEST_HEX =
110             composeSockDiagTcpHex(5 /* retrans */, 10 /* sent */);
111     private static final byte[] SOCK_DIAG_TCP_INET_TEST_BYTES =
112             HexEncoding.decode(SOCK_DIAG_TCP_TEST_HEX.toCharArray(), false);
113     private static final TcpInfo TEST_TCPINFO =
114             new TcpInfo(10 /* segsOut */, 0 /* segsIn */, 5 /* totalRetrans */);
115     private static final String NLMSG_DONE_HEX =
116             // struct nlmsghdr
117             "14000000"     // length = 20
118             + "0300"         // type = NLMSG_DONE
119             + "0301"         // flags = NLM_F_REQUEST | NLM_F_DUMP
120             + "00000000"     // seqno
121             + "00000000"     // pid (0 == kernel)
122             // struct inet_diag_req_v2
123             + "02"           // family = AF_INET
124             + "06"           // state
125             + "00"           // timer
126             + "00";          // retrans
127     private static final String TEST_RESPONSE_HEX = SOCK_DIAG_TCP_TEST_HEX + NLMSG_DONE_HEX;
128     private static final byte[] TEST_RESPONSE_BYTES =
129             HexEncoding.decode(TEST_RESPONSE_HEX.toCharArray(), false);
130     private static final int TEST_NETID1 = 0xA85;
131     private static final int TEST_NETID2 = 0x1A85;
132     private static final int TEST_NETID1_FWMARK = 0x0A85;
133     private static final int TEST_NETID2_FWMARK = 0x1A85;
134     private static final int NETID_MASK = 0xffff;
135     private static final int TEST_UID1 = 1234;
136     private static final int TEST_UID2 = TEST_UID1 + 1;
137     private static final short TEST_DST_PORT = 29113;
138     private static final long TEST_COOKIE1 = 43387759684916L;
139     private static final long TEST_COOKIE2 = TEST_COOKIE1 + 1;
140     private static final InetAddress TEST_DNS1 = InetAddresses.parseNumericAddress("8.8.8.8");
141 
142     private static final NetworkCapabilities CELL_METERED_CAPABILITIES =
143             new NetworkCapabilities()
144                     .addTransportType(TRANSPORT_CELLULAR)
145                     .addCapability(NET_CAPABILITY_INTERNET);
146 
147     private static final NetworkCapabilities CELL_NOT_METERED_CAPABILITIES =
148             new NetworkCapabilities()
149                     .addTransportType(TRANSPORT_CELLULAR)
150                     .addCapability(NET_CAPABILITY_INTERNET)
151                     .addCapability(NET_CAPABILITY_NOT_METERED);
152     @Mock private TcpSocketTracker.Dependencies mDependencies;
153     @Mock private INetd mNetd;
154     private final Network mNetwork = new Network(TEST_NETID1);
155     private final Network mOtherNetwork = new Network(TEST_NETID2);
156     private TerribleFailureHandler mOldWtfHandler;
157     @Mock private Context mContext;
158     private final Context mRealContext = InstrumentationRegistry.getInstrumentation().getContext();
159     @Mock private PowerManager mPowerManager;
160     @Mock private ConnectivityManager mCm;
161 
162     @Rule
163     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
164 
165     @Before
setUp()166     public void setUp() throws Exception {
167         MockitoAnnotations.initMocks(this);
168         // Override the default TerribleFailureHandler, as that handler might terminate the process
169         // (if we're on an eng build).
170         mOldWtfHandler =
171                 Log.setWtfHandler((tag, what, system) -> Log.e(tag, what.getMessage(), what));
172         when(mDependencies.getNetd()).thenReturn(mNetd);
173         when(mDependencies.connectToKernel()).thenReturn(new FileDescriptor());
174         when(mDependencies.getDeviceConfigPropertyInt(
175                 eq(NAMESPACE_CONNECTIVITY),
176                 eq(CONFIG_TCP_PACKETS_FAIL_PERCENTAGE),
177                 anyInt())).thenReturn(DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE);
178         when(mDependencies.shouldDisableInLightDoze(anyBoolean())).thenReturn(true);
179 
180         when(mNetd.getFwmarkForNetwork(eq(TEST_NETID1)))
181                 .thenReturn(makeMarkMaskParcel(NETID_MASK, TEST_NETID1_FWMARK));
182         doReturn(mContext).when(mDependencies).getContext();
183         doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
184         doReturn(mCm).when(mContext).getSystemService(ConnectivityManager.class);
185     }
186 
187     @After
tearDown()188     public void tearDown() {
189         Log.setWtfHandler(mOldWtfHandler);
190     }
191 
makeMarkMaskParcel(final int mask, final int mark)192     private MarkMaskParcel makeMarkMaskParcel(final int mask, final int mark) {
193         final MarkMaskParcel parcel = new MarkMaskParcel();
194         parcel.mask = mask;
195         parcel.mark = mark;
196         return parcel;
197     }
198 
getByteBufferFromHexString(String hexStr)199     private ByteBuffer getByteBufferFromHexString(String hexStr) {
200         final byte[] bytes = HexEncoding.decode(hexStr.toCharArray(), false);
201         return getByteBuffer(bytes);
202     }
203 
getByteBuffer(final byte[] bytes)204     private ByteBuffer getByteBuffer(final byte[] bytes) {
205         final ByteBuffer buffer = ByteBuffer.wrap(bytes);
206         buffer.order(ByteOrder.nativeOrder());
207         return buffer;
208     }
209 
210     @Test
testParseSockInfo()211     public void testParseSockInfo() {
212         final ByteBuffer buffer = getByteBuffer(SOCK_DIAG_TCP_INET_TEST_BYTES);
213         final ArrayList<TcpSocketTracker.SocketInfo> infoList = new ArrayList<>();
214         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
215         tst.parseMessage(buffer, AF_INET, infoList, 100L);
216         assertEquals(1, infoList.size());
217         final TcpSocketTracker.SocketInfo parsed = infoList.get(0);
218 
219         assertEquals(parsed.tcpInfo, TEST_TCPINFO);
220         assertEquals(parsed.fwmark, 789125);
221         assertEquals(parsed.updateTime, 100);
222         assertEquals(parsed.ipFamily, AF_INET);
223         assertEquals(parsed.uid, TEST_UID1);
224         assertEquals(parsed.cookie, TEST_COOKIE1);
225         assertEquals(parsed.dstPort, TEST_DST_PORT);
226     }
227 
228     @Test
testEnoughBytesRemainForValidNlMsg()229     public void testEnoughBytesRemainForValidNlMsg() {
230         final ByteBuffer buffer = ByteBuffer.allocate(TEST_BUFFER_SIZE);
231 
232         buffer.position(TEST_BUFFER_SIZE - StructNlMsgHdr.STRUCT_SIZE);
233         assertTrue(NetlinkUtils.enoughBytesRemainForValidNlMsg(buffer));
234         // Remaining buffer size is less than a valid StructNlMsgHdr size.
235         buffer.position(TEST_BUFFER_SIZE - StructNlMsgHdr.STRUCT_SIZE + 1);
236         assertFalse(NetlinkUtils.enoughBytesRemainForValidNlMsg(buffer));
237 
238         buffer.position(TEST_BUFFER_SIZE);
239         assertFalse(NetlinkUtils.enoughBytesRemainForValidNlMsg(buffer));
240     }
241 
242     @Test
testPollSocketsInfo()243     public void testPollSocketsInfo() throws Exception {
244         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
245 
246         // No enough bytes remain for a valid NlMsg.
247         final ByteBuffer invalidBuffer = ByteBuffer.allocate(1);
248         invalidBuffer.order(ByteOrder.nativeOrder());
249         when(mDependencies.recvMessage(any())).thenReturn(invalidBuffer);
250         assertTrue(tst.pollSocketsInfo());
251         assertEquals(-1, tst.getLatestPacketFailPercentage());
252         assertEquals(0, tst.getSentSinceLastRecv());
253 
254         // Header only.
255         final ByteBuffer headerBuffer = getByteBuffer(SOCK_DIAG_MSG_BYTES);
256         when(mDependencies.recvMessage(any())).thenReturn(headerBuffer);
257         assertTrue(tst.pollSocketsInfo());
258         assertEquals(-1, tst.getLatestPacketFailPercentage());
259         assertEquals(0, tst.getSentSinceLastRecv());
260 
261         setupNormalTestTcpInfo();
262         assertTrue(tst.pollSocketsInfo());
263 
264         assertEquals(10, tst.getSentSinceLastRecv());
265         assertEquals(50, tst.getLatestPacketFailPercentage());
266         assertFalse(tst.isDataStallSuspected());
267         // Lower the threshold.
268         when(mDependencies.getDeviceConfigPropertyInt(any(), eq(CONFIG_TCP_PACKETS_FAIL_PERCENTAGE),
269                 anyInt())).thenReturn(40);
270         // No device config change. Using cache value.
271         assertFalse(tst.isDataStallSuspected());
272         // Trigger a config update
273         tst.mConfigListener.onPropertiesChanged(null /* properties */);
274         assertTrue(tst.isDataStallSuspected());
275     }
276 
277     @Test
testPollSocketsInfo_ignorePrivateDnsPort()278     public void testPollSocketsInfo_ignorePrivateDnsPort() throws Exception {
279         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
280         // Simulate 1 message with data stall happened.
281         doReturn(getByteBufferFromHexString(
282                         composeSockDiagTcpHex(9, 10) + NLMSG_DONE_HEX))
283                 .when(mDependencies).recvMessage(any());
284         assertTrue(tst.pollSocketsInfo());
285 
286         // 9 retrans / 10 sent = 90 percent.
287         assertEquals(90, tst.getLatestPacketFailPercentage());
288         assertEquals(10, tst.getSentSinceLastRecv());
289         assertTrue(tst.isDataStallSuspected());
290 
291         // Append another message with private dns port which is generated
292         // in opportunistic mode. Also simulated the private dns probe is not finished.
293         tst.setOpportunisticMode(true);
294         final LinkProperties testLp = new LinkProperties();
295         testLp.addDnsServer(TEST_DNS1);
296         tst.setLinkProperties(testLp);
297         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10)
298                 + composeSockDiagTcpHex(9, 10, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID1)
299                 + NLMSG_DONE_HEX))
300                 .when(mDependencies).recvMessage(any());
301         assertTrue(tst.pollSocketsInfo());
302 
303         // Verify that when in opportunistic mode, the message with private dns
304         // port won't get involved with the calculation.
305         // While there is no packet sent in this polling cycle, 0 percentage is expected while the
306         // sent counter remains the same.
307         assertEquals(0, tst.getLatestPacketFailPercentage());
308         assertEquals(10, tst.getSentSinceLastRecv());
309         assertFalse(tst.isDataStallSuspected());
310 
311         // Verify that when private dns servers are all validated, the message with private dns port
312         // will be counted.
313         testLp.addValidatedPrivateDnsServer(TEST_DNS1);
314         tst.setLinkProperties(testLp);
315         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(10, 12)
316                 + composeSockDiagTcpHex(11, 12, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID1)
317                 + NLMSG_DONE_HEX))
318                 .when(mDependencies).recvMessage(any());
319         assertTrue(tst.pollSocketsInfo());
320         // Retrans ( 1 + 2 ) / ( 2 + 2 ) sent = 75 percent.
321         assertEquals(75, tst.getLatestPacketFailPercentage());
322         assertEquals(14, tst.getSentSinceLastRecv());
323         assertFalse(tst.isDataStallSuspected());
324 
325         // Verify that when exited opportunistic mode, the message with private dns port will be
326         // counted. And the stat is correctly subtracted from the stat ignored in the previous
327         // polling cycle.
328         tst.setOpportunisticMode(false);
329         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(11, 14)
330                 + composeSockDiagTcpHex(13, 14, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID1)
331                 + NLMSG_DONE_HEX))
332                 .when(mDependencies).recvMessage(any());
333         assertTrue(tst.pollSocketsInfo());
334         // Retrans ( 1 + 2 ) / ( 2 + 2 ) sent = 75 percent.
335         assertEquals(75, tst.getLatestPacketFailPercentage());
336         assertEquals(18, tst.getSentSinceLastRecv());
337         assertFalse(tst.isDataStallSuspected());
338     }
339 
340     // b/326143935 isUidNetworkingBlocked is not supported on pre-U device.
341     @IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
342     @Test
testPollSocketsInfo_ignoreBlockedUid_featureDisabled_beforeU()343     public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_beforeU() throws Exception {
344         doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled();
345     }
346 
347     // b/326143935 isUidNetworkingBlocked is not supported on pre-U device.
348     @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
349     @Test
testPollSocketsInfo_ignoreBlockedUid_featureDisabled_UOrAbove()350     public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_UOrAbove() throws Exception {
351         // Test only if the Tethering module is new enough to support the API.
352         assumeTrue(DeviceConfigUtils.isFeatureSupported(mRealContext,
353                 FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED));
354         doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled();
355         verify(mCm, never()).isUidNetworkingBlocked(anyInt(), anyBoolean());
356     }
357 
doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled()358     private void doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled() throws Exception {
359         doReturn(false).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
360         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
361         // Simulate 1 message with data stall happened.
362         doReturn(getByteBufferFromHexString(
363                 composeSockDiagTcpHex(4, 10) + NLMSG_DONE_HEX))
364                 .when(mDependencies).recvMessage(any());
365         assertTrue(tst.pollSocketsInfo());
366         // 4 retran / 10 sent = 40 percent.
367         assertEquals(40, tst.getLatestPacketFailPercentage());
368         assertEquals(10, tst.getSentSinceLastRecv());
369         assertFalse(tst.isDataStallSuspected());
370 
371         // With the feature disabled, append another message with blocked uid, verify the
372         // traffic of networking-blocked uid is not filtered.
373         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10)
374                 + composeSockDiagTcpHex(5, 10, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID2)
375                 + NLMSG_DONE_HEX))
376                 .when(mDependencies).recvMessage(any());
377         assertTrue(tst.pollSocketsInfo());
378         // 5 + 5 retrans / 10 sent = 100 percent.
379         assertEquals(100, tst.getLatestPacketFailPercentage());
380         assertEquals(20, tst.getSentSinceLastRecv());
381         assertTrue(tst.isDataStallSuspected());
382     }
383 
384     // b/326143935 isUidNetworkingBlocked is not supported on pre-U device.
385     @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
386     @Test
testPollSocketsInfo_ignoreBlockedUid_featureEnabled()387     public void testPollSocketsInfo_ignoreBlockedUid_featureEnabled() throws Exception {
388         // Test only if the Tethering module is new enough to support the API.
389         assumeTrue(DeviceConfigUtils.isFeatureSupported(mRealContext,
390                 FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED));
391         doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
392         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
393         tst.setNetworkCapabilities(CELL_NOT_METERED_CAPABILITIES);
394         doReturn(true).when(mCm).isUidNetworkingBlocked(TEST_UID2, false /* metered */);
395         // With the feature enabled, append another message with blocked uid, verify the
396         // traffic of networking-blocked uid is filtered out.
397         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(4, 10)
398                 + composeSockDiagTcpHex(6, 12, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID2)
399                 + NLMSG_DONE_HEX))
400                 .when(mDependencies).recvMessage(any());
401         assertTrue(tst.pollSocketsInfo());
402         assertEquals(40, tst.getLatestPacketFailPercentage());
403         assertEquals(10, tst.getSentSinceLastRecv());
404         assertFalse(tst.isDataStallSuspected());
405 
406         // Unblock traffic of the uid, verify the traffic of the uid is not filtered.
407         doReturn(false).when(mCm).isUidNetworkingBlocked(TEST_UID2, false /* metered */);
408         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(4, 10)
409                 + composeSockDiagTcpHex(8, 14, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID2)
410                 + NLMSG_DONE_HEX))
411                 .when(mDependencies).recvMessage(any());
412         assertTrue(tst.pollSocketsInfo());
413         // Lost 2 / 2 sent = 100 percent.
414         assertEquals(100, tst.getLatestPacketFailPercentage());
415         assertEquals(12, tst.getSentSinceLastRecv());
416         assertTrue(tst.isDataStallSuspected());
417     }
418 
419     // b/326143935 isUidNetworkingBlocked is not supported on pre-U device.
420     @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
421     @Test
testPollSocketsInfo_ignoreBlockedUid_featureEnabled_dataSaver()422     public void testPollSocketsInfo_ignoreBlockedUid_featureEnabled_dataSaver() throws Exception {
423         // Test only if the Tethering module is new enough to support the API.
424         assumeTrue(DeviceConfigUtils.isFeatureSupported(mRealContext,
425                 FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED));
426         doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
427         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
428 
429         tst.setNetworkCapabilities(CELL_NOT_METERED_CAPABILITIES);
430         final ByteBuffer mockMessage = getByteBufferFromHexString(composeSockDiagTcpHex(4, 10)
431                 + NLMSG_DONE_HEX);
432         doReturn(mockMessage).when(mDependencies).recvMessage(any());
433         assertTrue(tst.pollSocketsInfo());
434         verify(mCm).isUidNetworkingBlocked(TEST_UID1, false /* metered */);
435 
436         // Verify the metered parameter will be correctly passed to ConnectivityManager.
437         tst.setNetworkCapabilities(CELL_METERED_CAPABILITIES);
438         mockMessage.rewind(); // Reset read position to 0 since the same buffer is used.
439         assertTrue(tst.pollSocketsInfo());
440         verify(mCm).isUidNetworkingBlocked(TEST_UID1, true /* metered */);
441 
442         // Correctness of the logic which handling different blocked status is
443         // verified in other tests, see {@code testPollSocketsInfo_ignoreBlockedUid_featureEnabled}.
444     }
445 
446     @Test
testTcpInfoParsingWithMultipleMsgs()447     public void testTcpInfoParsingWithMultipleMsgs() throws Exception {
448         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
449 
450         // Case 1: A message about 5 sockets, then a message about 2 sockets,
451         // then a message about 2 sockets together with DONE
452         //
453         // Mocking 6 return results for different IP families(3 for IPv6; 3 for Ipv4). Use the same
454         // message for different IP families to reduce the complexity.
455         doReturn(getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 10), 5)),
456                 getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 10), 2)),
457                 getByteBufferFromHexString(
458                         repeat(composeSockDiagTcpHex(5, 10), 2) + NLMSG_DONE_HEX),
459                 getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 10), 5)),
460                 getByteBufferFromHexString(repeat(composeSockDiagTcpHex(5, 10), 2)),
461                 getByteBufferFromHexString(
462                         repeat(composeSockDiagTcpHex(5, 10), 2) + NLMSG_DONE_HEX))
463                 .when(mDependencies).recvMessage(any());
464 
465         assertTrue(tst.pollSocketsInfo());
466         // Verify that code reads all the messages. (3 times for IPv4, 3 times for IPv6)
467         verify(mDependencies, times(6)).recvMessage(any());
468         // Calculated from totalRetrans / segsout.
469         // Note that the counters cannot be verified given that the cookie of the mocked sockets
470         // are the same, the latest SocketInfo would overwrite previous reported ones.
471         assertEquals(50, tst.getLatestPacketFailPercentage());
472         // Lower than the 80% threshold
473         assertFalse(tst.isDataStallSuspected());
474 
475         // Case 2: A message about 1 socket, then a message about 5 sockets,
476         // then a message about 1 socket with DONE.
477         // "Sent" increases by 5. No change for lost and retrans.
478         //
479         // Mocking 6 return results for different IP families(3 for IPv6; 3 for Ipv4). Use the same
480         // message for different IP families to reduce the complexity.
481         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(10, 15)),
482                 getByteBufferFromHexString(repeat(composeSockDiagTcpHex(10, 15), 5)),
483                 getByteBufferFromHexString(composeSockDiagTcpHex(10, 15) + NLMSG_DONE_HEX),
484                 getByteBufferFromHexString(composeSockDiagTcpHex(10, 15)),
485                 getByteBufferFromHexString(repeat(composeSockDiagTcpHex(10, 15), 5)),
486                 getByteBufferFromHexString(composeSockDiagTcpHex(10, 15) + NLMSG_DONE_HEX))
487                 .when(mDependencies).recvMessage(any());
488 
489         assertTrue(tst.pollSocketsInfo());
490         // Not reset mDependencies because it will reset other mocks.
491         // Another 3 times for IPv6 and 3 times for IPv4
492         verify(mDependencies, times(12)).recvMessage(any());
493         // (5 lost + 0 retrans)/5 sent
494         assertEquals(100, tst.getLatestPacketFailPercentage());
495         assertTrue(tst.isDataStallSuspected());
496 
497         // Case 3: A message about 5 sockets, then a message about 1 socket,
498         // then a message about 1 socket with DONE.
499         // No change for sent, lost and retrans.
500         //
501         // Mocking 4 return results for different IP families(2 for IPv6; 2 for Ipv4). Use the same
502         // message for different IP families to reduce the complexity.
503         doReturn(getByteBufferFromHexString(repeat(composeSockDiagTcpHex(10, 15), 5)),
504                 getByteBufferFromHexString(composeSockDiagTcpHex(10, 15)),
505                 getByteBufferFromHexString(composeSockDiagTcpHex(10, 15) + NLMSG_DONE_HEX),
506                 getByteBufferFromHexString(repeat(composeSockDiagTcpHex(10, 15), 5)),
507                 getByteBufferFromHexString(composeSockDiagTcpHex(10, 15)),
508                 getByteBufferFromHexString(composeSockDiagTcpHex(10, 15) + NLMSG_DONE_HEX))
509                 .when(mDependencies).recvMessage(any());
510 
511         assertTrue(tst.pollSocketsInfo());
512         // Another 3 times for IPv6 and 3 times for IPv4
513         verify(mDependencies, times(18)).recvMessage(any());
514         // (0 lost + 0 retrans)/0 sent
515         assertEquals(0, tst.getLatestPacketFailPercentage());
516         // Lower than the 80% threshold
517         assertFalse(tst.isDataStallSuspected());
518 
519         // Case 4: A message about 8 sockets with DONE.
520         // "lost" increases by 3 and "sent" increases by 5
521         //
522         // Mocking 2 return results for different IP families(1 for IPv6; 1 for Ipv4). Use the same
523         // message for different IP families to reduce the complexity.
524         doReturn(getByteBufferFromHexString(
525                         repeat(composeSockDiagTcpHex(14, 20), 8) + NLMSG_DONE_HEX),
526                 getByteBufferFromHexString(
527                         repeat(composeSockDiagTcpHex(14, 20), 8) + NLMSG_DONE_HEX))
528                 .when(mDependencies).recvMessage(any());
529 
530         assertTrue(tst.pollSocketsInfo());
531         // Another 1 time for IPv6 and 1 time for IPv4
532         verify(mDependencies, times(20)).recvMessage(any());
533         // (4 lost + 0 retrans)/5 sent
534         assertEquals(80, tst.getLatestPacketFailPercentage());
535         //Reach 80% threshold
536         assertTrue(tst.isDataStallSuspected());
537 
538         // Case 5: A message about DONE with 2 sockets.
539         // No socket information will be parsed though "lost" increases by 6 and "sent"
540         // increases by 6.
541         //
542         // Mocking 2 return results for different IP families(1 for IPv6; 1 for Ipv4). Use the same
543         // message for different IP families to reduce the complexity.
544         doReturn(getByteBufferFromHexString(
545                         NLMSG_DONE_HEX + repeat(composeSockDiagTcpHex(20, 26), 2)),
546                 getByteBufferFromHexString(
547                         NLMSG_DONE_HEX + repeat(composeSockDiagTcpHex(20, 26), 2)))
548                 .when(mDependencies).recvMessage(any());
549         assertTrue(tst.pollSocketsInfo());
550         // Another 1 time for IPv6 and 1 time for IPv4
551         verify(mDependencies, times(22)).recvMessage(any());
552         // (0 lost + 0 retrans)/0 sent.
553         // Parsing will be stopped in DONE message. No socket information will be parsed.
554         assertEquals(0, tst.getLatestPacketFailPercentage());
555         // Lower than the 80% threshold
556         assertFalse(tst.isDataStallSuspected());
557     }
558 
repeat(String orig, int times)559     private String repeat(String orig, int times) {
560         if (SdkLevel.isAtLeastT()) {
561             // Only supported from Java 11
562             return orig.repeat(times);
563         } else {
564             String repeated = "";
565             for (int i = 0; i < times; i++) {
566                 repeated += orig;
567             }
568             return repeated;
569         }
570     }
571 
getHexStringFromInt(int v)572     private static String getHexStringFromInt(int v) {
573         // Android is always little-endian. Refer to https://developer.android.com/ndk/guides/abis.
574         return getHexStringOfSize(v, ByteOrder.nativeOrder(), Integer.BYTES);
575     }
576 
getHexStringFromShort(short v, ByteOrder order)577     private static String getHexStringFromShort(short v, ByteOrder order) {
578         return getHexStringOfSize(v, order, Short.BYTES);
579     }
580 
getHexStringFromLong(long v)581     private static String getHexStringFromLong(long v) {
582         // Android is always little-endian. Refer to https://developer.android.com/ndk/guides/abis.
583         return getHexStringOfSize(v, ByteOrder.nativeOrder(), Long.BYTES);
584     }
585 
getHexStringOfSize(long v, ByteOrder order, int size)586     private static String getHexStringOfSize(long v, ByteOrder order, int size) {
587         final ByteBuffer bb = ByteBuffer.allocate(size);
588         bb.order(order);
589         switch (size) {
590             case Short.BYTES:
591                 bb.putShort((short) v);
592                 break;
593             case Integer.BYTES:
594                 bb.putInt((int) v);
595                 break;
596             case Long.BYTES:
597                 bb.putLong(v);
598                 break;
599             default:
600                 throw new IllegalArgumentException("Unsupported size: " + size);
601         }
602         String s = "";
603         for (byte b : bb.array()) {
604             s += String.format("%02X", b);
605         }
606         return s;
607     }
608 
composeSockDiagTcpHex(int retrans, int sent)609     private static String composeSockDiagTcpHex(int retrans, int sent) {
610         return composeSockDiagTcpHex(retrans, sent, TEST_DST_PORT, TEST_COOKIE1, TEST_UID1);
611     }
612 
composeSockDiagTcpHex(int retrans, int sent, short dstPort, long cookie, int uid)613     private static String composeSockDiagTcpHex(int retrans, int sent, short dstPort,
614             long cookie, int uid) {
615         return // struct nlmsghdr.
616                 "14010000"          // length = 276
617                 + "1400"            // type = SOCK_DIAG_BY_FAMILY
618                 + "0301"            // flags = NLM_F_REQUEST | NLM_F_DUMP
619                 + "00000000"        // seqno
620                 + "00000000"        // pid (0 == kernel)
621                 // struct inet_diag_req_v2
622                 + "02"              // family = AF_INET
623                 + "06"              // state
624                 + "00"              // timer
625                 + "00"              // retrans
626                 // inet_diag_sockid: ports and addresses are always in big endian,
627                 // see StructInetDiagSockId.
628                 + "DEA5"                                                // idiag_sport = 56997
629                 + getHexStringFromShort(dstPort, ByteOrder.BIG_ENDIAN)  // idiag_dport
630                 + "0a006402000000000000000000000000"                    // idiag_src = 10.0.100.2
631                 + "08080808000000000000000000000000"                    // idiag_dst = 8.8.8.8
632                 + "00000000"                                            // idiag_if
633                 + getHexStringFromLong(cookie)                          // idiag_cookie
634                 + "00000000"                                            // idiag_expires
635                 + "00000000"                                            // idiag_rqueue
636                 + "00000000"                                            // idiag_wqueue
637                 + getHexStringFromInt(uid)                              // idiag_uid
638                 + "00000000"                                            // idiag_inode
639                 // rtattr
640                 + "0500"            // len = 5
641                 + "0800"            // type = 8
642                 + "00000000"        // data
643                 + "0800"            // len = 8
644                 + "0F00"            // type = 15(INET_DIAG_MARK)
645                 + "850A0C00"        // data, socket mark=789125
646                 + "AC00"            // len = 172
647                 + "0200"            // type = 2(INET_DIAG_INFO)
648                 // tcp_info
649                 + "01"              // state = TCP_ESTABLISHED
650                 + "00"              // ca_state = TCP_CA_OPEN
651                 + "05"              // retransmits = 5
652                 + "00"              // probes = 0
653                 + "00"              // backoff = 0
654                 + "07"              // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
655                 + "88"              // wscale = 8
656                 + "00"              // delivery_rate_app_limited = 0
657                 + "4A911B00"        // rto = 1806666
658                 + "00000000"        // ato = 0
659                 + "2E050000"        // sndMss = 1326
660                 + "18020000"        // rcvMss = 536
661                 + "00000000"        // unsacked = 0
662                 + "00000000"        // acked = 0
663                 + "00000000"        // lost
664                 + "00000000"        // retrans = 0
665                 + "00000000"        // fackets = 0
666                 + "BB000000"        // lastDataSent = 187
667                 + "00000000"        // lastAckSent = 0
668                 + "BB000000"        // lastDataRecv = 187
669                 + "BB000000"        // lastDataAckRecv = 187
670                 + "DC050000"        // pmtu = 1500
671                 + "30560100"        // rcvSsthresh = 87600
672                 + "3E2C0900"        // rttt = 601150
673                 + "1F960400"        // rttvar = 300575
674                 + "78050000"        // sndSsthresh = 1400
675                 + "0A000000"        // sndCwnd = 10
676                 + "A8050000"        // advmss = 1448
677                 + "03000000"        // reordering = 3
678                 + "00000000"        // rcvrtt = 0
679                 + "30560100"        // rcvspace = 87600
680                 + getHexStringFromInt(retrans)   // totalRetrans
681                 + "53AC000000000000"    // pacingRate = 44115
682                 + "FFFFFFFFFFFFFFFF"    // maxPacingRate = 18446744073709551615
683                 + "0100000000000000"    // bytesAcked = 1
684                 + "0000000000000000"    // bytesReceived = 0
685                 + getHexStringFromInt(sent) // SegsOut
686                 + "00000000"        // SegsIn = 0
687                 + "00000000"        // NotSentBytes = 0
688                 + "3E2C0900"        // minRtt = 601150
689                 + "00000000"        // DataSegsIn = 0
690                 + "00000000"        // DataSegsOut = 0
691                 + "0000000000000000"; // deliverRate = 0
692     }
693 
694     private static final int DEEP_DOZE = 0;
695     private static final int LIGHT_DOZE = 1;
696 
697     @Retention(RetentionPolicy.SOURCE)
698     @IntDef(value = {
699             DEEP_DOZE,
700             LIGHT_DOZE
701     })
702     private @interface DozeModeType {}
703 
704     @Test
testTcpInfoParsingWithDozeMode_enabled()705     public void testTcpInfoParsingWithDozeMode_enabled() throws Exception {
706         doReturn(false).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
707         doReturn(false).when(mDependencies).shouldDisableInLightDoze(anyBoolean());
708         doTestTcpInfoDisableParsingWithDozeMode(DEEP_DOZE, true /* featureEnabled */);
709     }
710 
711     // Ignore blocked uids is supported on U. Thus, for pre-U device this feature is always
712     // needed since there is no replacement.
713     @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
714     @Test
testTcpInfoParsingWithDozeMode_disabled()715     public void testTcpInfoParsingWithDozeMode_disabled() throws Exception {
716         // Test only if the Tethering module is new enough to support the API.
717         assumeTrue(DeviceConfigUtils.isFeatureSupported(mRealContext,
718                 FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED));
719         doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
720         doReturn(false).when(mDependencies).shouldDisableInLightDoze(anyBoolean());
721         doTestTcpInfoDisableParsingWithDozeMode(DEEP_DOZE, false /* featureEnabled */);
722     }
723 
724     @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
testTcpInfoDisableParsingWithLightDozeMode_enabled()725     public void testTcpInfoDisableParsingWithLightDozeMode_enabled() throws Exception {
726         doReturn(true).when(mDependencies).shouldDisableInLightDoze(anyBoolean());
727         doTestTcpInfoDisableParsingWithDozeMode(LIGHT_DOZE, true /* featureEnabled */);
728     }
729 
730     @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
testTcpInfoDisableParsingWithLightDozeMode_disabled()731     public void testTcpInfoDisableParsingWithLightDozeMode_disabled() throws Exception {
732         doReturn(false).when(mDependencies).shouldDisableInLightDoze(anyBoolean());
733         doTestTcpInfoDisableParsingWithDozeMode(LIGHT_DOZE, false /* featureEnabled */);
734     }
735 
doTestTcpInfoDisableParsingWithDozeMode(@ozeModeType int dozeModeType, boolean featureEnabled)736     private void doTestTcpInfoDisableParsingWithDozeMode(@DozeModeType int dozeModeType,
737             boolean featureEnabled) throws Exception {
738         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
739         tst.setNetworkCapabilities(CELL_NOT_METERED_CAPABILITIES);
740         final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
741                 ArgumentCaptor.forClass(BroadcastReceiver.class);
742 
743         // Enable doze mode with 1 netlink message.
744         verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(),
745                 anyBoolean(), anyBoolean());
746         final BroadcastReceiver receiver = receiverCaptor.getValue();
747         if (dozeModeType == DEEP_DOZE) {
748             doReturn(true).when(mPowerManager).isDeviceIdleMode();
749             receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
750         } else {
751             doReturn(true).when(mPowerManager).isDeviceLightIdleMode();
752             receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
753         }
754         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10)
755                 + NLMSG_DONE_HEX)).when(mDependencies).recvMessage(any());
756 
757         if (!featureEnabled) {
758             // Verify TcpInfo is still processed.
759             assertTrue(tst.pollSocketsInfo());
760             assertEquals(10, tst.getSentSinceLastRecv());
761             // Lost 4 + default 5 retrans / 10 sent.
762             assertEquals(90, tst.getLatestPacketFailPercentage());
763             assertTrue(tst.isDataStallSuspected());
764             return;
765         }
766 
767         // Verify counters are not updated.
768         assertFalse(tst.pollSocketsInfo());
769         assertEquals(0, tst.getSentSinceLastRecv());
770         // -1 if not enough packets.
771         assertEquals(-1, tst.getLatestPacketFailPercentage());
772         assertFalse(tst.isDataStallSuspected());
773 
774         // Disable deep/light doze mode, verify polling are processed and counters are updated.
775         if (dozeModeType == DEEP_DOZE) {
776             doReturn(false).when(mPowerManager).isDeviceIdleMode();
777             receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
778         } else {
779             doReturn(false).when(mPowerManager).isDeviceLightIdleMode();
780             receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
781         }
782         assertTrue(tst.pollSocketsInfo());
783         assertEquals(10, tst.getSentSinceLastRecv());
784         // Lost 4 + default 5 retrans / 10 sent.
785         assertEquals(90, tst.getLatestPacketFailPercentage());
786         assertTrue(tst.isDataStallSuspected());
787     }
788 
setupNormalTestTcpInfo()789     private void setupNormalTestTcpInfo() throws Exception {
790         final ByteBuffer tcpBufferV6 = getByteBuffer(TEST_RESPONSE_BYTES);
791         final ByteBuffer tcpBufferV4 = getByteBuffer(TEST_RESPONSE_BYTES);
792         doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvMessage(any());
793     }
794 
795     private static final String BAD_DIAG_MSG_HEX =
796         // struct nlmsghdr.
797             "00000058"      // length = 1476395008
798             + "1400"         // type = SOCK_DIAG_BY_FAMILY
799             + "0301"         // flags = NLM_F_REQUEST | NLM_F_DUMP
800             + "00000000"     // seqno
801             + "00000000"     // pid (0 == kernel)
802             // struct inet_diag_req_v2
803             + "02"           // family = AF_INET
804             + "06"           // state
805             + "00"           // timer
806             + "00"           // retrans
807             // inet_diag_sockid
808             + "DEA5"         // idiag_sport = 42462
809             + "71B9"         // idiag_dport = 47473
810             + "0a006402000000000000000000000000" // idiag_src = 10.0.100.2
811             + "08080808000000000000000000000000" // idiag_dst = 8.8.8.8
812             + "00000000"    // idiag_if
813             + "34ED000076270000" // idiag_cookie = 43387759684916
814             + "00000000"    // idiag_expires
815             + "00000000"    // idiag_rqueue
816             + "00000000"    // idiag_wqueue
817             + "00000000"    // idiag_uid
818             + "00000000";    // idiag_inode
819     private static final byte[] BAD_SOCK_DIAG_MSG_BYTES =
820         HexEncoding.decode(BAD_DIAG_MSG_HEX.toCharArray(), false);
821 
822     @Test
testPollSocketsInfo_BadFormat()823     public void testPollSocketsInfo_BadFormat() throws Exception {
824         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
825         setupNormalTestTcpInfo();
826         assertTrue(tst.pollSocketsInfo());
827         assertEquals(10, tst.getSentSinceLastRecv());
828         assertEquals(50, tst.getLatestPacketFailPercentage());
829 
830         final ByteBuffer badTcpBufferV6 = getByteBuffer(BAD_SOCK_DIAG_MSG_BYTES);
831         final ByteBuffer badTcpBufferV4 = getByteBuffer(BAD_SOCK_DIAG_MSG_BYTES);
832         doReturn(badTcpBufferV6, badTcpBufferV4).when(mDependencies).recvMessage(any());
833         assertTrue(tst.pollSocketsInfo());
834         // Expect no additional packets, so still 10.
835         assertEquals(10, tst.getSentSinceLastRecv());
836         // Expect to reset to 0.
837         assertEquals(0, tst.getLatestPacketFailPercentage());
838     }
839 
840     @Test
testUnMatchNetwork()841     public void testUnMatchNetwork() throws Exception {
842         when(mNetd.getFwmarkForNetwork(eq(TEST_NETID2)))
843                 .thenReturn(makeMarkMaskParcel(NETID_MASK, TEST_NETID2_FWMARK));
844         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mOtherNetwork);
845         setupNormalTestTcpInfo();
846         assertTrue(tst.pollSocketsInfo());
847 
848         assertEquals(0, tst.getSentSinceLastRecv());
849         assertEquals(-1, tst.getLatestPacketFailPercentage());
850         assertFalse(tst.isDataStallSuspected());
851     }
852 }
853