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 android.content.Context;
     20 import android.net.ConnectivityManager;
     21 import android.net.ConnectivityManager.NetworkCallback;
     22 import android.net.Network;
     23 import android.net.NetworkRequest;
     24 import android.net.metrics.DnsEvent;
     25 import android.net.metrics.IDnsEventListener;
     26 import android.net.metrics.IpConnectivityLog;
     27 import android.util.Log;
     28 
     29 import com.android.internal.annotations.GuardedBy;
     30 import com.android.internal.annotations.VisibleForTesting;
     31 import com.android.internal.util.IndentingPrintWriter;
     32 
     33 import java.io.PrintWriter;
     34 import java.util.Arrays;
     35 import java.util.SortedMap;
     36 import java.util.TreeMap;
     37 
     38 
     39 /**
     40  * Implementation of the IDnsEventListener interface.
     41  */
     42 public class DnsEventListenerService extends IDnsEventListener.Stub {
     43 
     44     public static final String SERVICE_NAME = "dns_listener";
     45 
     46     private static final String TAG = DnsEventListenerService.class.getSimpleName();
     47     private static final boolean DBG = true;
     48     private static final boolean VDBG = false;
     49 
     50     // TODO: read this constant from system property
     51     private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100;
     52 
     53     // Stores the results of a number of consecutive DNS lookups on the same network.
     54     // This class is not thread-safe and it is the responsibility of the service to call its methods
     55     // on one thread at a time.
     56     private class DnsEventBatch {
     57         private final int mNetId;
     58 
     59         private final byte[] mEventTypes = new byte[MAX_LOOKUPS_PER_DNS_EVENT];
     60         private final byte[] mReturnCodes = new byte[MAX_LOOKUPS_PER_DNS_EVENT];
     61         private final int[] mLatenciesMs = new int[MAX_LOOKUPS_PER_DNS_EVENT];
     62         private int mEventCount;
     63 
     64         public DnsEventBatch(int netId) {
     65             mNetId = netId;
     66         }
     67 
     68         public void addResult(byte eventType, byte returnCode, int latencyMs) {
     69             mEventTypes[mEventCount] = eventType;
     70             mReturnCodes[mEventCount] = returnCode;
     71             mLatenciesMs[mEventCount] = latencyMs;
     72             mEventCount++;
     73             if (mEventCount == MAX_LOOKUPS_PER_DNS_EVENT) {
     74                 logAndClear();
     75             }
     76         }
     77 
     78         public void logAndClear() {
     79             // Did we lose a race with addResult?
     80             if (mEventCount == 0) {
     81                 return;
     82             }
     83 
     84             // Only log as many events as we actually have.
     85             byte[] eventTypes = Arrays.copyOf(mEventTypes, mEventCount);
     86             byte[] returnCodes = Arrays.copyOf(mReturnCodes, mEventCount);
     87             int[] latenciesMs = Arrays.copyOf(mLatenciesMs, mEventCount);
     88             mMetricsLog.log(new DnsEvent(mNetId, eventTypes, returnCodes, latenciesMs));
     89             maybeLog(String.format("Logging %d results for netId %d", mEventCount, mNetId));
     90             mEventCount = 0;
     91         }
     92 
     93         // For debugging and unit tests only.
     94         public String toString() {
     95             return String.format("%s %d %d", getClass().getSimpleName(), mNetId, mEventCount);
     96         }
     97     }
     98 
     99     // Only sorted for ease of debugging. Because we only typically have a handful of networks up
    100     // at any given time, performance is not a concern.
    101     @GuardedBy("this")
    102     private final SortedMap<Integer, DnsEventBatch> mEventBatches = new TreeMap<>();
    103 
    104     // We register a NetworkCallback to ensure that when a network disconnects, we flush the DNS
    105     // queries we've logged on that network. Because we do not do this periodically, we might lose
    106     // up to MAX_LOOKUPS_PER_DNS_EVENT lookup stats on each network when the system is shutting
    107     // down. We believe this to be sufficient for now.
    108     private final ConnectivityManager mCm;
    109     private final IpConnectivityLog mMetricsLog;
    110     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
    111         @Override
    112         public void onLost(Network network) {
    113             synchronized (DnsEventListenerService.this) {
    114                 DnsEventBatch batch = mEventBatches.remove(network.netId);
    115                 if (batch != null) {
    116                     batch.logAndClear();
    117                 }
    118             }
    119         }
    120     };
    121 
    122     public DnsEventListenerService(Context context) {
    123         this(context.getSystemService(ConnectivityManager.class), new IpConnectivityLog());
    124     }
    125 
    126     @VisibleForTesting
    127     public DnsEventListenerService(ConnectivityManager cm, IpConnectivityLog log) {
    128         // We are started when boot is complete, so ConnectivityService should already be running.
    129         mCm = cm;
    130         mMetricsLog = log;
    131         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
    132         mCm.registerNetworkCallback(request, mNetworkCallback);
    133     }
    134 
    135     @Override
    136     // Called concurrently by multiple binder threads.
    137     public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs) {
    138         maybeVerboseLog(String.format("onDnsEvent(%d, %d, %d, %d)",
    139                 netId, eventType, returnCode, latencyMs));
    140 
    141         DnsEventBatch batch = mEventBatches.get(netId);
    142         if (batch == null) {
    143             batch = new DnsEventBatch(netId);
    144             mEventBatches.put(netId, batch);
    145         }
    146         batch.addResult((byte) eventType, (byte) returnCode, latencyMs);
    147     }
    148 
    149     public synchronized void dump(PrintWriter writer) {
    150         IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
    151         pw.println(TAG + ":");
    152         pw.increaseIndent();
    153         for (DnsEventBatch batch : mEventBatches.values()) {
    154             pw.println(batch.toString());
    155         }
    156         pw.decreaseIndent();
    157     }
    158 
    159     private static void maybeLog(String s) {
    160         if (DBG) Log.d(TAG, s);
    161     }
    162 
    163     private static void maybeVerboseLog(String s) {
    164         if (VDBG) Log.d(TAG, s);
    165     }
    166 }
    167