1 /* 2 * Copyright (C) 2016, 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.server.connectivity; 18 19 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO; 20 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME; 21 22 import static com.android.testutils.MiscAsserts.assertStringContains; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.junit.Assert.fail; 26 import static org.mockito.ArgumentMatchers.any; 27 import static org.mockito.Mockito.mock; 28 import static org.mockito.Mockito.verify; 29 30 import android.content.Context; 31 import android.net.ConnectivityManager; 32 import android.net.Network; 33 import android.net.NetworkCapabilities; 34 import android.os.Build; 35 import android.system.OsConstants; 36 import android.test.suitebuilder.annotation.SmallTest; 37 import android.util.Base64; 38 39 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; 40 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog; 41 import com.android.testutils.DevSdkIgnoreRule; 42 import com.android.testutils.DevSdkIgnoreRunner; 43 44 import org.junit.Before; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 import org.mockito.ArgumentCaptor; 48 49 import java.io.FileOutputStream; 50 import java.io.PrintWriter; 51 import java.io.StringWriter; 52 import java.util.Arrays; 53 import java.util.Comparator; 54 import java.util.List; 55 56 @RunWith(DevSdkIgnoreRunner.class) 57 @SmallTest 58 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) 59 public class NetdEventListenerServiceTest { 60 private static final String EXAMPLE_IPV4 = "192.0.2.1"; 61 private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1"; 62 63 private static final byte[] MAC_ADDR = 64 {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b}; 65 66 NetdEventListenerService mService; 67 ConnectivityManager mCm; 68 private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder() 69 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 70 .build(); 71 private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder() 72 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 73 .build(); 74 75 @Before setUp()76 public void setUp() { 77 mCm = mock(ConnectivityManager.class); 78 mService = new NetdEventListenerService(mCm); 79 } 80 81 @Test testWakeupEventLogging()82 public void testWakeupEventLogging() throws Exception { 83 final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH; 84 final long now = System.currentTimeMillis(); 85 final String iface = "wlan0"; 86 final byte[] mac = MAC_ADDR; 87 final String srcIp = "192.168.2.1"; 88 final String dstIp = "192.168.2.23"; 89 final String srcIp6 = "2001:db8:4:fd00:a585:13d1:6a23:4fb4"; 90 final String dstIp6 = "2001:db8:4006:807::200a"; 91 final int sport = 2356; 92 final int dport = 13489; 93 94 final int v4 = 0x800; 95 final int v6 = 0x86dd; 96 final int tcp = 6; 97 final int udp = 17; 98 final int icmp6 = 58; 99 100 // Baseline without any event 101 String[] baseline = listNetdEvent(); 102 103 int[] uids = {10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004}; 104 wakeupEvent(iface, uids[0], v4, tcp, mac, srcIp, dstIp, sport, dport, now); 105 wakeupEvent(iface, uids[1], v6, udp, mac, srcIp6, dstIp6, sport, dport, now); 106 wakeupEvent(iface, uids[2], v6, udp, mac, srcIp6, dstIp6, sport, dport, now); 107 wakeupEvent(iface, uids[3], v4, icmp6, mac, srcIp, dstIp, sport, dport, now); 108 wakeupEvent(iface, uids[4], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now); 109 wakeupEvent(iface, uids[5], v4, tcp, mac, srcIp, dstIp, sport, dport, now); 110 wakeupEvent(iface, uids[6], v6, udp, mac, srcIp6, dstIp6, sport, dport, now); 111 wakeupEvent(iface, uids[7], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now); 112 wakeupEvent(iface, uids[8], v6, udp, mac, srcIp6, dstIp6, sport, dport, now); 113 114 String[] events2 = remove(listNetdEvent(), baseline); 115 int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line 116 assertEquals(expectedLength2, events2.length); 117 assertStringContains(events2[0], "WakeupStats"); 118 assertStringContains(events2[0], "wlan0"); 119 assertStringContains(events2[0], "0x800"); 120 assertStringContains(events2[0], "0x86dd"); 121 for (int i = 0; i < uids.length; i++) { 122 String got = events2[i+1]; 123 assertStringContains(got, "WakeupEvent"); 124 assertStringContains(got, "wlan0"); 125 assertStringContains(got, "uid: " + uids[i]); 126 } 127 128 int uid = 20000; 129 for (int i = 0; i < BUFFER_LENGTH * 2; i++) { 130 long ts = now + 10; 131 wakeupEvent(iface, uid, 0x800, 6, mac, srcIp, dstIp, 23, 24, ts); 132 } 133 134 String[] events3 = remove(listNetdEvent(), baseline); 135 int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line 136 assertEquals(expectedLength3, events3.length); 137 assertStringContains(events2[0], "WakeupStats"); 138 assertStringContains(events2[0], "wlan0"); 139 for (int i = 1; i < expectedLength3; i++) { 140 String got = events3[i]; 141 assertStringContains(got, "WakeupEvent"); 142 assertStringContains(got, "wlan0"); 143 assertStringContains(got, "uid: " + uid); 144 } 145 146 uid = 45678; 147 wakeupEvent(iface, uid, 0x800, 6, mac, srcIp, dstIp, 23, 24, now); 148 149 String[] events4 = remove(listNetdEvent(), baseline); 150 String lastEvent = events4[events4.length - 1]; 151 assertStringContains(lastEvent, "WakeupEvent"); 152 assertStringContains(lastEvent, "wlan0"); 153 assertStringContains(lastEvent, "uid: " + uid); 154 } 155 156 @Test testWakeupStatsLogging()157 public void testWakeupStatsLogging() throws Exception { 158 final byte[] mac = MAC_ADDR; 159 final String srcIp = "192.168.2.1"; 160 final String dstIp = "192.168.2.23"; 161 final String srcIp6 = "2401:fa00:4:fd00:a585:13d1:6a23:4fb4"; 162 final String dstIp6 = "2404:6800:4006:807::200a"; 163 final int sport = 2356; 164 final int dport = 13489; 165 final long now = 1001L; 166 167 final int v4 = 0x800; 168 final int v6 = 0x86dd; 169 final int tcp = 6; 170 final int udp = 17; 171 final int icmp6 = 58; 172 173 wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now); 174 wakeupEvent("rmnet0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, now); 175 wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, now); 176 wakeupEvent("rmnet0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now); 177 wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now); 178 wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now); 179 wakeupEvent("rmnet0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now); 180 wakeupEvent("wlan0", 10004, v4, udp, mac, srcIp, dstIp, sport, dport, now); 181 wakeupEvent("wlan0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now); 182 wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now); 183 wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now); 184 wakeupEvent("rmnet0", 10052, v4, tcp, mac, srcIp, dstIp, sport, dport, now); 185 wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now); 186 wakeupEvent("rmnet0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now); 187 wakeupEvent("wlan0", 1010, v4, udp, mac, srcIp, dstIp, sport, dport, now); 188 189 String got = flushStatistics(); 190 String want = String.join("\n", 191 "dropped_events: 0", 192 "events <", 193 " if_name: \"\"", 194 " link_layer: 2", 195 " network_id: 0", 196 " time_ms: 0", 197 " transports: 0", 198 " wakeup_stats <", 199 " application_wakeups: 3", 200 " duration_sec: 0", 201 " ethertype_counts <", 202 " key: 2048", 203 " value: 4", 204 " >", 205 " ethertype_counts <", 206 " key: 34525", 207 " value: 1", 208 " >", 209 " ip_next_header_counts <", 210 " key: 6", 211 " value: 5", 212 " >", 213 " l2_broadcast_count: 0", 214 " l2_multicast_count: 0", 215 " l2_unicast_count: 5", 216 " no_uid_wakeups: 0", 217 " non_application_wakeups: 0", 218 " root_wakeups: 0", 219 " system_wakeups: 2", 220 " total_wakeups: 5", 221 " >", 222 ">", 223 "events <", 224 " if_name: \"\"", 225 " link_layer: 4", 226 " network_id: 0", 227 " time_ms: 0", 228 " transports: 0", 229 " wakeup_stats <", 230 " application_wakeups: 2", 231 " duration_sec: 0", 232 " ethertype_counts <", 233 " key: 2048", 234 " value: 5", 235 " >", 236 " ethertype_counts <", 237 " key: 34525", 238 " value: 5", 239 " >", 240 " ip_next_header_counts <", 241 " key: 6", 242 " value: 3", 243 " >", 244 " ip_next_header_counts <", 245 " key: 17", 246 " value: 5", 247 " >", 248 " ip_next_header_counts <", 249 " key: 58", 250 " value: 2", 251 " >", 252 " l2_broadcast_count: 0", 253 " l2_multicast_count: 0", 254 " l2_unicast_count: 10", 255 " no_uid_wakeups: 2", 256 " non_application_wakeups: 1", 257 " root_wakeups: 2", 258 " system_wakeups: 3", 259 " total_wakeups: 10", 260 " >", 261 ">", 262 "version: 2\n"); 263 assertEquals(want, got); 264 } 265 266 @Test testDnsLogging()267 public void testDnsLogging() throws Exception { 268 asyncDump(100); 269 270 dnsEvent(100, EVENT_GETADDRINFO, 0, 3456); 271 dnsEvent(100, EVENT_GETADDRINFO, 0, 267); 272 dnsEvent(100, EVENT_GETHOSTBYNAME, 22, 1230); 273 dnsEvent(100, EVENT_GETADDRINFO, 3, 45); 274 dnsEvent(100, EVENT_GETADDRINFO, 1, 2111); 275 dnsEvent(100, EVENT_GETADDRINFO, 0, 450); 276 dnsEvent(100, EVENT_GETHOSTBYNAME, 200, 638); 277 dnsEvent(100, EVENT_GETHOSTBYNAME, 178, 1300); 278 dnsEvent(101, EVENT_GETADDRINFO, 0, 56); 279 dnsEvent(101, EVENT_GETADDRINFO, 0, 78); 280 dnsEvent(101, EVENT_GETADDRINFO, 0, 14); 281 dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 56); 282 dnsEvent(101, EVENT_GETADDRINFO, 0, 78); 283 dnsEvent(101, EVENT_GETADDRINFO, 0, 14); 284 285 String got = flushStatistics(); 286 String want = String.join("\n", 287 "dropped_events: 0", 288 "events <", 289 " if_name: \"\"", 290 " link_layer: 4", 291 " network_id: 100", 292 " time_ms: 0", 293 " transports: 2", 294 " dns_lookup_batch <", 295 " event_types: 1", 296 " event_types: 1", 297 " event_types: 2", 298 " event_types: 1", 299 " event_types: 1", 300 " event_types: 1", 301 " event_types: 2", 302 " event_types: 2", 303 " getaddrinfo_error_count: 0", 304 " getaddrinfo_query_count: 0", 305 " gethostbyname_error_count: 0", 306 " gethostbyname_query_count: 0", 307 " latencies_ms: 3456", 308 " latencies_ms: 267", 309 " latencies_ms: 1230", 310 " latencies_ms: 45", 311 " latencies_ms: 2111", 312 " latencies_ms: 450", 313 " latencies_ms: 638", 314 " latencies_ms: 1300", 315 " return_codes: 0", 316 " return_codes: 0", 317 " return_codes: 22", 318 " return_codes: 3", 319 " return_codes: 1", 320 " return_codes: 0", 321 " return_codes: 200", 322 " return_codes: 178", 323 " >", 324 ">", 325 "events <", 326 " if_name: \"\"", 327 " link_layer: 2", 328 " network_id: 101", 329 " time_ms: 0", 330 " transports: 1", 331 " dns_lookup_batch <", 332 " event_types: 1", 333 " event_types: 1", 334 " event_types: 1", 335 " event_types: 2", 336 " event_types: 1", 337 " event_types: 1", 338 " getaddrinfo_error_count: 0", 339 " getaddrinfo_query_count: 0", 340 " gethostbyname_error_count: 0", 341 " gethostbyname_query_count: 0", 342 " latencies_ms: 56", 343 " latencies_ms: 78", 344 " latencies_ms: 14", 345 " latencies_ms: 56", 346 " latencies_ms: 78", 347 " latencies_ms: 14", 348 " return_codes: 0", 349 " return_codes: 0", 350 " return_codes: 0", 351 " return_codes: 0", 352 " return_codes: 0", 353 " return_codes: 0", 354 " >", 355 ">", 356 "version: 2\n"); 357 assertEquals(want, got); 358 } 359 360 @Test testConnectLogging()361 public void testConnectLogging() throws Exception { 362 asyncDump(100); 363 364 final int OK = 0; 365 Thread[] logActions = { 366 // ignored 367 connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4), 368 connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV6), 369 connectEventAction(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4), 370 connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), 371 connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), 372 // valid latencies 373 connectEventAction(100, OK, 110, EXAMPLE_IPV4), 374 connectEventAction(100, OK, 23, EXAMPLE_IPV4), 375 connectEventAction(100, OK, 45, EXAMPLE_IPV4), 376 connectEventAction(101, OK, 56, EXAMPLE_IPV4), 377 connectEventAction(101, OK, 523, EXAMPLE_IPV6), 378 connectEventAction(101, OK, 214, EXAMPLE_IPV6), 379 connectEventAction(101, OK, 67, EXAMPLE_IPV6), 380 // errors 381 connectEventAction(100, OsConstants.EPERM, 0, EXAMPLE_IPV4), 382 connectEventAction(101, OsConstants.EPERM, 0, EXAMPLE_IPV4), 383 connectEventAction(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4), 384 connectEventAction(100, OsConstants.EACCES, 0, EXAMPLE_IPV4), 385 connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV4), 386 connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV6), 387 connectEventAction(100, OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4), 388 connectEventAction(101, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4), 389 connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), 390 connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), 391 connectEventAction(101, OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4), 392 }; 393 394 for (Thread t : logActions) { 395 t.start(); 396 } 397 for (Thread t : logActions) { 398 t.join(); 399 } 400 401 String got = flushStatistics(); 402 String want = String.join("\n", 403 "dropped_events: 0", 404 "events <", 405 " if_name: \"\"", 406 " link_layer: 4", 407 " network_id: 100", 408 " time_ms: 0", 409 " transports: 2", 410 " connect_statistics <", 411 " connect_blocking_count: 3", 412 " connect_count: 6", 413 " errnos_counters <", 414 " key: 1", 415 " value: 1", 416 " >", 417 " errnos_counters <", 418 " key: 11", 419 " value: 1", 420 " >", 421 " errnos_counters <", 422 " key: 13", 423 " value: 1", 424 " >", 425 " errnos_counters <", 426 " key: 98", 427 " value: 1", 428 " >", 429 " errnos_counters <", 430 " key: 110", 431 " value: 2", 432 " >", 433 " ipv6_addr_count: 1", 434 " latencies_ms: 23", 435 " latencies_ms: 45", 436 " latencies_ms: 110", 437 " >", 438 ">", 439 "events <", 440 " if_name: \"\"", 441 " link_layer: 2", 442 " network_id: 101", 443 " time_ms: 0", 444 " transports: 1", 445 " connect_statistics <", 446 " connect_blocking_count: 4", 447 " connect_count: 6", 448 " errnos_counters <", 449 " key: 1", 450 " value: 1", 451 " >", 452 " errnos_counters <", 453 " key: 13", 454 " value: 2", 455 " >", 456 " errnos_counters <", 457 " key: 110", 458 " value: 1", 459 " >", 460 " errnos_counters <", 461 " key: 111", 462 " value: 1", 463 " >", 464 " ipv6_addr_count: 5", 465 " latencies_ms: 56", 466 " latencies_ms: 67", 467 " latencies_ms: 214", 468 " latencies_ms: 523", 469 " >", 470 ">", 471 "version: 2\n"); 472 assertEquals(want, got); 473 } 474 setCapabilities(int netId)475 private void setCapabilities(int netId) { 476 final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback = 477 ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); 478 verify(mCm).registerNetworkCallback(any(), networkCallback.capture()); 479 networkCallback.getValue().onCapabilitiesChanged(new Network(netId), 480 netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL); 481 } 482 connectEventAction(int netId, int error, int latencyMs, String ipAddr)483 Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) { 484 setCapabilities(netId); 485 return new Thread(() -> { 486 try { 487 mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1); 488 } catch (Exception e) { 489 fail(e.toString()); 490 } 491 }); 492 } 493 494 void dnsEvent(int netId, int type, int result, int latency) throws Exception { 495 setCapabilities(netId); 496 mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0); 497 } 498 499 void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp, 500 String dstIp, int sport, int dport, long now) throws Exception { 501 String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface; 502 mService.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now); 503 } 504 505 void asyncDump(long durationMs) throws Exception { 506 final long stop = System.currentTimeMillis() + durationMs; 507 final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); 508 new Thread(() -> { 509 while (System.currentTimeMillis() < stop) { 510 mService.list(pw); 511 } 512 }).start(); 513 } 514 515 // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. 516 String flushStatistics() throws Exception { 517 IpConnectivityMetrics metricsService = 518 new IpConnectivityMetrics(mock(Context.class), (ctx) -> 2000); 519 metricsService.mNetdListener = mService; 520 521 StringWriter buffer = new StringWriter(); 522 PrintWriter writer = new PrintWriter(buffer); 523 metricsService.impl.dump(null, writer, new String[]{"flush"}); 524 byte[] bytes = Base64.decode(buffer.toString(), Base64.DEFAULT); 525 IpConnectivityLog log = IpConnectivityLog.parseFrom(bytes); 526 for (IpConnectivityEvent ev : log.events) { 527 if (ev.getConnectStatistics() == null) { 528 continue; 529 } 530 // Sort repeated fields of connect() events arriving in non-deterministic order. 531 Arrays.sort(ev.getConnectStatistics().latenciesMs); 532 Arrays.sort(ev.getConnectStatistics().errnosCounters, 533 Comparator.comparingInt((p) -> p.key)); 534 } 535 return log.toString(); 536 } 537 538 String[] listNetdEvent() throws Exception { 539 StringWriter buffer = new StringWriter(); 540 PrintWriter writer = new PrintWriter(buffer); 541 mService.list(writer); 542 return buffer.toString().split("\\n"); 543 } 544 545 static <T> T[] remove(T[] array, T[] filtered) { 546 List<T> c = Arrays.asList(filtered); 547 int next = 0; 548 for (int i = 0; i < array.length; i++) { 549 if (c.contains(array[i])) { 550 continue; 551 } 552 array[next++] = array[i]; 553 } 554 return Arrays.copyOf(array, next); 555 } 556 } 557