blob: 18ab73100b81032409b9ed8dbd73122361b7e76f [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.connectivity;
import android.content.Context;
import android.net.metrics.DnsEvent;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.metrics.IDnsEventListener;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Implementation of the IDnsEventListener interface.
*/
public class DnsEventListenerService extends IDnsEventListener.Stub {
public static final String SERVICE_NAME = "dns_listener";
private static final String TAG = DnsEventListenerService.class.getSimpleName();
private static final boolean DBG = true;
private static final boolean VDBG = false;
private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100;
// Stores the results of a number of consecutive DNS lookups on the same network.
// This class is not thread-safe and it is the responsibility of the service to call its methods
// on one thread at a time.
private static class DnsEventBatch {
private final int mNetId;
private final byte[] mEventTypes = new byte[MAX_LOOKUPS_PER_DNS_EVENT];
private final byte[] mReturnCodes = new byte[MAX_LOOKUPS_PER_DNS_EVENT];
private final int[] mLatenciesMs = new int[MAX_LOOKUPS_PER_DNS_EVENT];
private int mEventCount;
public DnsEventBatch(int netId) {
mNetId = netId;
}
public void addResult(byte eventType, byte returnCode, int latencyMs) {
mEventTypes[mEventCount] = eventType;
mReturnCodes[mEventCount] = returnCode;
mLatenciesMs[mEventCount] = latencyMs;
mEventCount++;
if (mEventCount == MAX_LOOKUPS_PER_DNS_EVENT) {
logAndClear();
}
}
public void logAndClear() {
// Did we lose a race with addResult?
if (mEventCount == 0) {
return;
}
// Only log as many events as we actually have.
byte[] eventTypes = Arrays.copyOf(mEventTypes, mEventCount);
byte[] returnCodes = Arrays.copyOf(mReturnCodes, mEventCount);
int[] latenciesMs = Arrays.copyOf(mLatenciesMs, mEventCount);
DnsEvent.logEvent(mNetId, eventTypes, returnCodes, latenciesMs);
maybeLog(String.format("Logging %d results for netId %d", mEventCount, mNetId));
mEventCount = 0;
}
// For debugging and unit tests only.
public String toString() {
return String.format("%s %d %d", getClass().getSimpleName(), mNetId, mEventCount);
}
}
// Only sorted for ease of debugging. Because we only typically have a handful of networks up
// at any given time, performance is not a concern.
@GuardedBy("this")
private SortedMap<Integer, DnsEventBatch> mEventBatches = new TreeMap<>();
// We register a NetworkCallback to ensure that when a network disconnects, we flush the DNS
// queries we've logged on that network. Because we do not do this periodically, we might lose
// up to MAX_LOOKUPS_PER_DNS_EVENT lookup stats on each network when the system is shutting
// down. We believe this to be sufficient for now.
private final ConnectivityManager mCm;
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onLost(Network network) {
synchronized (DnsEventListenerService.this) {
DnsEventBatch batch = mEventBatches.remove(network.netId);
if (batch != null) {
batch.logAndClear();
}
}
}
};
public DnsEventListenerService(Context context) {
// We are started when boot is complete, so ConnectivityService should already be running.
final NetworkRequest request = new NetworkRequest.Builder()
.clearCapabilities()
.build();
mCm = context.getSystemService(ConnectivityManager.class);
mCm.registerNetworkCallback(request, mNetworkCallback);
}
@Override
// Called concurrently by multiple binder threads.
public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs) {
maybeVerboseLog(String.format("onDnsEvent(%d, %d, %d, %d)",
netId, eventType, returnCode, latencyMs));
DnsEventBatch batch = mEventBatches.get(netId);
if (batch == null) {
batch = new DnsEventBatch(netId);
mEventBatches.put(netId, batch);
}
batch.addResult((byte) eventType, (byte) returnCode, latencyMs);
}
public synchronized void dump(PrintWriter writer) {
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println(TAG + ":");
pw.increaseIndent();
for (DnsEventBatch batch : mEventBatches.values()) {
pw.println(batch.toString());
}
pw.decreaseIndent();
}
private static void maybeLog(String s) {
if (DBG) Log.d(TAG, s);
}
private static void maybeVerboseLog(String s) {
if (VDBG) Log.d(TAG, s);
}
}