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 import android.util.StatsLog; 39 40 import com.android.internal.annotations.GuardedBy; 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.util.BitUtils; 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 StatsLog.write(StatsLog.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 addWakeupEvent(WakeupEvent event)313 private void addWakeupEvent(WakeupEvent event) { 314 String iface = event.iface; 315 mWakeupEvents.append(event); 316 WakeupStats stats = mWakeupStats.get(iface); 317 if (stats == null) { 318 stats = new WakeupStats(iface); 319 mWakeupStats.put(iface, stats); 320 } 321 stats.countEvent(event); 322 } 323 flushStatistics(List<IpConnectivityEvent> events)324 public synchronized void flushStatistics(List<IpConnectivityEvent> events) { 325 for (int i = 0; i < mNetworkMetrics.size(); i++) { 326 ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics; 327 if (stats.eventCount == 0) { 328 continue; 329 } 330 events.add(IpConnectivityEventBuilder.toProto(stats)); 331 } 332 for (int i = 0; i < mNetworkMetrics.size(); i++) { 333 DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics; 334 if (ev.eventCount == 0) { 335 continue; 336 } 337 events.add(IpConnectivityEventBuilder.toProto(ev)); 338 } 339 for (int i = 0; i < mWakeupStats.size(); i++) { 340 events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); 341 } 342 mNetworkMetrics.clear(); 343 mWakeupStats.clear(); 344 } 345 list(PrintWriter pw)346 public synchronized void list(PrintWriter pw) { 347 pw.println("dns/connect events:"); 348 for (int i = 0; i < mNetworkMetrics.size(); i++) { 349 pw.println(mNetworkMetrics.valueAt(i).connectMetrics); 350 } 351 for (int i = 0; i < mNetworkMetrics.size(); i++) { 352 pw.println(mNetworkMetrics.valueAt(i).dnsMetrics); 353 } 354 pw.println(""); 355 pw.println("network statistics:"); 356 for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) { 357 pw.println(s); 358 } 359 pw.println(""); 360 pw.println("packet wakeup events:"); 361 for (int i = 0; i < mWakeupStats.size(); i++) { 362 pw.println(mWakeupStats.valueAt(i)); 363 } 364 for (WakeupEvent wakeup : mWakeupEvents.toArray()) { 365 pw.println(wakeup); 366 } 367 } 368 listAsProtos(PrintWriter pw)369 public synchronized void listAsProtos(PrintWriter pw) { 370 for (int i = 0; i < mNetworkMetrics.size(); i++) { 371 pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics)); 372 } 373 for (int i = 0; i < mNetworkMetrics.size(); i++) { 374 pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics)); 375 } 376 for (int i = 0; i < mWakeupStats.size(); i++) { 377 pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); 378 } 379 } 380 getTransports(int netId)381 private long getTransports(int netId) { 382 // TODO: directly query ConnectivityService instead of going through Binder interface. 383 NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId)); 384 if (nc == null) { 385 return 0; 386 } 387 return BitUtils.packBits(nc.getTransportTypes()); 388 } 389 maybeLog(String s, Object... args)390 private static void maybeLog(String s, Object... args) { 391 if (DBG) Log.d(TAG, String.format(s, args)); 392 } 393 394 /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */ 395 static class NetworkMetricsSnapshot { 396 397 public long timeMs; 398 public List<NetworkMetrics.Summary> stats = new ArrayList<>(); 399 collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics)400 static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) { 401 NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot(); 402 snapshot.timeMs = timeMs; 403 for (int i = 0; i < networkMetrics.size(); i++) { 404 NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats(); 405 if (s != null) { 406 snapshot.stats.add(s); 407 } 408 } 409 return snapshot; 410 } 411 412 @Override toString()413 public String toString() { 414 StringJoiner j = new StringJoiner(", "); 415 for (NetworkMetrics.Summary s : stats) { 416 j.add(s.toString()); 417 } 418 return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString()); 419 } 420 } 421 } 422