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