• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         NetworkMetrics metrics = mNetworkMetrics.get(netId);
175         final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
176         final long transports = (nc != null) ? BitUtils.packBits(nc.getTransportTypes()) : 0;
177         final boolean forceCollect =
178                 (metrics != null && nc != null && metrics.transports != transports);
179         collectPendingMetricsSnapshot(timeMs, forceCollect);
180         if (metrics == null || forceCollect) {
181             metrics = new NetworkMetrics(netId, transports, mConnectTb);
182             mNetworkMetrics.put(netId, metrics);
183         }
184         return metrics;
185     }
186 
getNetworkMetricsSnapshots()187     private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() {
188         collectPendingMetricsSnapshot(System.currentTimeMillis(), false /* forceCollect */);
189         return mNetworkMetricsSnapshots.toArray();
190     }
191 
collectPendingMetricsSnapshot(long timeMs, boolean forceCollect)192     private void collectPendingMetricsSnapshot(long timeMs, boolean forceCollect) {
193         // Detects time differences larger than the snapshot collection period.
194         // This is robust against clock jumps and long inactivity periods.
195         if (!forceCollect && Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
196             return;
197         }
198         mLastSnapshot = projectSnapshotTime(timeMs);
199         NetworkMetricsSnapshot snapshot =
200                 NetworkMetricsSnapshot.collect(mLastSnapshot, mNetworkMetrics);
201         if (snapshot.stats.isEmpty()) {
202             return;
203         }
204         mNetworkMetricsSnapshots.append(snapshot);
205     }
206 
207     @Override
208     // Called concurrently by multiple binder threads.
209     // 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)210     public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
211             String hostname, String[] ipAddresses, int ipAddressesCount, int uid)
212             throws RemoteException {
213         long timestamp = System.currentTimeMillis();
214         getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
215 
216         for (INetdEventCallback callback : mNetdEventCallbackList) {
217             if (callback != null) {
218                 callback.onDnsEvent(netId, eventType, returnCode, hostname, ipAddresses,
219                         ipAddressesCount, timestamp, uid);
220             }
221         }
222     }
223 
224     @Override
225     // Called concurrently by multiple binder threads.
226     // This method must not block or perform long-running operations.
onNat64PrefixEvent(int netId, boolean added, String prefixString, int prefixLength)227     public synchronized void onNat64PrefixEvent(int netId,
228             boolean added, String prefixString, int prefixLength)
229             throws RemoteException {
230         for (INetdEventCallback callback : mNetdEventCallbackList) {
231             if (callback != null) {
232                 callback.onNat64PrefixEvent(netId, added, prefixString, prefixLength);
233             }
234         }
235     }
236 
237     @Override
238     // Called concurrently by multiple binder threads.
239     // This method must not block or perform long-running operations.
onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated)240     public synchronized void onPrivateDnsValidationEvent(int netId,
241             String ipAddress, String hostname, boolean validated)
242             throws RemoteException {
243         for (INetdEventCallback callback : mNetdEventCallbackList) {
244             if (callback != null) {
245                 callback.onPrivateDnsValidationEvent(netId, ipAddress, hostname, validated);
246             }
247         }
248     }
249 
250     @Override
251     // Called concurrently by multiple binder threads.
252     // This method must not block or perform long-running operations.
onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid)253     public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr,
254             int port, int uid) throws RemoteException {
255         long timestamp = System.currentTimeMillis();
256         getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
257 
258         for (INetdEventCallback callback : mNetdEventCallbackList) {
259             if (callback != null) {
260                 callback.onConnectEvent(ipAddr, port, timestamp, uid);
261             }
262         }
263     }
264 
265     @Override
onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs)266     public synchronized void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
267             byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs) {
268         String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, "");
269         final long timestampMs;
270         if (timestampNs > 0) {
271             timestampMs = timestampNs / NANOS_PER_MS;
272         } else {
273             timestampMs = System.currentTimeMillis();
274         }
275 
276         WakeupEvent event = new WakeupEvent();
277         event.iface = iface;
278         event.timestampMs = timestampMs;
279         event.uid = uid;
280         event.ethertype = ethertype;
281         event.dstHwAddr = MacAddress.fromBytes(dstHw);
282         event.srcIp = srcIp;
283         event.dstIp = dstIp;
284         event.ipNextHeader = ipNextHeader;
285         event.srcPort = srcPort;
286         event.dstPort = dstPort;
287         addWakeupEvent(event);
288 
289         String dstMac = event.dstHwAddr.toString();
290         FrameworkStatsLog.write(FrameworkStatsLog.PACKET_WAKEUP_OCCURRED,
291                 uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
292     }
293 
294     @Override
onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets, int[] rttsUs, int[] sentAckDiffsMs)295     public synchronized void onTcpSocketStatsEvent(int[] networkIds,
296             int[] sentPackets, int[] lostPackets, int[] rttsUs, int[] sentAckDiffsMs) {
297         if (networkIds.length != sentPackets.length
298                 || networkIds.length != lostPackets.length
299                 || networkIds.length != rttsUs.length
300                 || networkIds.length != sentAckDiffsMs.length) {
301             Log.e(TAG, "Mismatched lengths of TCP socket stats data arrays");
302             return;
303         }
304 
305         long timestamp = System.currentTimeMillis();
306         for (int i = 0; i < networkIds.length; i++) {
307             int netId = networkIds[i];
308             int sent = sentPackets[i];
309             int lost = lostPackets[i];
310             int rttUs = rttsUs[i];
311             int sentAckDiffMs = sentAckDiffsMs[i];
312             getMetricsForNetwork(timestamp, netId)
313                     .addTcpStatsResult(sent, lost, rttUs, sentAckDiffMs);
314         }
315     }
316 
317     @Override
getInterfaceVersion()318     public int getInterfaceVersion() throws RemoteException {
319         return this.VERSION;
320     }
321 
322     @Override
getInterfaceHash()323     public String getInterfaceHash() {
324         return this.HASH;
325     }
326 
addWakeupEvent(WakeupEvent event)327     private void addWakeupEvent(WakeupEvent event) {
328         String iface = event.iface;
329         mWakeupEvents.append(event);
330         WakeupStats stats = mWakeupStats.get(iface);
331         if (stats == null) {
332             stats = new WakeupStats(iface);
333             mWakeupStats.put(iface, stats);
334         }
335         stats.countEvent(event);
336     }
337 
flushStatistics(List<IpConnectivityEvent> events)338     public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
339         for (int i = 0; i < mNetworkMetrics.size(); i++) {
340             ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics;
341             if (stats.eventCount == 0) {
342                 continue;
343             }
344             events.add(IpConnectivityEventBuilder.toProto(stats));
345         }
346         for (int i = 0; i < mNetworkMetrics.size(); i++) {
347             DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics;
348             if (ev.eventCount == 0) {
349                 continue;
350             }
351             events.add(IpConnectivityEventBuilder.toProto(ev));
352         }
353         for (int i = 0; i < mWakeupStats.size(); i++) {
354             events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
355         }
356         mNetworkMetrics.clear();
357         mWakeupStats.clear();
358     }
359 
list(PrintWriter pw)360     public synchronized void list(PrintWriter pw) {
361         pw.println("dns/connect events:");
362         for (int i = 0; i < mNetworkMetrics.size(); i++) {
363             pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
364         }
365         for (int i = 0; i < mNetworkMetrics.size(); i++) {
366             pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
367         }
368         pw.println("");
369         pw.println("network statistics:");
370         for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
371             pw.println(s);
372         }
373         pw.println("");
374         pw.println("packet wakeup events:");
375         for (int i = 0; i < mWakeupStats.size(); i++) {
376             pw.println(mWakeupStats.valueAt(i));
377         }
378         for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
379             pw.println(wakeup);
380         }
381     }
382 
383     /**
384      * Convert events in the buffer to a list of IpConnectivityEvent protos
385      */
listAsProtos()386     public synchronized List<IpConnectivityEvent> listAsProtos() {
387         List<IpConnectivityEvent> list = new ArrayList<>();
388         for (int i = 0; i < mNetworkMetrics.size(); i++) {
389             list.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics));
390         }
391         for (int i = 0; i < mNetworkMetrics.size(); i++) {
392             list.add(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics));
393         }
394         for (int i = 0; i < mWakeupStats.size(); i++) {
395             list.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
396         }
397         return list;
398     }
399 
400     /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
401     static class NetworkMetricsSnapshot {
402 
403         public long timeMs;
404         public List<NetworkMetrics.Summary> stats = new ArrayList<>();
405 
collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics)406         static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) {
407             NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot();
408             snapshot.timeMs = timeMs;
409             for (int i = 0; i < networkMetrics.size(); i++) {
410                 NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats();
411                 if (s != null) {
412                     snapshot.stats.add(s);
413                 }
414             }
415             return snapshot;
416         }
417 
418         @Override
toString()419         public String toString() {
420             StringJoiner j = new StringJoiner(", ");
421             for (NetworkMetrics.Summary s : stats) {
422                 j.add(s.toString());
423             }
424             return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
425         }
426     }
427 
428     private class TransportForNetIdNetworkCallback extends ConnectivityManager.NetworkCallback {
429         private final SparseArray<NetworkCapabilities> mCapabilities = new SparseArray<>();
430 
431         @Override
onCapabilitiesChanged(Network network, NetworkCapabilities nc)432         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
433             synchronized (mCapabilities) {
434                 mCapabilities.put(network.getNetId(), nc);
435             }
436         }
437 
438         @Override
onLost(Network network)439         public void onLost(Network network) {
440             synchronized (mCapabilities) {
441                 mCapabilities.remove(network.getNetId());
442             }
443         }
444 
445         @Nullable
getNetworkCapabilities(int netId)446         public NetworkCapabilities getNetworkCapabilities(int netId) {
447             synchronized (mCapabilities) {
448                 return mCapabilities.get(netId);
449             }
450         }
451     }
452 }
453