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