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.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