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.annotation.Nullable;
22 import android.content.Context;
23 import android.net.ConnectivityManager;
24 import android.net.INetdEventCallback;
25 import android.net.MacAddress;
26 import android.net.Network;
27 import android.net.NetworkCapabilities;
28 import android.net.NetworkRequest;
29 import android.net.metrics.ConnectStats;
30 import android.net.metrics.DnsEvent;
31 import android.net.metrics.INetdEventListener;
32 import android.net.metrics.NetworkMetrics;
33 import android.net.metrics.WakeupEvent;
34 import android.net.metrics.WakeupStats;
35 import android.os.RemoteException;
36 import android.text.format.DateUtils;
37 import android.util.ArrayMap;
38 import android.util.Log;
39 import android.util.SparseArray;
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.FrameworkStatsLog;
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     final TransportForNetIdNetworkCallback mCallback = new TransportForNetIdNetworkCallback();
104 
105     /**
106      * There are only 3 possible callbacks.
107      *
108      * mNetdEventCallbackList[CALLBACK_CALLER_CONNECTIVITY_SERVICE]
109      * Callback registered/unregistered by ConnectivityService.
110      *
111      * mNetdEventCallbackList[CALLBACK_CALLER_DEVICE_POLICY]
112      * Callback registered/unregistered when logging is being enabled/disabled in DPM
113      * by the device owner. It's DevicePolicyManager's responsibility to ensure that.
114      *
115      * mNetdEventCallbackList[CALLBACK_CALLER_NETWORK_WATCHLIST]
116      * Callback registered/unregistered by NetworkWatchlistService.
117      */
118     @GuardedBy("this")
119     private static final int[] ALLOWED_CALLBACK_TYPES = {
120         INetdEventCallback.CALLBACK_CALLER_CONNECTIVITY_SERVICE,
121         INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY,
122         INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST
123     };
124 
125     @GuardedBy("this")
126     private INetdEventCallback[] mNetdEventCallbackList =
127             new INetdEventCallback[ALLOWED_CALLBACK_TYPES.length];
128 
addNetdEventCallback(int callerType, INetdEventCallback callback)129     public synchronized boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
130         if (!isValidCallerType(callerType)) {
131             Log.e(TAG, "Invalid caller type: " + callerType);
132             return false;
133         }
134         mNetdEventCallbackList[callerType] = callback;
135         return true;
136     }
137 
removeNetdEventCallback(int callerType)138     public synchronized boolean removeNetdEventCallback(int callerType) {
139         if (!isValidCallerType(callerType)) {
140             Log.e(TAG, "Invalid caller type: " + callerType);
141             return false;
142         }
143         mNetdEventCallbackList[callerType] = null;
144         return true;
145     }
146 
isValidCallerType(int callerType)147     private static boolean isValidCallerType(int callerType) {
148         for (int i = 0; i < ALLOWED_CALLBACK_TYPES.length; i++) {
149             if (callerType == ALLOWED_CALLBACK_TYPES[i]) {
150                 return true;
151             }
152         }
153         return false;
154     }
155 
NetdEventListenerService(Context context)156     public NetdEventListenerService(Context context) {
157         this(context.getSystemService(ConnectivityManager.class));
158     }
159 
160     @VisibleForTesting
NetdEventListenerService(ConnectivityManager cm)161     public NetdEventListenerService(ConnectivityManager cm) {
162         // We are started when boot is complete, so ConnectivityService should already be running.
163         mCm = cm;
164         // Clear all capabilities to listen all networks.
165         mCm.registerNetworkCallback(new NetworkRequest.Builder().clearCapabilities().build(),
166                 mCallback);
167     }
168 
projectSnapshotTime(long timeMs)169     private static long projectSnapshotTime(long timeMs) {
170         return (timeMs / METRICS_SNAPSHOT_SPAN_MS) * METRICS_SNAPSHOT_SPAN_MS;
171     }
172 
getMetricsForNetwork(long timeMs, int netId)173     private NetworkMetrics getMetricsForNetwork(long timeMs, int netId) {
174         collectPendingMetricsSnapshot(timeMs);
175         NetworkMetrics metrics = mNetworkMetrics.get(netId);
176         if (metrics == null) {
177             // TODO: allow to change transport for a given netid.
178             metrics = new NetworkMetrics(netId, getTransports(netId), mConnectTb);
179             mNetworkMetrics.put(netId, metrics);
180         }
181         return metrics;
182     }
183 
getNetworkMetricsSnapshots()184     private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() {
185         collectPendingMetricsSnapshot(System.currentTimeMillis());
186         return mNetworkMetricsSnapshots.toArray();
187     }
188 
collectPendingMetricsSnapshot(long timeMs)189     private void collectPendingMetricsSnapshot(long timeMs) {
190         // Detects time differences larger than the snapshot collection period.
191         // This is robust against clock jumps and long inactivity periods.
192         if (Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
193             return;
194         }
195         mLastSnapshot = projectSnapshotTime(timeMs);
196         NetworkMetricsSnapshot snapshot =
197                 NetworkMetricsSnapshot.collect(mLastSnapshot, mNetworkMetrics);
198         if (snapshot.stats.isEmpty()) {
199             return;
200         }
201         mNetworkMetricsSnapshots.append(snapshot);
202     }
203 
204     @Override
205     // Called concurrently by multiple binder threads.
206     // 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)207     public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
208             String hostname, String[] ipAddresses, int ipAddressesCount, int uid)
209             throws RemoteException {
210         long timestamp = System.currentTimeMillis();
211         getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
212 
213         for (INetdEventCallback callback : mNetdEventCallbackList) {
214             if (callback != null) {
215                 callback.onDnsEvent(netId, eventType, returnCode, hostname, ipAddresses,
216                         ipAddressesCount, timestamp, uid);
217             }
218         }
219     }
220 
221     @Override
222     // Called concurrently by multiple binder threads.
223     // This method must not block or perform long-running operations.
onNat64PrefixEvent(int netId, boolean added, String prefixString, int prefixLength)224     public synchronized void onNat64PrefixEvent(int netId,
225             boolean added, String prefixString, int prefixLength)
226             throws RemoteException {
227         for (INetdEventCallback callback : mNetdEventCallbackList) {
228             if (callback != null) {
229                 callback.onNat64PrefixEvent(netId, added, prefixString, prefixLength);
230             }
231         }
232     }
233 
234     @Override
235     // Called concurrently by multiple binder threads.
236     // This method must not block or perform long-running operations.
onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated)237     public synchronized void onPrivateDnsValidationEvent(int netId,
238             String ipAddress, String hostname, boolean validated)
239             throws RemoteException {
240         for (INetdEventCallback callback : mNetdEventCallbackList) {
241             if (callback != null) {
242                 callback.onPrivateDnsValidationEvent(netId, ipAddress, hostname, validated);
243             }
244         }
245     }
246 
247     @Override
248     // Called concurrently by multiple binder threads.
249     // This method must not block or perform long-running operations.
onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid)250     public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr,
251             int port, int uid) throws RemoteException {
252         long timestamp = System.currentTimeMillis();
253         getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
254 
255         for (INetdEventCallback callback : mNetdEventCallbackList) {
256             if (callback != null) {
257                 callback.onConnectEvent(ipAddr, port, timestamp, uid);
258             }
259         }
260     }
261 
262     @Override
onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs)263     public synchronized void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
264             byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs) {
265         String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, "");
266         final long timestampMs;
267         if (timestampNs > 0) {
268             timestampMs = timestampNs / NANOS_PER_MS;
269         } else {
270             timestampMs = System.currentTimeMillis();
271         }
272 
273         WakeupEvent event = new WakeupEvent();
274         event.iface = iface;
275         event.timestampMs = timestampMs;
276         event.uid = uid;
277         event.ethertype = ethertype;
278         event.dstHwAddr = MacAddress.fromBytes(dstHw);
279         event.srcIp = srcIp;
280         event.dstIp = dstIp;
281         event.ipNextHeader = ipNextHeader;
282         event.srcPort = srcPort;
283         event.dstPort = dstPort;
284         addWakeupEvent(event);
285 
286         String dstMac = event.dstHwAddr.toString();
287         FrameworkStatsLog.write(FrameworkStatsLog.PACKET_WAKEUP_OCCURRED,
288                 uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
289     }
290 
291     @Override
onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets, int[] rttsUs, int[] sentAckDiffsMs)292     public synchronized void onTcpSocketStatsEvent(int[] networkIds,
293             int[] sentPackets, int[] lostPackets, int[] rttsUs, int[] sentAckDiffsMs) {
294         if (networkIds.length != sentPackets.length
295                 || networkIds.length != lostPackets.length
296                 || networkIds.length != rttsUs.length
297                 || networkIds.length != sentAckDiffsMs.length) {
298             Log.e(TAG, "Mismatched lengths of TCP socket stats data arrays");
299             return;
300         }
301 
302         long timestamp = System.currentTimeMillis();
303         for (int i = 0; i < networkIds.length; i++) {
304             int netId = networkIds[i];
305             int sent = sentPackets[i];
306             int lost = lostPackets[i];
307             int rttUs = rttsUs[i];
308             int sentAckDiffMs = sentAckDiffsMs[i];
309             getMetricsForNetwork(timestamp, netId)
310                     .addTcpStatsResult(sent, lost, rttUs, sentAckDiffMs);
311         }
312     }
313 
314     @Override
getInterfaceVersion()315     public int getInterfaceVersion() throws RemoteException {
316         return this.VERSION;
317     }
318 
319     @Override
getInterfaceHash()320     public String getInterfaceHash() {
321         return this.HASH;
322     }
323 
addWakeupEvent(WakeupEvent event)324     private void addWakeupEvent(WakeupEvent event) {
325         String iface = event.iface;
326         mWakeupEvents.append(event);
327         WakeupStats stats = mWakeupStats.get(iface);
328         if (stats == null) {
329             stats = new WakeupStats(iface);
330             mWakeupStats.put(iface, stats);
331         }
332         stats.countEvent(event);
333     }
334 
flushStatistics(List<IpConnectivityEvent> events)335     public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
336         for (int i = 0; i < mNetworkMetrics.size(); i++) {
337             ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics;
338             if (stats.eventCount == 0) {
339                 continue;
340             }
341             events.add(IpConnectivityEventBuilder.toProto(stats));
342         }
343         for (int i = 0; i < mNetworkMetrics.size(); i++) {
344             DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics;
345             if (ev.eventCount == 0) {
346                 continue;
347             }
348             events.add(IpConnectivityEventBuilder.toProto(ev));
349         }
350         for (int i = 0; i < mWakeupStats.size(); i++) {
351             events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
352         }
353         mNetworkMetrics.clear();
354         mWakeupStats.clear();
355     }
356 
list(PrintWriter pw)357     public synchronized void list(PrintWriter pw) {
358         pw.println("dns/connect events:");
359         for (int i = 0; i < mNetworkMetrics.size(); i++) {
360             pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
361         }
362         for (int i = 0; i < mNetworkMetrics.size(); i++) {
363             pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
364         }
365         pw.println("");
366         pw.println("network statistics:");
367         for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
368             pw.println(s);
369         }
370         pw.println("");
371         pw.println("packet wakeup events:");
372         for (int i = 0; i < mWakeupStats.size(); i++) {
373             pw.println(mWakeupStats.valueAt(i));
374         }
375         for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
376             pw.println(wakeup);
377         }
378     }
379 
380     /**
381      * Convert events in the buffer to a list of IpConnectivityEvent protos
382      */
listAsProtos()383     public synchronized List<IpConnectivityEvent> listAsProtos() {
384         List<IpConnectivityEvent> list = new ArrayList<>();
385         for (int i = 0; i < mNetworkMetrics.size(); i++) {
386             list.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics));
387         }
388         for (int i = 0; i < mNetworkMetrics.size(); i++) {
389             list.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics));
390         }
391         for (int i = 0; i < mWakeupStats.size(); i++) {
392             list.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
393         }
394         return list;
395     }
396 
getTransports(int netId)397     private long getTransports(int netId) {
398         final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
399         if (nc == null) {
400             return 0;
401         }
402         return BitUtils.packBits(nc.getTransportTypes());
403     }
404 
405     /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
406     static class NetworkMetricsSnapshot {
407 
408         public long timeMs;
409         public List<NetworkMetrics.Summary> stats = new ArrayList<>();
410 
collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics)411         static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) {
412             NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot();
413             snapshot.timeMs = timeMs;
414             for (int i = 0; i < networkMetrics.size(); i++) {
415                 NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats();
416                 if (s != null) {
417                     snapshot.stats.add(s);
418                 }
419             }
420             return snapshot;
421         }
422 
423         @Override
toString()424         public String toString() {
425             StringJoiner j = new StringJoiner(", ");
426             for (NetworkMetrics.Summary s : stats) {
427                 j.add(s.toString());
428             }
429             return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
430         }
431     }
432 
433     private class TransportForNetIdNetworkCallback extends ConnectivityManager.NetworkCallback {
434         private final SparseArray<NetworkCapabilities> mCapabilities = new SparseArray<>();
435 
436         @Override
onCapabilitiesChanged(Network network, NetworkCapabilities nc)437         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
438             synchronized (mCapabilities) {
439                 mCapabilities.put(network.getNetId(), nc);
440             }
441         }
442 
443         @Override
onLost(Network network)444         public void onLost(Network network) {
445             synchronized (mCapabilities) {
446                 mCapabilities.remove(network.getNetId());
447             }
448         }
449 
450         @Nullable
getNetworkCapabilities(int netId)451         public NetworkCapabilities getNetworkCapabilities(int netId) {
452             synchronized (mCapabilities) {
453                 return mCapabilities.get(netId);
454             }
455         }
456     }
457 }
458