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.NetworkMetrics; 31 import android.net.metrics.WakeupEvent; 32 import android.net.metrics.WakeupStats; 33 import android.os.RemoteException; 34 import android.text.format.DateUtils; 35 import android.util.ArrayMap; 36 import android.util.Log; 37 import android.util.SparseArray; 38 39 import com.android.internal.annotations.GuardedBy; 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.util.BitUtils; 42 import com.android.internal.util.FrameworkStatsLog; 43 import com.android.internal.util.RingBuffer; 44 import com.android.internal.util.TokenBucket; 45 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; 46 47 import java.io.PrintWriter; 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.StringJoiner; 51 52 /** 53 * Implementation of the INetdEventListener interface. 54 */ 55 public class NetdEventListenerService extends INetdEventListener.Stub { 56 57 public static final String SERVICE_NAME = "netd_listener"; 58 59 private static final String TAG = NetdEventListenerService.class.getSimpleName(); 60 private static final boolean DBG = false; 61 62 // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum 63 // bursts of 5000 measurements. 64 private static final int CONNECT_LATENCY_BURST_LIMIT = 5000; 65 private static final int CONNECT_LATENCY_FILL_RATE = 15 * (int) DateUtils.SECOND_IN_MILLIS; 66 67 private static final long METRICS_SNAPSHOT_SPAN_MS = 5 * DateUtils.MINUTE_IN_MILLIS; 68 private static final int METRICS_SNAPSHOT_BUFFER_SIZE = 48; // 4 hours 69 70 @VisibleForTesting 71 static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024; 72 // TODO: dedup this String constant with the one used in 73 // ConnectivityService#wakeupModifyInterface(). 74 @VisibleForTesting 75 static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:"; 76 77 // Array of aggregated DNS and connect events sent by netd, grouped by net id. 78 @GuardedBy("this") 79 private final SparseArray<NetworkMetrics> mNetworkMetrics = new SparseArray<>(); 80 81 @GuardedBy("this") 82 private final RingBuffer<NetworkMetricsSnapshot> mNetworkMetricsSnapshots = 83 new RingBuffer<>(NetworkMetricsSnapshot.class, METRICS_SNAPSHOT_BUFFER_SIZE); 84 @GuardedBy("this") 85 private long mLastSnapshot = 0; 86 87 // Array of aggregated wakeup event stats, grouped by interface name. 88 @GuardedBy("this") 89 private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>(); 90 // Ring buffer array for storing packet wake up events sent by Netd. 91 @GuardedBy("this") 92 private final RingBuffer<WakeupEvent> mWakeupEvents = 93 new RingBuffer<>(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH); 94 95 private final ConnectivityManager mCm; 96 97 @GuardedBy("this") 98 private final TokenBucket mConnectTb = 99 new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT); 100 101 102 /** 103 * There are only 3 possible callbacks. 104 * 105 * mNetdEventCallbackList[CALLBACK_CALLER_CONNECTIVITY_SERVICE] 106 * Callback registered/unregistered by ConnectivityService. 107 * 108 * mNetdEventCallbackList[CALLBACK_CALLER_DEVICE_POLICY] 109 * Callback registered/unregistered when logging is being enabled/disabled in DPM 110 * by the device owner. It's DevicePolicyManager's responsibility to ensure that. 111 * 112 * mNetdEventCallbackList[CALLBACK_CALLER_NETWORK_WATCHLIST] 113 * Callback registered/unregistered by NetworkWatchlistService. 114 */ 115 @GuardedBy("this") 116 private static final int[] ALLOWED_CALLBACK_TYPES = { 117 INetdEventCallback.CALLBACK_CALLER_CONNECTIVITY_SERVICE, 118 INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, 119 INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST 120 }; 121 122 @GuardedBy("this") 123 private INetdEventCallback[] mNetdEventCallbackList = 124 new INetdEventCallback[ALLOWED_CALLBACK_TYPES.length]; 125 addNetdEventCallback(int callerType, INetdEventCallback callback)126 public synchronized boolean addNetdEventCallback(int callerType, INetdEventCallback callback) { 127 if (!isValidCallerType(callerType)) { 128 Log.e(TAG, "Invalid caller type: " + callerType); 129 return false; 130 } 131 mNetdEventCallbackList[callerType] = callback; 132 return true; 133 } 134 removeNetdEventCallback(int callerType)135 public synchronized boolean removeNetdEventCallback(int callerType) { 136 if (!isValidCallerType(callerType)) { 137 Log.e(TAG, "Invalid caller type: " + callerType); 138 return false; 139 } 140 mNetdEventCallbackList[callerType] = null; 141 return true; 142 } 143 isValidCallerType(int callerType)144 private static boolean isValidCallerType(int callerType) { 145 for (int i = 0; i < ALLOWED_CALLBACK_TYPES.length; i++) { 146 if (callerType == ALLOWED_CALLBACK_TYPES[i]) { 147 return true; 148 } 149 } 150 return false; 151 } 152 NetdEventListenerService(Context context)153 public NetdEventListenerService(Context context) { 154 this(context.getSystemService(ConnectivityManager.class)); 155 } 156 157 @VisibleForTesting NetdEventListenerService(ConnectivityManager cm)158 public NetdEventListenerService(ConnectivityManager cm) { 159 // We are started when boot is complete, so ConnectivityService should already be running. 160 mCm = cm; 161 } 162 projectSnapshotTime(long timeMs)163 private static long projectSnapshotTime(long timeMs) { 164 return (timeMs / METRICS_SNAPSHOT_SPAN_MS) * METRICS_SNAPSHOT_SPAN_MS; 165 } 166 getMetricsForNetwork(long timeMs, int netId)167 private NetworkMetrics getMetricsForNetwork(long timeMs, int netId) { 168 collectPendingMetricsSnapshot(timeMs); 169 NetworkMetrics metrics = mNetworkMetrics.get(netId); 170 if (metrics == null) { 171 // TODO: allow to change transport for a given netid. 172 metrics = new NetworkMetrics(netId, getTransports(netId), mConnectTb); 173 mNetworkMetrics.put(netId, metrics); 174 } 175 return metrics; 176 } 177 getNetworkMetricsSnapshots()178 private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() { 179 collectPendingMetricsSnapshot(System.currentTimeMillis()); 180 return mNetworkMetricsSnapshots.toArray(); 181 } 182 collectPendingMetricsSnapshot(long timeMs)183 private void collectPendingMetricsSnapshot(long timeMs) { 184 // Detects time differences larger than the snapshot collection period. 185 // This is robust against clock jumps and long inactivity periods. 186 if (Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) { 187 return; 188 } 189 mLastSnapshot = projectSnapshotTime(timeMs); 190 NetworkMetricsSnapshot snapshot = 191 NetworkMetricsSnapshot.collect(mLastSnapshot, mNetworkMetrics); 192 if (snapshot.stats.isEmpty()) { 193 return; 194 } 195 mNetworkMetricsSnapshots.append(snapshot); 196 } 197 198 @Override 199 // Called concurrently by multiple binder threads. 200 // 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)201 public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, 202 String hostname, String[] ipAddresses, int ipAddressesCount, int uid) 203 throws RemoteException { 204 long timestamp = System.currentTimeMillis(); 205 getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs); 206 207 for (INetdEventCallback callback : mNetdEventCallbackList) { 208 if (callback != null) { 209 callback.onDnsEvent(netId, eventType, returnCode, hostname, ipAddresses, 210 ipAddressesCount, timestamp, uid); 211 } 212 } 213 } 214 215 @Override 216 // Called concurrently by multiple binder threads. 217 // This method must not block or perform long-running operations. onNat64PrefixEvent(int netId, boolean added, String prefixString, int prefixLength)218 public synchronized void onNat64PrefixEvent(int netId, 219 boolean added, String prefixString, int prefixLength) 220 throws RemoteException { 221 for (INetdEventCallback callback : mNetdEventCallbackList) { 222 if (callback != null) { 223 callback.onNat64PrefixEvent(netId, added, prefixString, prefixLength); 224 } 225 } 226 } 227 228 @Override 229 // Called concurrently by multiple binder threads. 230 // This method must not block or perform long-running operations. onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated)231 public synchronized void onPrivateDnsValidationEvent(int netId, 232 String ipAddress, String hostname, boolean validated) 233 throws RemoteException { 234 for (INetdEventCallback callback : mNetdEventCallbackList) { 235 if (callback != null) { 236 callback.onPrivateDnsValidationEvent(netId, ipAddress, hostname, validated); 237 } 238 } 239 } 240 241 @Override 242 // Called concurrently by multiple binder threads. 243 // This method must not block or perform long-running operations. onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid)244 public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, 245 int port, int uid) throws RemoteException { 246 long timestamp = System.currentTimeMillis(); 247 getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr); 248 249 for (INetdEventCallback callback : mNetdEventCallbackList) { 250 if (callback != null) { 251 callback.onConnectEvent(ipAddr, port, timestamp, uid); 252 } 253 } 254 } 255 256 @Override onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs)257 public synchronized void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, 258 byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs) { 259 String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, ""); 260 final long timestampMs; 261 if (timestampNs > 0) { 262 timestampMs = timestampNs / NANOS_PER_MS; 263 } else { 264 timestampMs = System.currentTimeMillis(); 265 } 266 267 WakeupEvent event = new WakeupEvent(); 268 event.iface = iface; 269 event.timestampMs = timestampMs; 270 event.uid = uid; 271 event.ethertype = ethertype; 272 event.dstHwAddr = MacAddress.fromBytes(dstHw); 273 event.srcIp = srcIp; 274 event.dstIp = dstIp; 275 event.ipNextHeader = ipNextHeader; 276 event.srcPort = srcPort; 277 event.dstPort = dstPort; 278 addWakeupEvent(event); 279 280 String dstMac = event.dstHwAddr.toString(); 281 FrameworkStatsLog.write(FrameworkStatsLog.PACKET_WAKEUP_OCCURRED, 282 uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort); 283 } 284 285 @Override onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets, int[] rttsUs, int[] sentAckDiffsMs)286 public synchronized void onTcpSocketStatsEvent(int[] networkIds, 287 int[] sentPackets, int[] lostPackets, int[] rttsUs, int[] sentAckDiffsMs) { 288 if (networkIds.length != sentPackets.length 289 || networkIds.length != lostPackets.length 290 || networkIds.length != rttsUs.length 291 || networkIds.length != sentAckDiffsMs.length) { 292 Log.e(TAG, "Mismatched lengths of TCP socket stats data arrays"); 293 return; 294 } 295 296 long timestamp = System.currentTimeMillis(); 297 for (int i = 0; i < networkIds.length; i++) { 298 int netId = networkIds[i]; 299 int sent = sentPackets[i]; 300 int lost = lostPackets[i]; 301 int rttUs = rttsUs[i]; 302 int sentAckDiffMs = sentAckDiffsMs[i]; 303 getMetricsForNetwork(timestamp, netId) 304 .addTcpStatsResult(sent, lost, rttUs, sentAckDiffMs); 305 } 306 } 307 308 @Override getInterfaceVersion()309 public int getInterfaceVersion() throws RemoteException { 310 return this.VERSION; 311 } 312 313 @Override getInterfaceHash()314 public String getInterfaceHash() { 315 return this.HASH; 316 } 317 addWakeupEvent(WakeupEvent event)318 private void addWakeupEvent(WakeupEvent event) { 319 String iface = event.iface; 320 mWakeupEvents.append(event); 321 WakeupStats stats = mWakeupStats.get(iface); 322 if (stats == null) { 323 stats = new WakeupStats(iface); 324 mWakeupStats.put(iface, stats); 325 } 326 stats.countEvent(event); 327 } 328 flushStatistics(List<IpConnectivityEvent> events)329 public synchronized void flushStatistics(List<IpConnectivityEvent> events) { 330 for (int i = 0; i < mNetworkMetrics.size(); i++) { 331 ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics; 332 if (stats.eventCount == 0) { 333 continue; 334 } 335 events.add(IpConnectivityEventBuilder.toProto(stats)); 336 } 337 for (int i = 0; i < mNetworkMetrics.size(); i++) { 338 DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics; 339 if (ev.eventCount == 0) { 340 continue; 341 } 342 events.add(IpConnectivityEventBuilder.toProto(ev)); 343 } 344 for (int i = 0; i < mWakeupStats.size(); i++) { 345 events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); 346 } 347 mNetworkMetrics.clear(); 348 mWakeupStats.clear(); 349 } 350 list(PrintWriter pw)351 public synchronized void list(PrintWriter pw) { 352 pw.println("dns/connect events:"); 353 for (int i = 0; i < mNetworkMetrics.size(); i++) { 354 pw.println(mNetworkMetrics.valueAt(i).connectMetrics); 355 } 356 for (int i = 0; i < mNetworkMetrics.size(); i++) { 357 pw.println(mNetworkMetrics.valueAt(i).dnsMetrics); 358 } 359 pw.println(""); 360 pw.println("network statistics:"); 361 for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) { 362 pw.println(s); 363 } 364 pw.println(""); 365 pw.println("packet wakeup events:"); 366 for (int i = 0; i < mWakeupStats.size(); i++) { 367 pw.println(mWakeupStats.valueAt(i)); 368 } 369 for (WakeupEvent wakeup : mWakeupEvents.toArray()) { 370 pw.println(wakeup); 371 } 372 } 373 listAsProtos(PrintWriter pw)374 public synchronized void listAsProtos(PrintWriter pw) { 375 for (int i = 0; i < mNetworkMetrics.size(); i++) { 376 pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics)); 377 } 378 for (int i = 0; i < mNetworkMetrics.size(); i++) { 379 pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics)); 380 } 381 for (int i = 0; i < mWakeupStats.size(); i++) { 382 pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); 383 } 384 } 385 getTransports(int netId)386 private long getTransports(int netId) { 387 // TODO: directly query ConnectivityService instead of going through Binder interface. 388 NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId)); 389 if (nc == null) { 390 return 0; 391 } 392 return BitUtils.packBits(nc.getTransportTypes()); 393 } 394 maybeLog(String s, Object... args)395 private static void maybeLog(String s, Object... args) { 396 if (DBG) Log.d(TAG, String.format(s, args)); 397 } 398 399 /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */ 400 static class NetworkMetricsSnapshot { 401 402 public long timeMs; 403 public List<NetworkMetrics.Summary> stats = new ArrayList<>(); 404 collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics)405 static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) { 406 NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot(); 407 snapshot.timeMs = timeMs; 408 for (int i = 0; i < networkMetrics.size(); i++) { 409 NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats(); 410 if (s != null) { 411 snapshot.stats.add(s); 412 } 413 } 414 return snapshot; 415 } 416 417 @Override toString()418 public String toString() { 419 StringJoiner j = new StringJoiner(", "); 420 for (NetworkMetrics.Summary s : stats) { 421 j.add(s.toString()); 422 } 423 return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString()); 424 } 425 } 426 } 427