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 17package com.android.server.connectivity; 18 19import android.net.ConnectivityManager; 20import android.net.ConnectivityManager.NetworkCallback; 21import android.net.Network; 22import android.net.metrics.DnsEvent; 23import android.net.metrics.INetdEventListener; 24import android.net.metrics.IpConnectivityLog; 25import android.os.RemoteException; 26import android.system.OsConstants; 27import android.test.suitebuilder.annotation.SmallTest; 28import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent; 29import java.io.FileOutputStream; 30import java.io.PrintWriter; 31import java.util.ArrayList; 32import java.util.Arrays; 33import java.util.Comparator; 34import java.util.List; 35import java.util.OptionalInt; 36import java.util.stream.IntStream; 37import junit.framework.TestCase; 38import org.junit.Before; 39import org.junit.Test; 40import org.mockito.ArgumentCaptor; 41import org.mockito.Mock; 42import org.mockito.Mockito; 43import org.mockito.MockitoAnnotations; 44 45import static org.junit.Assert.assertArrayEquals; 46import static org.junit.Assert.assertTrue; 47import static org.mockito.Mockito.any; 48import static org.mockito.Mockito.anyInt; 49import static org.mockito.Mockito.eq; 50import static org.mockito.Mockito.timeout; 51import static org.mockito.Mockito.times; 52import static org.mockito.Mockito.verify; 53 54public class NetdEventListenerServiceTest extends TestCase { 55 56 // TODO: read from NetdEventListenerService after this constant is read from system property 57 static final int BATCH_SIZE = 100; 58 static final int EVENT_TYPE = INetdEventListener.EVENT_GETADDRINFO; 59 // TODO: read from INetdEventListener 60 static final int RETURN_CODE = 1; 61 62 static final byte[] EVENT_TYPES = new byte[BATCH_SIZE]; 63 static final byte[] RETURN_CODES = new byte[BATCH_SIZE]; 64 static final int[] LATENCIES = new int[BATCH_SIZE]; 65 static { 66 for (int i = 0; i < BATCH_SIZE; i++) { 67 EVENT_TYPES[i] = EVENT_TYPE; 68 RETURN_CODES[i] = RETURN_CODE; 69 LATENCIES[i] = i; 70 } 71 } 72 73 private static final String EXAMPLE_IPV4 = "192.0.2.1"; 74 private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1"; 75 76 NetdEventListenerService mNetdEventListenerService; 77 78 @Mock ConnectivityManager mCm; 79 @Mock IpConnectivityLog mLog; 80 ArgumentCaptor<NetworkCallback> mCallbackCaptor; 81 ArgumentCaptor<DnsEvent> mDnsEvCaptor; 82 83 public void setUp() { 84 MockitoAnnotations.initMocks(this); 85 mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class); 86 mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); 87 mNetdEventListenerService = new NetdEventListenerService(mCm, mLog); 88 89 verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture()); 90 } 91 92 @SmallTest 93 public void testOneDnsBatch() throws Exception { 94 log(105, LATENCIES); 95 log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event 96 97 verifyLoggedDnsEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); 98 99 log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE)); 100 101 mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor 102 verifyLoggedDnsEvents( 103 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 104 new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES)); 105 } 106 107 @SmallTest 108 public void testSeveralDmsBatches() throws Exception { 109 log(105, LATENCIES); 110 log(106, LATENCIES); 111 log(105, LATENCIES); 112 log(107, LATENCIES); 113 114 verifyLoggedDnsEvents( 115 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 116 new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), 117 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 118 new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); 119 } 120 121 @SmallTest 122 public void testDnsBatchAndNetworkLost() throws Exception { 123 byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20); 124 byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20); 125 int[] latencies = Arrays.copyOf(LATENCIES, 20); 126 127 log(105, LATENCIES); 128 log(105, latencies); 129 mCallbackCaptor.getValue().onLost(new Network(105)); 130 log(105, LATENCIES); 131 132 verifyLoggedDnsEvents( 133 new DnsEvent(105, eventTypes, returnCodes, latencies), 134 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 135 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); 136 } 137 138 @SmallTest 139 public void testConcurrentDnsBatchesAndDumps() throws Exception { 140 final long stop = System.currentTimeMillis() + 100; 141 final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); 142 new Thread() { 143 public void run() { 144 while (System.currentTimeMillis() < stop) { 145 mNetdEventListenerService.dump(pw); 146 } 147 } 148 }.start(); 149 150 logDnsAsync(105, LATENCIES); 151 logDnsAsync(106, LATENCIES); 152 logDnsAsync(107, LATENCIES); 153 154 verifyLoggedDnsEvents(500, 155 new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), 156 new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), 157 new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); 158 } 159 160 @SmallTest 161 public void testConcurrentDnsBatchesAndNetworkLoss() throws Exception { 162 logDnsAsync(105, LATENCIES); 163 Thread.sleep(10L); 164 // call onLost() asynchronously to logDnsAsync's onDnsEvent() calls. 165 mCallbackCaptor.getValue().onLost(new Network(105)); 166 167 // do not verify unpredictable batch 168 verify(mLog, timeout(500).times(1)).log(any()); 169 } 170 171 @SmallTest 172 public void testConnectLogging() throws Exception { 173 final int OK = 0; 174 Thread[] logActions = { 175 // ignored 176 connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV4), 177 connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV6), 178 connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4), 179 connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), 180 connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), 181 // valid latencies 182 connectEventAction(OK, 110, EXAMPLE_IPV4), 183 connectEventAction(OK, 23, EXAMPLE_IPV4), 184 connectEventAction(OK, 45, EXAMPLE_IPV4), 185 connectEventAction(OK, 56, EXAMPLE_IPV4), 186 connectEventAction(OK, 523, EXAMPLE_IPV6), 187 connectEventAction(OK, 214, EXAMPLE_IPV6), 188 connectEventAction(OK, 67, EXAMPLE_IPV6), 189 // errors 190 connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4), 191 connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4), 192 connectEventAction(OsConstants.EAGAIN, 0, EXAMPLE_IPV4), 193 connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4), 194 connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4), 195 connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV6), 196 connectEventAction(OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4), 197 connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4), 198 connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), 199 connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), 200 connectEventAction(OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4), 201 }; 202 203 for (Thread t : logActions) { 204 t.start(); 205 } 206 for (Thread t : logActions) { 207 t.join(); 208 } 209 210 List<IpConnectivityEvent> events = new ArrayList<>(); 211 mNetdEventListenerService.flushStatistics(events); 212 213 IpConnectivityEvent got = events.get(0); 214 String want = joinLines( 215 "time_ms: 0", 216 "transport: 0", 217 "connect_statistics <", 218 " connect_count: 12", 219 " errnos_counters <", 220 " key: 1", 221 " value: 2", 222 " >", 223 " errnos_counters <", 224 " key: 11", 225 " value: 1", 226 " >", 227 " errnos_counters <", 228 " key: 13", 229 " value: 3", 230 " >", 231 " errnos_counters <", 232 " key: 98", 233 " value: 1", 234 " >", 235 " errnos_counters <", 236 " key: 110", 237 " value: 3", 238 " >", 239 " errnos_counters <", 240 " key: 111", 241 " value: 1", 242 " >", 243 " ipv6_addr_count: 6", 244 " latencies_ms: 23", 245 " latencies_ms: 45", 246 " latencies_ms: 56", 247 " latencies_ms: 67", 248 " latencies_ms: 110", 249 " latencies_ms: 214", 250 " latencies_ms: 523"); 251 verifyConnectEvent(want, got); 252 } 253 254 Thread connectEventAction(int error, int latencyMs, String ipAddr) { 255 return new Thread(() -> { 256 try { 257 mNetdEventListenerService.onConnectEvent(100, error, latencyMs, ipAddr, 80, 1); 258 } catch (Exception e) { 259 fail(e.toString()); 260 } 261 }); 262 } 263 264 void log(int netId, int[] latencies) { 265 try { 266 for (int l : latencies) { 267 mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null, 268 0, 0); 269 } 270 } catch (RemoteException re) { 271 throw re.rethrowFromSystemServer(); 272 } 273 } 274 275 void logDnsAsync(int netId, int[] latencies) { 276 new Thread() { 277 public void run() { 278 log(netId, latencies); 279 } 280 }.start(); 281 } 282 283 void verifyLoggedDnsEvents(DnsEvent... expected) { 284 verifyLoggedDnsEvents(0, expected); 285 } 286 287 void verifyLoggedDnsEvents(int wait, DnsEvent... expectedEvents) { 288 verify(mLog, timeout(wait).times(expectedEvents.length)).log(mDnsEvCaptor.capture()); 289 for (DnsEvent got : mDnsEvCaptor.getAllValues()) { 290 OptionalInt index = IntStream.range(0, expectedEvents.length) 291 .filter(i -> dnsEventsEqual(expectedEvents[i], got)) 292 .findFirst(); 293 // Don't match same expected event more than once. 294 index.ifPresent(i -> expectedEvents[i] = null); 295 assertTrue(index.isPresent()); 296 } 297 } 298 299 /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */ 300 static boolean dnsEventsEqual(DnsEvent expected, DnsEvent got) { 301 return (expected == got) || ((expected != null) && (got != null) 302 && (expected.netId == got.netId) 303 && Arrays.equals(expected.eventTypes, got.eventTypes) 304 && Arrays.equals(expected.returnCodes, got.returnCodes) 305 && Arrays.equals(expected.latenciesMs, got.latenciesMs)); 306 } 307 308 static String joinLines(String ... elems) { 309 StringBuilder b = new StringBuilder(); 310 for (String s : elems) { 311 b.append(s).append("\n"); 312 } 313 return b.toString(); 314 } 315 316 static void verifyConnectEvent(String expected, IpConnectivityEvent got) { 317 try { 318 Arrays.sort(got.connectStatistics.latenciesMs); 319 Arrays.sort(got.connectStatistics.errnosCounters, 320 Comparator.comparingInt((p) -> p.key)); 321 assertEquals(expected, got.toString()); 322 } catch (Exception e) { 323 fail(e.toString()); 324 } 325 } 326} 327