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