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