Home | History | Annotate | Download | only in connectivity
      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