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