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 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 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 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 155 public NetdEventListenerService(Context context) { 156 this(context.getSystemService(ConnectivityManager.class)); 157 } 158 159 @VisibleForTesting 160 public NetdEventListenerService(ConnectivityManager cm) { 161 // We are started when boot is complete, so ConnectivityService should already be running. 162 mCm = cm; 163 } 164 165 private static long projectSnapshotTime(long timeMs) { 166 return (timeMs / METRICS_SNAPSHOT_SPAN_MS) * METRICS_SNAPSHOT_SPAN_MS; 167 } 168 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 180 private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() { 181 collectPendingMetricsSnapshot(System.currentTimeMillis()); 182 return mNetworkMetricsSnapshots.toArray(); 183 } 184 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. 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. 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. 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 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 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 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 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 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 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 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 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 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 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