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.mockito.Mockito.timeout;
22 import static org.mockito.Mockito.verify;
23 import static org.mockito.Mockito.when;
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28 
29 import android.content.Context;
30 import android.net.ConnectivityManager;
31 import android.net.ConnectivityMetricsEvent;
32 import android.net.IIpConnectivityMetrics;
33 import android.net.Network;
34 import android.net.NetworkCapabilities;
35 import android.net.metrics.ApfProgramEvent;
36 import android.net.metrics.ApfStats;
37 import android.net.metrics.DefaultNetworkEvent;
38 import android.net.metrics.DhcpClientEvent;
39 import android.net.metrics.IpConnectivityLog;
40 import android.net.metrics.IpManagerEvent;
41 import android.net.metrics.IpReachabilityEvent;
42 import android.net.metrics.RaEvent;
43 import android.net.metrics.ValidationProbeEvent;
44 import android.system.OsConstants;
45 import android.os.Parcelable;
46 import android.support.test.runner.AndroidJUnit4;
47 import android.test.suitebuilder.annotation.SmallTest;
48 import android.util.Base64;
49 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
50 import java.io.PrintWriter;
51 import java.io.StringWriter;
52 import java.util.Collections;
53 import java.util.Comparator;
54 import java.util.Iterator;
55 import java.util.List;
56 import org.mockito.ArgumentCaptor;
57 import org.mockito.Mock;
58 import org.mockito.MockitoAnnotations;
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 
63 @RunWith(AndroidJUnit4.class)
64 @SmallTest
65 public class IpConnectivityMetricsTest {
66     static final IpReachabilityEvent FAKE_EV =
67             new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
68 
69     private static final String EXAMPLE_IPV4 = "192.0.2.1";
70     private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
71 
72     @Mock Context mCtx;
73     @Mock IIpConnectivityMetrics mMockService;
74     @Mock ConnectivityManager mCm;
75 
76     IpConnectivityMetrics mService;
77     NetdEventListenerService mNetdListener;
78 
79     @Before
setUp()80     public void setUp() {
81         MockitoAnnotations.initMocks(this);
82         mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
83         mNetdListener = new NetdEventListenerService(mCm);
84         mService.mNetdListener = mNetdListener;
85     }
86 
87     @Test
testLoggingEvents()88     public void testLoggingEvents() throws Exception {
89         IpConnectivityLog logger = new IpConnectivityLog(mMockService);
90 
91         assertTrue(logger.log(1, FAKE_EV));
92         assertTrue(logger.log(2, FAKE_EV));
93         assertTrue(logger.log(3, FAKE_EV));
94 
95         List<ConnectivityMetricsEvent> got = verifyEvents(3);
96         assertEventsEqual(expectedEvent(1), got.get(0));
97         assertEventsEqual(expectedEvent(2), got.get(1));
98         assertEventsEqual(expectedEvent(3), got.get(2));
99     }
100 
101     @Test
testLoggingEventsWithMultipleCallers()102     public void testLoggingEventsWithMultipleCallers() throws Exception {
103         IpConnectivityLog logger = new IpConnectivityLog(mMockService);
104 
105         final int nCallers = 10;
106         final int nEvents = 10;
107         for (int n = 0; n < nCallers; n++) {
108             final int i = n;
109             new Thread() {
110                 public void run() {
111                     for (int j = 0; j < nEvents; j++) {
112                         assertTrue(logger.log(1 + i * 100 + j, FAKE_EV));
113                     }
114                 }
115             }.start();
116         }
117 
118         List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
119         Collections.sort(got, EVENT_COMPARATOR);
120         Iterator<ConnectivityMetricsEvent> iter = got.iterator();
121         for (int i = 0; i < nCallers; i++) {
122             for (int j = 0; j < nEvents; j++) {
123                 int expectedTimestamp = 1 + i * 100 + j;
124                 assertEventsEqual(expectedEvent(expectedTimestamp), iter.next());
125             }
126         }
127     }
128 
129     @Test
testBufferFlushing()130     public void testBufferFlushing() {
131         String output1 = getdump("flush");
132         assertEquals("", output1);
133 
134         new IpConnectivityLog(mService.impl).log(1, FAKE_EV);
135         String output2 = getdump("flush");
136         assertFalse("".equals(output2));
137 
138         String output3 = getdump("flush");
139         assertEquals("", output3);
140     }
141 
142     @Test
testRateLimiting()143     public void testRateLimiting() {
144         final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
145         final ApfProgramEvent ev = new ApfProgramEvent();
146         final long fakeTimestamp = 1;
147 
148         int attempt = 100; // More than burst quota, but less than buffer size.
149         for (int i = 0; i < attempt; i++) {
150             logger.log(ev);
151         }
152 
153         String output1 = getdump("flush");
154         assertFalse("".equals(output1));
155 
156         for (int i = 0; i < attempt; i++) {
157             assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
158         }
159 
160         String output2 = getdump("flush");
161         assertEquals("", output2);
162     }
163 
164     @Test
testEndToEndLogging()165     public void testEndToEndLogging() throws Exception {
166         // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
167         IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
168 
169         NetworkCapabilities ncWifi = new NetworkCapabilities();
170         NetworkCapabilities ncCell = new NetworkCapabilities();
171         ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
172         ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
173 
174         when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
175         when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
176 
177         ApfStats apfStats = new ApfStats();
178         apfStats.durationMs = 45000;
179         apfStats.receivedRas = 10;
180         apfStats.matchingRas = 2;
181         apfStats.droppedRas = 2;
182         apfStats.parseErrors = 2;
183         apfStats.zeroLifetimeRas = 1;
184         apfStats.programUpdates = 4;
185         apfStats.programUpdatesAll = 7;
186         apfStats.programUpdatesAllowingMulticast = 3;
187         apfStats.maxProgramSize = 2048;
188 
189         ValidationProbeEvent validationEv = new ValidationProbeEvent();
190         validationEv.durationMs = 40730;
191         validationEv.probeType = ValidationProbeEvent.PROBE_HTTP;
192         validationEv.returnCode = 204;
193 
194         Parcelable[] events = {
195             new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
196             new DhcpClientEvent("SomeState", 192),
197             new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
198             new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
199             validationEv,
200             apfStats,
201             new RaEvent(2000, 400, 300, -1, 1000, -1)
202         };
203 
204         for (int i = 0; i < events.length; i++) {
205             ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
206             ev.timestamp = 100 * (i + 1);
207             ev.ifname = "wlan0";
208             ev.data = events[i];
209             logger.log(ev);
210         }
211 
212         // netId, errno, latency, destination
213         connectEvent(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4);
214         connectEvent(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6);
215         connectEvent(100, 0, 110, EXAMPLE_IPV4);
216         connectEvent(101, 0, 23, EXAMPLE_IPV4);
217         connectEvent(101, 0, 45, EXAMPLE_IPV6);
218         connectEvent(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4);
219 
220         // netId, type, return code, latency
221         dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
222         dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
223         dnsEvent(100, EVENT_GETHOSTBYNAME, 0, 638);
224         dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
225         dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34);
226 
227         String want = String.join("\n",
228                 "dropped_events: 0",
229                 "events <",
230                 "  if_name: \"\"",
231                 "  link_layer: 4",
232                 "  network_id: 0",
233                 "  time_ms: 100",
234                 "  transports: 0",
235                 "  ip_reachability_event <",
236                 "    event_type: 512",
237                 "    if_name: \"\"",
238                 "  >",
239                 ">",
240                 "events <",
241                 "  if_name: \"\"",
242                 "  link_layer: 4",
243                 "  network_id: 0",
244                 "  time_ms: 200",
245                 "  transports: 0",
246                 "  dhcp_event <",
247                 "    duration_ms: 192",
248                 "    if_name: \"\"",
249                 "    state_transition: \"SomeState\"",
250                 "  >",
251                 ">",
252                 "events <",
253                 "  if_name: \"\"",
254                 "  link_layer: 4",
255                 "  network_id: 0",
256                 "  time_ms: 300",
257                 "  transports: 0",
258                 "  default_network_event <",
259                 "    network_id <",
260                 "      network_id: 102",
261                 "    >",
262                 "    previous_network_id <",
263                 "      network_id: 101",
264                 "    >",
265                 "    previous_network_ip_support: 1",
266                 "    transport_types: 1",
267                 "    transport_types: 2",
268                 "    transport_types: 3",
269                 "  >",
270                 ">",
271                 "events <",
272                 "  if_name: \"\"",
273                 "  link_layer: 4",
274                 "  network_id: 0",
275                 "  time_ms: 400",
276                 "  transports: 0",
277                 "  ip_provisioning_event <",
278                 "    event_type: 1",
279                 "    if_name: \"\"",
280                 "    latency_ms: 5678",
281                 "  >",
282                 ">",
283                 "events <",
284                 "  if_name: \"\"",
285                 "  link_layer: 4",
286                 "  network_id: 0",
287                 "  time_ms: 500",
288                 "  transports: 0",
289                 "  validation_probe_event <",
290                 "    latency_ms: 40730",
291                 "    probe_result: 204",
292                 "    probe_type: 1",
293                 "  >",
294                 ">",
295                 "events <",
296                 "  if_name: \"\"",
297                 "  link_layer: 4",
298                 "  network_id: 0",
299                 "  time_ms: 600",
300                 "  transports: 0",
301                 "  apf_statistics <",
302                 "    dropped_ras: 2",
303                 "    duration_ms: 45000",
304                 "    matching_ras: 2",
305                 "    max_program_size: 2048",
306                 "    parse_errors: 2",
307                 "    program_updates: 4",
308                 "    program_updates_all: 7",
309                 "    program_updates_allowing_multicast: 3",
310                 "    received_ras: 10",
311                 "    zero_lifetime_ras: 1",
312                 "  >",
313                 ">",
314                 "events <",
315                 "  if_name: \"\"",
316                 "  link_layer: 4",
317                 "  network_id: 0",
318                 "  time_ms: 700",
319                 "  transports: 0",
320                 "  ra_event <",
321                 "    dnssl_lifetime: -1",
322                 "    prefix_preferred_lifetime: 300",
323                 "    prefix_valid_lifetime: 400",
324                 "    rdnss_lifetime: 1000",
325                 "    route_info_lifetime: -1",
326                 "    router_lifetime: 2000",
327                 "  >",
328                 ">",
329                 "events <",
330                 "  if_name: \"\"",
331                 "  link_layer: 4",
332                 "  network_id: 100",
333                 "  time_ms: 0",
334                 "  transports: 2",
335                 "  connect_statistics <",
336                 "    connect_blocking_count: 1",
337                 "    connect_count: 3",
338                 "    errnos_counters <",
339                 "      key: 11",
340                 "      value: 1",
341                 "    >",
342                 "    ipv6_addr_count: 1",
343                 "    latencies_ms: 110",
344                 "  >",
345                 ">",
346                 "events <",
347                 "  if_name: \"\"",
348                 "  link_layer: 2",
349                 "  network_id: 101",
350                 "  time_ms: 0",
351                 "  transports: 1",
352                 "  connect_statistics <",
353                 "    connect_blocking_count: 2",
354                 "    connect_count: 2",
355                 "    ipv6_addr_count: 1",
356                 "    latencies_ms: 23",
357                 "    latencies_ms: 45",
358                 "  >",
359                 ">",
360                 "events <",
361                 "  if_name: \"\"",
362                 "  link_layer: 4",
363                 "  network_id: 100",
364                 "  time_ms: 0",
365                 "  transports: 2",
366                 "  dns_lookup_batch <",
367                 "    event_types: 1",
368                 "    event_types: 1",
369                 "    event_types: 2",
370                 "    latencies_ms: 3456",
371                 "    latencies_ms: 45",
372                 "    latencies_ms: 638",
373                 "    return_codes: 0",
374                 "    return_codes: 3",
375                 "    return_codes: 0",
376                 "  >",
377                 ">",
378                 "events <",
379                 "  if_name: \"\"",
380                 "  link_layer: 2",
381                 "  network_id: 101",
382                 "  time_ms: 0",
383                 "  transports: 1",
384                 "  dns_lookup_batch <",
385                 "    event_types: 1",
386                 "    event_types: 2",
387                 "    latencies_ms: 56",
388                 "    latencies_ms: 34",
389                 "    return_codes: 0",
390                 "    return_codes: 0",
391                 "  >",
392                 ">",
393                 "version: 2\n");
394 
395         verifySerialization(want, getdump("flush"));
396     }
397 
getdump(String .... command)398     String getdump(String ... command) {
399         StringWriter buffer = new StringWriter();
400         PrintWriter writer = new PrintWriter(buffer);
401         mService.impl.dump(null, writer, command);
402         return buffer.toString();
403     }
404 
connectEvent(int netid, int error, int latencyMs, String ipAddr)405     void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
406         mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
407     }
408 
dnsEvent(int netId, int type, int result, int latency)409     void dnsEvent(int netId, int type, int result, int latency) throws Exception {
410         mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
411     }
412 
verifyEvents(int n, int timeoutMs)413     List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
414         ArgumentCaptor<ConnectivityMetricsEvent> captor =
415                 ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
416         verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
417         return captor.getAllValues();
418     }
419 
verifyEvents(int n)420     List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
421         return verifyEvents(n, 10);
422     }
423 
verifySerialization(String want, String output)424     static void verifySerialization(String want, String output) {
425         try {
426             byte[] got = Base64.decode(output, Base64.DEFAULT);
427             IpConnectivityLogClass.IpConnectivityLog log =
428                     IpConnectivityLogClass.IpConnectivityLog.parseFrom(got);
429             assertEquals(want, log.toString());
430         } catch (Exception e) {
431             fail(e.toString());
432         }
433     }
434 
joinLines(String .... elems)435     static String joinLines(String ... elems) {
436         StringBuilder b = new StringBuilder();
437         for (String s : elems) {
438             b.append(s).append("\n");
439         }
440         return b.toString();
441     }
442 
expectedEvent(int timestamp)443     static ConnectivityMetricsEvent expectedEvent(int timestamp) {
444         ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
445         ev.timestamp = timestamp;
446         ev.data = FAKE_EV;
447         return ev;
448     }
449 
450     /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got)451     static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
452         assertEquals(expected.timestamp, got.timestamp);
453         assertEquals(expected.data, got.data);
454     }
455 
456     static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
457         Comparator.comparingLong((ev) -> ev.timestamp);
458 }
459