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 
39 import com.android.internal.annotations.GuardedBy;
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.internal.util.BitUtils;
42 import com.android.internal.util.FrameworkStatsLog;
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         FrameworkStatsLog.write(FrameworkStatsLog.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 
313     @Override
getInterfaceHash()314     public String getInterfaceHash() {
315         return this.HASH;
316     }
317 
addWakeupEvent(WakeupEvent event)318     private void addWakeupEvent(WakeupEvent event) {
319         String iface = event.iface;
320         mWakeupEvents.append(event);
321         WakeupStats stats = mWakeupStats.get(iface);
322         if (stats == null) {
323             stats = new WakeupStats(iface);
324             mWakeupStats.put(iface, stats);
325         }
326         stats.countEvent(event);
327     }
328 
flushStatistics(List<IpConnectivityEvent> events)329     public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
330         for (int i = 0; i < mNetworkMetrics.size(); i++) {
331             ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics;
332             if (stats.eventCount == 0) {
333                 continue;
334             }
335             events.add(IpConnectivityEventBuilder.toProto(stats));
336         }
337         for (int i = 0; i < mNetworkMetrics.size(); i++) {
338             DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics;
339             if (ev.eventCount == 0) {
340                 continue;
341             }
342             events.add(IpConnectivityEventBuilder.toProto(ev));
343         }
344         for (int i = 0; i < mWakeupStats.size(); i++) {
345             events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
346         }
347         mNetworkMetrics.clear();
348         mWakeupStats.clear();
349     }
350 
list(PrintWriter pw)351     public synchronized void list(PrintWriter pw) {
352         pw.println("dns/connect events:");
353         for (int i = 0; i < mNetworkMetrics.size(); i++) {
354             pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
355         }
356         for (int i = 0; i < mNetworkMetrics.size(); i++) {
357             pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
358         }
359         pw.println("");
360         pw.println("network statistics:");
361         for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
362             pw.println(s);
363         }
364         pw.println("");
365         pw.println("packet wakeup events:");
366         for (int i = 0; i < mWakeupStats.size(); i++) {
367             pw.println(mWakeupStats.valueAt(i));
368         }
369         for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
370             pw.println(wakeup);
371         }
372     }
373 
listAsProtos(PrintWriter pw)374     public synchronized void listAsProtos(PrintWriter pw) {
375         for (int i = 0; i < mNetworkMetrics.size(); i++) {
376             pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics));
377         }
378         for (int i = 0; i < mNetworkMetrics.size(); i++) {
379             pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics));
380         }
381         for (int i = 0; i < mWakeupStats.size(); i++) {
382             pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
383         }
384     }
385 
getTransports(int netId)386     private long getTransports(int netId) {
387         // TODO: directly query ConnectivityService instead of going through Binder interface.
388         NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
389         if (nc == null) {
390             return 0;
391         }
392         return BitUtils.packBits(nc.getTransportTypes());
393     }
394 
maybeLog(String s, Object... args)395     private static void maybeLog(String s, Object... args) {
396         if (DBG) Log.d(TAG, String.format(s, args));
397     }
398 
399     /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
400     static class NetworkMetricsSnapshot {
401 
402         public long timeMs;
403         public List<NetworkMetrics.Summary> stats = new ArrayList<>();
404 
collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics)405         static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) {
406             NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot();
407             snapshot.timeMs = timeMs;
408             for (int i = 0; i < networkMetrics.size(); i++) {
409                 NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats();
410                 if (s != null) {
411                     snapshot.stats.add(s);
412                 }
413             }
414             return snapshot;
415         }
416 
417         @Override
toString()418         public String toString() {
419             StringJoiner j = new StringJoiner(", ");
420             for (NetworkMetrics.Summary s : stats) {
421                 j.add(s.toString());
422             }
423             return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
424         }
425     }
426 }
427