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