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 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 import static org.mockito.Mockito.any; 25 import static org.mockito.Mockito.mock; 26 import static org.mockito.Mockito.anyInt; 27 import static org.mockito.Mockito.eq; 28 import static org.mockito.Mockito.timeout; 29 import static org.mockito.Mockito.times; 30 import static org.mockito.Mockito.verify; 31 import static org.mockito.Mockito.when; 32 33 import android.content.Context; 34 import android.net.ConnectivityManager; 35 import android.net.Network; 36 import android.net.NetworkCapabilities; 37 import android.support.test.runner.AndroidJUnit4; 38 import android.system.OsConstants; 39 import android.test.suitebuilder.annotation.SmallTest; 40 import android.util.Base64; 41 42 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.DNSLookupBatch; 43 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; 44 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog; 45 46 import java.io.FileOutputStream; 47 import java.io.PrintWriter; 48 import java.io.StringWriter; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Comparator; 52 import java.util.List; 53 54 import org.junit.Before; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 58 @RunWith(AndroidJUnit4.class) 59 @SmallTest 60 public class NetdEventListenerServiceTest { 61 private static final String EXAMPLE_IPV4 = "192.0.2.1"; 62 private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1"; 63 64 NetdEventListenerService mNetdEventListenerService; 65 ConnectivityManager mCm; 66 67 @Before 68 public void setUp() { 69 NetworkCapabilities ncWifi = new NetworkCapabilities(); 70 NetworkCapabilities ncCell = new NetworkCapabilities(); 71 ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); 72 ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); 73 74 mCm = mock(ConnectivityManager.class); 75 when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi); 76 when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell); 77 78 mNetdEventListenerService = new NetdEventListenerService(mCm); 79 } 80 81 @Test 82 public void testWakeupEventLogging() throws Exception { 83 final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH; 84 85 // Assert no events 86 String[] events1 = listNetdEvent(); 87 assertEquals(new String[]{""}, events1); 88 89 long now = System.currentTimeMillis(); 90 String prefix = "iface:wlan0"; 91 int[] uids = { 10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004 }; 92 for (int uid : uids) { 93 mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now); 94 } 95 96 String[] events2 = listNetdEvent(); 97 int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line 98 assertEquals(expectedLength2, events2.length); 99 assertContains(events2[0], "WakeupStats"); 100 assertContains(events2[0], "wlan0"); 101 for (int i = 0; i < uids.length; i++) { 102 String got = events2[i+1]; 103 assertContains(got, "WakeupEvent"); 104 assertContains(got, "wlan0"); 105 assertContains(got, "uid: " + uids[i]); 106 } 107 108 int uid = 20000; 109 for (int i = 0; i < BUFFER_LENGTH * 2; i++) { 110 long ts = now + 10; 111 mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts); 112 } 113 114 String[] events3 = listNetdEvent(); 115 int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line 116 assertEquals(expectedLength3, events3.length); 117 assertContains(events2[0], "WakeupStats"); 118 assertContains(events2[0], "wlan0"); 119 for (int i = 1; i < expectedLength3; i++) { 120 String got = events3[i]; 121 assertContains(got, "WakeupEvent"); 122 assertContains(got, "wlan0"); 123 assertContains(got, "uid: " + uid); 124 } 125 126 uid = 45678; 127 mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now); 128 129 String[] events4 = listNetdEvent(); 130 String lastEvent = events4[events4.length - 1]; 131 assertContains(lastEvent, "WakeupEvent"); 132 assertContains(lastEvent, "wlan0"); 133 assertContains(lastEvent, "uid: " + uid); 134 } 135 136 @Test 137 public void testWakeupStatsLogging() throws Exception { 138 wakeupEvent("wlan0", 1000); 139 wakeupEvent("rmnet0", 10123); 140 wakeupEvent("wlan0", 1000); 141 wakeupEvent("rmnet0", 10008); 142 wakeupEvent("wlan0", -1); 143 wakeupEvent("wlan0", 10008); 144 wakeupEvent("rmnet0", 1000); 145 wakeupEvent("wlan0", 10004); 146 wakeupEvent("wlan0", 1000); 147 wakeupEvent("wlan0", 0); 148 wakeupEvent("wlan0", -1); 149 wakeupEvent("rmnet0", 10052); 150 wakeupEvent("wlan0", 0); 151 wakeupEvent("rmnet0", 1000); 152 wakeupEvent("wlan0", 1010); 153 154 String got = flushStatistics(); 155 String want = String.join("\n", 156 "dropped_events: 0", 157 "events <", 158 " if_name: \"\"", 159 " link_layer: 2", 160 " network_id: 0", 161 " time_ms: 0", 162 " transports: 0", 163 " wakeup_stats <", 164 " application_wakeups: 3", 165 " duration_sec: 0", 166 " no_uid_wakeups: 0", 167 " non_application_wakeups: 0", 168 " root_wakeups: 0", 169 " system_wakeups: 2", 170 " total_wakeups: 5", 171 " >", 172 ">", 173 "events <", 174 " if_name: \"\"", 175 " link_layer: 4", 176 " network_id: 0", 177 " time_ms: 0", 178 " transports: 0", 179 " wakeup_stats <", 180 " application_wakeups: 2", 181 " duration_sec: 0", 182 " no_uid_wakeups: 2", 183 " non_application_wakeups: 1", 184 " root_wakeups: 2", 185 " system_wakeups: 3", 186 " total_wakeups: 10", 187 " >", 188 ">", 189 "version: 2\n"); 190 assertEquals(want, got); 191 } 192 193 @Test 194 public void testDnsLogging() throws Exception { 195 asyncDump(100); 196 197 dnsEvent(100, EVENT_GETADDRINFO, 0, 3456); 198 dnsEvent(100, EVENT_GETADDRINFO, 0, 267); 199 dnsEvent(100, EVENT_GETHOSTBYNAME, 22, 1230); 200 dnsEvent(100, EVENT_GETADDRINFO, 3, 45); 201 dnsEvent(100, EVENT_GETADDRINFO, 1, 2111); 202 dnsEvent(100, EVENT_GETADDRINFO, 0, 450); 203 dnsEvent(100, EVENT_GETHOSTBYNAME, 200, 638); 204 dnsEvent(100, EVENT_GETHOSTBYNAME, 178, 1300); 205 dnsEvent(101, EVENT_GETADDRINFO, 0, 56); 206 dnsEvent(101, EVENT_GETADDRINFO, 0, 78); 207 dnsEvent(101, EVENT_GETADDRINFO, 0, 14); 208 dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 56); 209 dnsEvent(101, EVENT_GETADDRINFO, 0, 78); 210 dnsEvent(101, EVENT_GETADDRINFO, 0, 14); 211 212 String got = flushStatistics(); 213 String want = String.join("\n", 214 "dropped_events: 0", 215 "events <", 216 " if_name: \"\"", 217 " link_layer: 4", 218 " network_id: 100", 219 " time_ms: 0", 220 " transports: 2", 221 " dns_lookup_batch <", 222 " event_types: 1", 223 " event_types: 1", 224 " event_types: 2", 225 " event_types: 1", 226 " event_types: 1", 227 " event_types: 1", 228 " event_types: 2", 229 " event_types: 2", 230 " getaddrinfo_error_count: 0", 231 " getaddrinfo_query_count: 0", 232 " gethostbyname_error_count: 0", 233 " gethostbyname_query_count: 0", 234 " latencies_ms: 3456", 235 " latencies_ms: 267", 236 " latencies_ms: 1230", 237 " latencies_ms: 45", 238 " latencies_ms: 2111", 239 " latencies_ms: 450", 240 " latencies_ms: 638", 241 " latencies_ms: 1300", 242 " return_codes: 0", 243 " return_codes: 0", 244 " return_codes: 22", 245 " return_codes: 3", 246 " return_codes: 1", 247 " return_codes: 0", 248 " return_codes: 200", 249 " return_codes: 178", 250 " >", 251 ">", 252 "events <", 253 " if_name: \"\"", 254 " link_layer: 2", 255 " network_id: 101", 256 " time_ms: 0", 257 " transports: 1", 258 " dns_lookup_batch <", 259 " event_types: 1", 260 " event_types: 1", 261 " event_types: 1", 262 " event_types: 2", 263 " event_types: 1", 264 " event_types: 1", 265 " getaddrinfo_error_count: 0", 266 " getaddrinfo_query_count: 0", 267 " gethostbyname_error_count: 0", 268 " gethostbyname_query_count: 0", 269 " latencies_ms: 56", 270 " latencies_ms: 78", 271 " latencies_ms: 14", 272 " latencies_ms: 56", 273 " latencies_ms: 78", 274 " latencies_ms: 14", 275 " return_codes: 0", 276 " return_codes: 0", 277 " return_codes: 0", 278 " return_codes: 0", 279 " return_codes: 0", 280 " return_codes: 0", 281 " >", 282 ">", 283 "version: 2\n"); 284 assertEquals(want, got); 285 } 286 287 @Test 288 public void testConnectLogging() throws Exception { 289 asyncDump(100); 290 291 final int OK = 0; 292 Thread[] logActions = { 293 // ignored 294 connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4), 295 connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV6), 296 connectEventAction(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4), 297 connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), 298 connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), 299 // valid latencies 300 connectEventAction(100, OK, 110, EXAMPLE_IPV4), 301 connectEventAction(100, OK, 23, EXAMPLE_IPV4), 302 connectEventAction(100, OK, 45, EXAMPLE_IPV4), 303 connectEventAction(101, OK, 56, EXAMPLE_IPV4), 304 connectEventAction(101, OK, 523, EXAMPLE_IPV6), 305 connectEventAction(101, OK, 214, EXAMPLE_IPV6), 306 connectEventAction(101, OK, 67, EXAMPLE_IPV6), 307 // errors 308 connectEventAction(100, OsConstants.EPERM, 0, EXAMPLE_IPV4), 309 connectEventAction(101, OsConstants.EPERM, 0, EXAMPLE_IPV4), 310 connectEventAction(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4), 311 connectEventAction(100, OsConstants.EACCES, 0, EXAMPLE_IPV4), 312 connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV4), 313 connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV6), 314 connectEventAction(100, OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4), 315 connectEventAction(101, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4), 316 connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), 317 connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), 318 connectEventAction(101, OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4), 319 }; 320 321 for (Thread t : logActions) { 322 t.start(); 323 } 324 for (Thread t : logActions) { 325 t.join(); 326 } 327 328 String got = flushStatistics(); 329 String want = String.join("\n", 330 "dropped_events: 0", 331 "events <", 332 " if_name: \"\"", 333 " link_layer: 4", 334 " network_id: 100", 335 " time_ms: 0", 336 " transports: 2", 337 " connect_statistics <", 338 " connect_blocking_count: 3", 339 " connect_count: 6", 340 " errnos_counters <", 341 " key: 1", 342 " value: 1", 343 " >", 344 " errnos_counters <", 345 " key: 11", 346 " value: 1", 347 " >", 348 " errnos_counters <", 349 " key: 13", 350 " value: 1", 351 " >", 352 " errnos_counters <", 353 " key: 98", 354 " value: 1", 355 " >", 356 " errnos_counters <", 357 " key: 110", 358 " value: 2", 359 " >", 360 " ipv6_addr_count: 1", 361 " latencies_ms: 23", 362 " latencies_ms: 45", 363 " latencies_ms: 110", 364 " >", 365 ">", 366 "events <", 367 " if_name: \"\"", 368 " link_layer: 2", 369 " network_id: 101", 370 " time_ms: 0", 371 " transports: 1", 372 " connect_statistics <", 373 " connect_blocking_count: 4", 374 " connect_count: 6", 375 " errnos_counters <", 376 " key: 1", 377 " value: 1", 378 " >", 379 " errnos_counters <", 380 " key: 13", 381 " value: 2", 382 " >", 383 " errnos_counters <", 384 " key: 110", 385 " value: 1", 386 " >", 387 " errnos_counters <", 388 " key: 111", 389 " value: 1", 390 " >", 391 " ipv6_addr_count: 5", 392 " latencies_ms: 56", 393 " latencies_ms: 67", 394 " latencies_ms: 214", 395 " latencies_ms: 523", 396 " >", 397 ">", 398 "version: 2\n"); 399 assertEquals(want, got); 400 } 401 402 Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) { 403 return new Thread(() -> { 404 try { 405 mNetdEventListenerService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1); 406 } catch (Exception e) { 407 fail(e.toString()); 408 } 409 }); 410 } 411 412 void dnsEvent(int netId, int type, int result, int latency) throws Exception { 413 mNetdEventListenerService.onDnsEvent(netId, type, result, latency, "", null, 0, 0); 414 } 415 416 void wakeupEvent(String iface, int uid) throws Exception { 417 String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface; 418 mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, 0); 419 } 420 421 void asyncDump(long durationMs) throws Exception { 422 final long stop = System.currentTimeMillis() + durationMs; 423 final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); 424 new Thread(() -> { 425 while (System.currentTimeMillis() < stop) { 426 mNetdEventListenerService.dump(pw); 427 } 428 }).start(); 429 } 430 431 // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. 432 String flushStatistics() throws Exception { 433 IpConnectivityMetrics metricsService = 434 new IpConnectivityMetrics(mock(Context.class), (ctx) -> 2000); 435 metricsService.mNetdListener = mNetdEventListenerService; 436 437 StringWriter buffer = new StringWriter(); 438 PrintWriter writer = new PrintWriter(buffer); 439 metricsService.impl.dump(null, writer, new String[]{"flush"}); 440 byte[] bytes = Base64.decode(buffer.toString(), Base64.DEFAULT); 441 IpConnectivityLog log = IpConnectivityLog.parseFrom(bytes); 442 for (IpConnectivityEvent ev : log.events) { 443 if (ev.getConnectStatistics() == null) { 444 continue; 445 } 446 // Sort repeated fields of connect() events arriving in non-deterministic order. 447 Arrays.sort(ev.getConnectStatistics().latenciesMs); 448 Arrays.sort(ev.getConnectStatistics().errnosCounters, 449 Comparator.comparingInt((p) -> p.key)); 450 } 451 return log.toString(); 452 } 453 454 String[] listNetdEvent() throws Exception { 455 StringWriter buffer = new StringWriter(); 456 PrintWriter writer = new PrintWriter(buffer); 457 mNetdEventListenerService.list(writer); 458 return buffer.toString().split("\\n"); 459 } 460 461 static void assertContains(String got, String want) { 462 assertTrue(got + " did not contain \"" + want + "\"", got.contains(want)); 463 } 464 } 465