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