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 addNetdEventCallback(int callerType, INetdEventCallback callback)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 removeNetdEventCallback(int callerType)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 isValidCallerType(int callerType)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 NetdEventListenerService(Context context)155 public NetdEventListenerService(Context context) { 156 this(context.getSystemService(ConnectivityManager.class)); 157 } 158 159 @VisibleForTesting NetdEventListenerService(ConnectivityManager cm)160 public NetdEventListenerService(ConnectivityManager cm) { 161 // We are started when boot is complete, so ConnectivityService should already be running. 162 mCm = cm; 163 } 164 projectSnapshotTime(long timeMs)165 private static long projectSnapshotTime(long timeMs) { 166 return (timeMs / METRICS_SNAPSHOT_SPAN_MS) * METRICS_SNAPSHOT_SPAN_MS; 167 } 168 getMetricsForNetwork(long timeMs, int netId)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 getNetworkMetricsSnapshots()180 private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() { 181 collectPendingMetricsSnapshot(System.currentTimeMillis()); 182 return mNetworkMetricsSnapshots.toArray(); 183 } 184 collectPendingMetricsSnapshot(long timeMs)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. onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, String hostname, String[] ipAddresses, int ipAddressesCount, int uid)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. onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated)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. onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid)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 onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs)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 onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets, int[] rttsUs, int[] sentAckDiffsMs)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 addWakeupEvent(WakeupEvent event)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 flushStatistics(List<IpConnectivityEvent> events)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 list(PrintWriter pw)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 listAsProtos(PrintWriter pw)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 getTransports(int netId)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 maybeLog(String s, Object... args)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 collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics)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 toString()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