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 static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
20import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
21import static org.mockito.Mockito.timeout;
22import static org.mockito.Mockito.verify;
23import static org.mockito.Mockito.when;
24import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.assertFalse;
26import static org.junit.Assert.assertTrue;
27import static org.junit.Assert.fail;
28
29import android.content.Context;
30import android.net.ConnectivityManager;
31import android.net.ConnectivityMetricsEvent;
32import android.net.IIpConnectivityMetrics;
33import android.net.Network;
34import android.net.NetworkCapabilities;
35import android.net.metrics.ApfProgramEvent;
36import android.net.metrics.ApfStats;
37import android.net.metrics.DefaultNetworkEvent;
38import android.net.metrics.DhcpClientEvent;
39import android.net.metrics.IpConnectivityLog;
40import android.net.metrics.IpManagerEvent;
41import android.net.metrics.IpReachabilityEvent;
42import android.net.metrics.RaEvent;
43import android.net.metrics.ValidationProbeEvent;
44import android.system.OsConstants;
45import android.os.Parcelable;
46import android.support.test.runner.AndroidJUnit4;
47import android.test.suitebuilder.annotation.SmallTest;
48import android.util.Base64;
49import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
50import java.io.PrintWriter;
51import java.io.StringWriter;
52import java.util.Collections;
53import java.util.Comparator;
54import java.util.Iterator;
55import java.util.List;
56import org.mockito.ArgumentCaptor;
57import org.mockito.Mock;
58import org.mockito.MockitoAnnotations;
59import org.junit.Before;
60import org.junit.Test;
61import org.junit.runner.RunWith;
62
63@RunWith(AndroidJUnit4.class)
64@SmallTest
65public 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
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
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
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
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
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
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
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
405    void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
406        mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
407    }
408
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
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
420    List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
421        return verifyEvents(n, 10);
422    }
423
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
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
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(). */
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