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.util.TimeUtils.NANOS_PER_MS;
20 
21 import android.content.Context;
22 import android.net.ConnectivityManager;
23 import android.net.INetdEventCallback;
24 import android.net.MacAddress;
25 import android.net.Network;
26 import android.net.NetworkCapabilities;
27 import android.net.metrics.ConnectStats;
28 import android.net.metrics.DnsEvent;
29 import android.net.metrics.INetdEventListener;
30 import android.net.metrics.NetworkMetrics;
31 import android.net.metrics.WakeupEvent;
32 import android.net.metrics.WakeupStats;
33 import android.os.RemoteException;
34 import android.text.format.DateUtils;
35 import android.util.ArrayMap;
36 import android.util.Log;
37 import android.util.SparseArray;
38 import android.util.StatsLog;
39 
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.util.BitUtils;
43 import com.android.internal.util.RingBuffer;
44 import com.android.internal.util.TokenBucket;
45 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
46 
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.StringJoiner;
51 
52 /**
53  * Implementation of the INetdEventListener interface.
54  */
55 public class NetdEventListenerService extends INetdEventListener.Stub {
56 
57     public static final String SERVICE_NAME = "netd_listener";
58 
59     private static final String TAG = NetdEventListenerService.class.getSimpleName();
60     private static final boolean DBG = false;
61 
62     // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
63     // bursts of 5000 measurements.
64     private static final int CONNECT_LATENCY_BURST_LIMIT  = 5000;
65     private static final int CONNECT_LATENCY_FILL_RATE    = 15 * (int) DateUtils.SECOND_IN_MILLIS;
66 
67     private static final long METRICS_SNAPSHOT_SPAN_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
68     private static final int METRICS_SNAPSHOT_BUFFER_SIZE = 48; // 4 hours
69 
70     @VisibleForTesting
71     static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024;
72     // TODO: dedup this String constant with the one used in
73     // ConnectivityService#wakeupModifyInterface().
74     @VisibleForTesting
75     static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:";
76 
77     // Array of aggregated DNS and connect events sent by netd, grouped by net id.
78     @GuardedBy("this")
79     private final SparseArray<NetworkMetrics> mNetworkMetrics = new SparseArray<>();
80 
81     @GuardedBy("this")
82     private final RingBuffer<NetworkMetricsSnapshot> mNetworkMetricsSnapshots =
83             new RingBuffer<>(NetworkMetricsSnapshot.class, METRICS_SNAPSHOT_BUFFER_SIZE);
84     @GuardedBy("this")
85     private long mLastSnapshot = 0;
86 
87     // Array of aggregated wakeup event stats, grouped by interface name.
88     @GuardedBy("this")
89     private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>();
90     // Ring buffer array for storing packet wake up events sent by Netd.
91     @GuardedBy("this")
92     private final RingBuffer<WakeupEvent> mWakeupEvents =
93             new RingBuffer<>(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
94 
95     private final ConnectivityManager mCm;
96 
97     @GuardedBy("this")
98     private final TokenBucket mConnectTb =
99             new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
100 
101 
102     /**
103      * There are only 3 possible callbacks.
104      *
105      * mNetdEventCallbackList[CALLBACK_CALLER_CONNECTIVITY_SERVICE]
106      * Callback registered/unregistered by ConnectivityService.
107      *
108      * mNetdEventCallbackList[CALLBACK_CALLER_DEVICE_POLICY]
109      * Callback registered/unregistered when logging is being enabled/disabled in DPM
110      * by the device owner. It's DevicePolicyManager's responsibility to ensure that.
111      *
112      * mNetdEventCallbackList[CALLBACK_CALLER_NETWORK_WATCHLIST]
113      * Callback registered/unregistered by NetworkWatchlistService.
114      */
115     @GuardedBy("this")
116     private static final int[] ALLOWED_CALLBACK_TYPES = {
117         INetdEventCallback.CALLBACK_CALLER_CONNECTIVITY_SERVICE,
118         INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY,
119         INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST
120     };
121 
122     @GuardedBy("this")
123     private INetdEventCallback[] mNetdEventCallbackList =
124             new INetdEventCallback[ALLOWED_CALLBACK_TYPES.length];
125 
addNetdEventCallback(int callerType, INetdEventCallback callback)126     public synchronized boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
127         if (!isValidCallerType(callerType)) {
128             Log.e(TAG, "Invalid caller type: " + callerType);
129             return false;
130         }
131         mNetdEventCallbackList[callerType] = callback;
132         return true;
133     }
134 
removeNetdEventCallback(int callerType)135     public synchronized boolean removeNetdEventCallback(int callerType) {
136         if (!isValidCallerType(callerType)) {
137             Log.e(TAG, "Invalid caller type: " + callerType);
138             return false;
139         }
140         mNetdEventCallbackList[callerType] = null;
141         return true;
142     }
143 
isValidCallerType(int callerType)144     private static boolean isValidCallerType(int callerType) {
145         for (int i = 0; i < ALLOWED_CALLBACK_TYPES.length; i++) {
146             if (callerType == ALLOWED_CALLBACK_TYPES[i]) {
147                 return true;
148             }
149         }
150         return false;
151     }
152 
NetdEventListenerService(Context context)153     public NetdEventListenerService(Context context) {
154         this(context.getSystemService(ConnectivityManager.class));
155     }
156 
157     @VisibleForTesting
NetdEventListenerService(ConnectivityManager cm)158     public NetdEventListenerService(ConnectivityManager cm) {
159         // We are started when boot is complete, so ConnectivityService should already be running.
160         mCm = cm;
161     }
162 
projectSnapshotTime(long timeMs)163     private static long projectSnapshotTime(long timeMs) {
164         return (timeMs / METRICS_SNAPSHOT_SPAN_MS) * METRICS_SNAPSHOT_SPAN_MS;
165     }
166 
getMetricsForNetwork(long timeMs, int netId)167     private NetworkMetrics getMetricsForNetwork(long timeMs, int netId) {
168         collectPendingMetricsSnapshot(timeMs);
169         NetworkMetrics metrics = mNetworkMetrics.get(netId);
170         if (metrics == null) {
171             // TODO: allow to change transport for a given netid.
172             metrics = new NetworkMetrics(netId, getTransports(netId), mConnectTb);
173             mNetworkMetrics.put(netId, metrics);
174         }
175         return metrics;
176     }
177 
getNetworkMetricsSnapshots()178     private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() {
179         collectPendingMetricsSnapshot(System.currentTimeMillis());
180         return mNetworkMetricsSnapshots.toArray();
181     }
182 
collectPendingMetricsSnapshot(long timeMs)183     private void collectPendingMetricsSnapshot(long timeMs) {
184         // Detects time differences larger than the snapshot collection period.
185         // This is robust against clock jumps and long inactivity periods.
186         if (Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
187             return;
188         }
189         mLastSnapshot = projectSnapshotTime(timeMs);
190         NetworkMetricsSnapshot snapshot =
191                 NetworkMetricsSnapshot.collect(mLastSnapshot, mNetworkMetrics);
192         if (snapshot.stats.isEmpty()) {
193             return;
194         }
195         mNetworkMetricsSnapshots.append(snapshot);
196     }
197 
198     @Override
199     // Called concurrently by multiple binder threads.
200     // This method must not block or perform long-running operations.
onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, String hostname, String[] ipAddresses, int ipAddressesCount, int uid)201     public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
202             String hostname, String[] ipAddresses, int ipAddressesCount, int uid)
203             throws RemoteException {
204         long timestamp = System.currentTimeMillis();
205         getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
206 
207         for (INetdEventCallback callback : mNetdEventCallbackList) {
208             if (callback != null) {
209                 callback.onDnsEvent(netId, eventType, returnCode, hostname, ipAddresses,
210                         ipAddressesCount, timestamp, uid);
211             }
212         }
213     }
214 
215     @Override
216     // Called concurrently by multiple binder threads.
217     // This method must not block or perform long-running operations.
onNat64PrefixEvent(int netId, boolean added, String prefixString, int prefixLength)218     public synchronized void onNat64PrefixEvent(int netId,
219             boolean added, String prefixString, int prefixLength)
220             throws RemoteException {
221         for (INetdEventCallback callback : mNetdEventCallbackList) {
222             if (callback != null) {
223                 callback.onNat64PrefixEvent(netId, added, prefixString, prefixLength);
224             }
225         }
226     }
227 
228     @Override
229     // Called concurrently by multiple binder threads.
230     // This method must not block or perform long-running operations.
onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated)231     public synchronized void onPrivateDnsValidationEvent(int netId,
232             String ipAddress, String hostname, boolean validated)
233             throws RemoteException {
234         for (INetdEventCallback callback : mNetdEventCallbackList) {
235             if (callback != null) {
236                 callback.onPrivateDnsValidationEvent(netId, ipAddress, hostname, validated);
237             }
238         }
239     }
240 
241     @Override
242     // Called concurrently by multiple binder threads.
243     // This method must not block or perform long-running operations.
onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid)244     public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr,
245             int port, int uid) throws RemoteException {
246         long timestamp = System.currentTimeMillis();
247         getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
248 
249         for (INetdEventCallback callback : mNetdEventCallbackList) {
250             if (callback != null) {
251                 callback.onConnectEvent(ipAddr, port, timestamp, uid);
252             }
253         }
254     }
255 
256     @Override
onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs)257     public synchronized void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
258             byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs) {
259         String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, "");
260         final long timestampMs;
261         if (timestampNs > 0) {
262             timestampMs = timestampNs / NANOS_PER_MS;
263         } else {
264             timestampMs = System.currentTimeMillis();
265         }
266 
267         WakeupEvent event = new WakeupEvent();
268         event.iface = iface;
269         event.timestampMs = timestampMs;
270         event.uid = uid;
271         event.ethertype = ethertype;
272         event.dstHwAddr = MacAddress.fromBytes(dstHw);
273         event.srcIp = srcIp;
274         event.dstIp = dstIp;
275         event.ipNextHeader = ipNextHeader;
276         event.srcPort = srcPort;
277         event.dstPort = dstPort;
278         addWakeupEvent(event);
279 
280         String dstMac = event.dstHwAddr.toString();
281         StatsLog.write(StatsLog.PACKET_WAKEUP_OCCURRED,
282                 uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
283     }
284 
285     @Override
onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets, int[] rttsUs, int[] sentAckDiffsMs)286     public synchronized void onTcpSocketStatsEvent(int[] networkIds,
287             int[] sentPackets, int[] lostPackets, int[] rttsUs, int[] sentAckDiffsMs) {
288         if (networkIds.length != sentPackets.length
289                 || networkIds.length != lostPackets.length
290                 || networkIds.length != rttsUs.length
291                 || networkIds.length != sentAckDiffsMs.length) {
292             Log.e(TAG, "Mismatched lengths of TCP socket stats data arrays");
293             return;
294         }
295 
296         long timestamp = System.currentTimeMillis();
297         for (int i = 0; i < networkIds.length; i++) {
298             int netId = networkIds[i];
299             int sent = sentPackets[i];
300             int lost = lostPackets[i];
301             int rttUs = rttsUs[i];
302             int sentAckDiffMs = sentAckDiffsMs[i];
303             getMetricsForNetwork(timestamp, netId)
304                     .addTcpStatsResult(sent, lost, rttUs, sentAckDiffMs);
305         }
306     }
307 
308     @Override
getInterfaceVersion()309     public int getInterfaceVersion() throws RemoteException {
310         return this.VERSION;
311     }
312 
addWakeupEvent(WakeupEvent event)313     private void addWakeupEvent(WakeupEvent event) {
314         String iface = event.iface;
315         mWakeupEvents.append(event);
316         WakeupStats stats = mWakeupStats.get(iface);
317         if (stats == null) {
318             stats = new WakeupStats(iface);
319             mWakeupStats.put(iface, stats);
320         }
321         stats.countEvent(event);
322     }
323 
flushStatistics(List<IpConnectivityEvent> events)324     public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
325         for (int i = 0; i < mNetworkMetrics.size(); i++) {
326             ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics;
327             if (stats.eventCount == 0) {
328                 continue;
329             }
330             events.add(IpConnectivityEventBuilder.toProto(stats));
331         }
332         for (int i = 0; i < mNetworkMetrics.size(); i++) {
333             DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics;
334             if (ev.eventCount == 0) {
335                 continue;
336             }
337             events.add(IpConnectivityEventBuilder.toProto(ev));
338         }
339         for (int i = 0; i < mWakeupStats.size(); i++) {
340             events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
341         }
342         mNetworkMetrics.clear();
343         mWakeupStats.clear();
344     }
345 
list(PrintWriter pw)346     public synchronized void list(PrintWriter pw) {
347         pw.println("dns/connect events:");
348         for (int i = 0; i < mNetworkMetrics.size(); i++) {
349             pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
350         }
351         for (int i = 0; i < mNetworkMetrics.size(); i++) {
352             pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
353         }
354         pw.println("");
355         pw.println("network statistics:");
356         for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
357             pw.println(s);
358         }
359         pw.println("");
360         pw.println("packet wakeup events:");
361         for (int i = 0; i < mWakeupStats.size(); i++) {
362             pw.println(mWakeupStats.valueAt(i));
363         }
364         for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
365             pw.println(wakeup);
366         }
367     }
368 
listAsProtos(PrintWriter pw)369     public synchronized void listAsProtos(PrintWriter pw) {
370         for (int i = 0; i < mNetworkMetrics.size(); i++) {
371             pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics));
372         }
373         for (int i = 0; i < mNetworkMetrics.size(); i++) {
374             pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics));
375         }
376         for (int i = 0; i < mWakeupStats.size(); i++) {
377             pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
378         }
379     }
380 
getTransports(int netId)381     private long getTransports(int netId) {
382         // TODO: directly query ConnectivityService instead of going through Binder interface.
383         NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
384         if (nc == null) {
385             return 0;
386         }
387         return BitUtils.packBits(nc.getTransportTypes());
388     }
389 
maybeLog(String s, Object... args)390     private static void maybeLog(String s, Object... args) {
391         if (DBG) Log.d(TAG, String.format(s, args));
392     }
393 
394     /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
395     static class NetworkMetricsSnapshot {
396 
397         public long timeMs;
398         public List<NetworkMetrics.Summary> stats = new ArrayList<>();
399 
collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics)400         static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) {
401             NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot();
402             snapshot.timeMs = timeMs;
403             for (int i = 0; i < networkMetrics.size(); i++) {
404                 NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats();
405                 if (s != null) {
406                     snapshot.stats.add(s);
407                 }
408             }
409             return snapshot;
410         }
411 
412         @Override
toString()413         public String toString() {
414             StringJoiner j = new StringJoiner(", ");
415             for (NetworkMetrics.Summary s : stats) {
416                 j.add(s.toString());
417             }
418             return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
419         }
420     }
421 }
422