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.Network; 25 import android.net.NetworkCapabilities; 26 import android.net.metrics.ConnectStats; 27 import android.net.metrics.DnsEvent; 28 import android.net.metrics.INetdEventListener; 29 import android.net.metrics.IpConnectivityLog; 30 import android.net.metrics.WakeupEvent; 31 import android.net.metrics.WakeupStats; 32 import android.os.RemoteException; 33 import android.text.format.DateUtils; 34 import android.util.Log; 35 import android.util.ArrayMap; 36 import android.util.SparseArray; 37 import com.android.internal.annotations.GuardedBy; 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.util.BitUtils; 40 import com.android.internal.util.IndentingPrintWriter; 41 import com.android.internal.util.TokenBucket; 42 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; 43 import java.io.PrintWriter; 44 import java.util.List; 45 import java.util.function.Function; 46 import java.util.function.IntFunction; 47 48 /** 49 * Implementation of the INetdEventListener interface. 50 */ 51 public class NetdEventListenerService extends INetdEventListener.Stub { 52 53 public static final String SERVICE_NAME = "netd_listener"; 54 55 private static final String TAG = NetdEventListenerService.class.getSimpleName(); 56 private static final boolean DBG = false; 57 private static final boolean VDBG = false; 58 59 private static final int INITIAL_DNS_BATCH_SIZE = 100; 60 61 // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum 62 // bursts of 5000 measurements. 63 private static final int CONNECT_LATENCY_BURST_LIMIT = 5000; 64 private static final int CONNECT_LATENCY_FILL_RATE = 15 * (int) DateUtils.SECOND_IN_MILLIS; 65 private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000; 66 67 @VisibleForTesting 68 static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024; 69 // TODO: dedup this String constant with the one used in 70 // ConnectivityService#wakeupModifyInterface(). 71 @VisibleForTesting 72 static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:"; 73 74 // Sparse arrays of DNS and connect events, grouped by net id. 75 @GuardedBy("this") 76 private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>(); 77 @GuardedBy("this") 78 private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>(); 79 80 // Array of aggregated wakeup event stats, grouped by interface name. 81 @GuardedBy("this") 82 private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>(); 83 // Ring buffer array for storing packet wake up events sent by Netd. 84 @GuardedBy("this") 85 private final WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH]; 86 @GuardedBy("this") 87 private long mWakeupEventCursor = 0; 88 89 private final ConnectivityManager mCm; 90 91 @GuardedBy("this") 92 private final TokenBucket mConnectTb = 93 new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT); 94 // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM 95 // by the device owner. It's DevicePolicyManager's responsibility to ensure that. 96 @GuardedBy("this") 97 private INetdEventCallback mNetdEventCallback; 98 99 public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) { 100 mNetdEventCallback = callback; 101 return true; 102 } 103 104 public synchronized boolean unregisterNetdEventCallback() { 105 mNetdEventCallback = null; 106 return true; 107 } 108 109 public NetdEventListenerService(Context context) { 110 this(context.getSystemService(ConnectivityManager.class)); 111 } 112 113 @VisibleForTesting 114 public NetdEventListenerService(ConnectivityManager cm) { 115 // We are started when boot is complete, so ConnectivityService should already be running. 116 mCm = cm; 117 } 118 119 @Override 120 // Called concurrently by multiple binder threads. 121 // This method must not block or perform long-running operations. 122 public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, 123 String hostname, String[] ipAddresses, int ipAddressesCount, int uid) 124 throws RemoteException { 125 maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs); 126 127 DnsEvent dnsEvent = mDnsEvents.get(netId); 128 if (dnsEvent == null) { 129 dnsEvent = makeDnsEvent(netId); 130 mDnsEvents.put(netId, dnsEvent); 131 } 132 dnsEvent.addResult((byte) eventType, (byte) returnCode, latencyMs); 133 134 if (mNetdEventCallback != null) { 135 long timestamp = System.currentTimeMillis(); 136 mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid); 137 } 138 } 139 140 @Override 141 // Called concurrently by multiple binder threads. 142 // This method must not block or perform long-running operations. 143 public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, 144 int port, int uid) throws RemoteException { 145 maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs); 146 147 ConnectStats connectStats = mConnectEvents.get(netId); 148 if (connectStats == null) { 149 connectStats = makeConnectStats(netId); 150 mConnectEvents.put(netId, connectStats); 151 } 152 connectStats.addEvent(error, latencyMs, ipAddr); 153 154 if (mNetdEventCallback != null) { 155 mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid); 156 } 157 } 158 159 @Override 160 public synchronized void onWakeupEvent(String prefix, int uid, int gid, long timestampNs) { 161 maybeVerboseLog("onWakeupEvent(%s, %d, %d, %sns)", prefix, uid, gid, timestampNs); 162 163 // TODO: add ip protocol and port 164 165 String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, ""); 166 final long timestampMs; 167 if (timestampNs > 0) { 168 timestampMs = timestampNs / NANOS_PER_MS; 169 } else { 170 timestampMs = System.currentTimeMillis(); 171 } 172 173 addWakeupEvent(iface, timestampMs, uid); 174 } 175 176 @GuardedBy("this") 177 private void addWakeupEvent(String iface, long timestampMs, int uid) { 178 int index = wakeupEventIndex(mWakeupEventCursor); 179 mWakeupEventCursor++; 180 WakeupEvent event = new WakeupEvent(); 181 event.iface = iface; 182 event.timestampMs = timestampMs; 183 event.uid = uid; 184 mWakeupEvents[index] = event; 185 WakeupStats stats = mWakeupStats.get(iface); 186 if (stats == null) { 187 stats = new WakeupStats(iface); 188 mWakeupStats.put(iface, stats); 189 } 190 stats.countEvent(event); 191 } 192 193 @GuardedBy("this") 194 private WakeupEvent[] getWakeupEvents() { 195 int length = (int) Math.min(mWakeupEventCursor, (long) mWakeupEvents.length); 196 WakeupEvent[] out = new WakeupEvent[length]; 197 // Reverse iteration from youngest event to oldest event. 198 long inCursor = mWakeupEventCursor - 1; 199 int outIdx = out.length - 1; 200 while (outIdx >= 0) { 201 out[outIdx--] = mWakeupEvents[wakeupEventIndex(inCursor--)]; 202 } 203 return out; 204 } 205 206 private static int wakeupEventIndex(long cursor) { 207 return (int) Math.abs(cursor % WAKEUP_EVENT_BUFFER_LENGTH); 208 } 209 210 public synchronized void flushStatistics(List<IpConnectivityEvent> events) { 211 flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto); 212 flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto); 213 for (int i = 0; i < mWakeupStats.size(); i++) { 214 events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); 215 } 216 mWakeupStats.clear(); 217 } 218 219 public synchronized void dump(PrintWriter writer) { 220 IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); 221 pw.println(TAG + ":"); 222 pw.increaseIndent(); 223 list(pw); 224 pw.decreaseIndent(); 225 } 226 227 public synchronized void list(PrintWriter pw) { 228 listEvents(pw, mConnectEvents, (x) -> x, "\n"); 229 listEvents(pw, mDnsEvents, (x) -> x, "\n"); 230 for (int i = 0; i < mWakeupStats.size(); i++) { 231 pw.println(mWakeupStats.valueAt(i)); 232 } 233 for (WakeupEvent wakeup : getWakeupEvents()) { 234 pw.println(wakeup); 235 } 236 } 237 238 public synchronized void listAsProtos(PrintWriter pw) { 239 listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto, ""); 240 listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto, ""); 241 for (int i = 0; i < mWakeupStats.size(); i++) { 242 pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); 243 } 244 } 245 246 private static <T> void flushProtos(List<IpConnectivityEvent> out, SparseArray<T> in, 247 Function<T, IpConnectivityEvent> mapper) { 248 for (int i = 0; i < in.size(); i++) { 249 out.add(mapper.apply(in.valueAt(i))); 250 } 251 in.clear(); 252 } 253 254 private static <T> void listEvents( 255 PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper, String separator) { 256 // Proto derived Classes have toString method that adds a \n at the end. 257 // Let the caller control that by passing in the line separator explicitly. 258 for (int i = 0; i < events.size(); i++) { 259 pw.print(mapper.apply(events.valueAt(i))); 260 pw.print(separator); 261 } 262 } 263 264 private ConnectStats makeConnectStats(int netId) { 265 long transports = getTransports(netId); 266 return new ConnectStats(netId, transports, mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS); 267 } 268 269 private DnsEvent makeDnsEvent(int netId) { 270 long transports = getTransports(netId); 271 return new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE); 272 } 273 274 private long getTransports(int netId) { 275 // TODO: directly query ConnectivityService instead of going through Binder interface. 276 NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId)); 277 if (nc == null) { 278 return 0; 279 } 280 return BitUtils.packBits(nc.getTransportTypes()); 281 } 282 283 private static void maybeLog(String s, Object... args) { 284 if (DBG) Log.d(TAG, String.format(s, args)); 285 } 286 287 private static void maybeVerboseLog(String s, Object... args) { 288 if (VDBG) Log.d(TAG, String.format(s, args)); 289 } 290 } 291