2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.connectivity;
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.INetdEventCallback;
24 import android.net.NetworkRequest;
25 import android.net.metrics.DnsEvent;
26 import android.net.metrics.INetdEventListener;
27 import android.net.metrics.IpConnectivityLog;
28 import android.os.RemoteException;
29 import android.util.Log;
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.util.IndentingPrintWriter;
35 import java.io.PrintWriter;
36 import java.util.Arrays;
37 import java.util.SortedMap;
38 import java.util.TreeMap;
42 * Implementation of the INetdEventListener interface.
44 public class NetdEventListenerService extends INetdEventListener.Stub {
46 public static final String SERVICE_NAME = "netd_listener";
48 private static final String TAG = NetdEventListenerService.class.getSimpleName();
49 private static final boolean DBG = true;
50 private static final boolean VDBG = false;
52 // TODO: read this constant from system property
53 private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100;
55 // Stores the results of a number of consecutive DNS lookups on the same network.
56 // This class is not thread-safe and it is the responsibility of the service to call its methods
57 // on one thread at a time.
58 private class DnsEventBatch {
59 private final int mNetId;
61 private final byte[] mEventTypes = new byte[MAX_LOOKUPS_PER_DNS_EVENT];
62 private final byte[] mReturnCodes = new byte[MAX_LOOKUPS_PER_DNS_EVENT];
63 private final int[] mLatenciesMs = new int[MAX_LOOKUPS_PER_DNS_EVENT];
64 private int mEventCount;
66 public DnsEventBatch(int netId) {
70 public void addResult(byte eventType, byte returnCode, int latencyMs) {
71 mEventTypes[mEventCount] = eventType;
72 mReturnCodes[mEventCount] = returnCode;
73 mLatenciesMs[mEventCount] = latencyMs;
75 if (mEventCount == MAX_LOOKUPS_PER_DNS_EVENT) {
80 public void logAndClear() {
81 // Did we lose a race with addResult?
82 if (mEventCount == 0) {
86 // Only log as many events as we actually have.
87 byte[] eventTypes = Arrays.copyOf(mEventTypes, mEventCount);
88 byte[] returnCodes = Arrays.copyOf(mReturnCodes, mEventCount);
89 int[] latenciesMs = Arrays.copyOf(mLatenciesMs, mEventCount);
90 mMetricsLog.log(new DnsEvent(mNetId, eventTypes, returnCodes, latenciesMs));
91 maybeLog(String.format("Logging %d results for netId %d", mEventCount, mNetId));
95 // For debugging and unit tests only.
96 public String toString() {
97 return String.format("%s %d %d", getClass().getSimpleName(), mNetId, mEventCount);
101 // Only sorted for ease of debugging. Because we only typically have a handful of networks up
102 // at any given time, performance is not a concern.
104 private final SortedMap<Integer, DnsEventBatch> mEventBatches = new TreeMap<>();
106 // We register a NetworkCallback to ensure that when a network disconnects, we flush the DNS
107 // queries we've logged on that network. Because we do not do this periodically, we might lose
108 // up to MAX_LOOKUPS_PER_DNS_EVENT lookup stats on each network when the system is shutting
109 // down. We believe this to be sufficient for now.
110 private final ConnectivityManager mCm;
111 private final IpConnectivityLog mMetricsLog;
112 private final NetworkCallback mNetworkCallback = new NetworkCallback() {
114 public void onLost(Network network) {
115 synchronized (NetdEventListenerService.this) {
116 DnsEventBatch batch = mEventBatches.remove(network.netId);
124 // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM
125 // by the device owner. It's DevicePolicyManager's responsibility to ensure that.
127 private INetdEventCallback mNetdEventCallback;
129 public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) {
130 mNetdEventCallback = callback;
134 public synchronized boolean unregisterNetdEventCallback() {
135 mNetdEventCallback = null;
139 public NetdEventListenerService(Context context) {
140 this(context.getSystemService(ConnectivityManager.class), new IpConnectivityLog());
144 public NetdEventListenerService(ConnectivityManager cm, IpConnectivityLog log) {
145 // We are started when boot is complete, so ConnectivityService should already be running.
148 final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
149 mCm.registerNetworkCallback(request, mNetworkCallback);
153 // Called concurrently by multiple binder threads.
154 // This method must not block or perform long-running operations.
155 public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
156 String hostname, String[] ipAddresses, int ipAddressesCount, int uid)
157 throws RemoteException {
158 maybeVerboseLog(String.format("onDnsEvent(%d, %d, %d, %d)",
159 netId, eventType, returnCode, latencyMs));
161 DnsEventBatch batch = mEventBatches.get(netId);
163 batch = new DnsEventBatch(netId);
164 mEventBatches.put(netId, batch);
166 batch.addResult((byte) eventType, (byte) returnCode, latencyMs);
168 if (mNetdEventCallback != null) {
169 mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount,
170 System.currentTimeMillis(), uid);
175 // Called concurrently by multiple binder threads.
176 // This method must not block or perform long-running operations.
177 public synchronized void onConnectEvent(int netId, int latencyMs, String ipAddr, int port,
178 int uid) throws RemoteException {
179 maybeVerboseLog(String.format("onConnectEvent(%d, %d)", netId, latencyMs));
181 if (mNetdEventCallback != null) {
182 mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
186 public synchronized void dump(PrintWriter writer) {
187 IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
188 pw.println(TAG + ":");
190 for (DnsEventBatch batch : mEventBatches.values()) {
191 pw.println(batch.toString());
196 private static void maybeLog(String s) {
197 if (DBG) Log.d(TAG, s);
200 private static void maybeVerboseLog(String s) {
201 if (VDBG) Log.d(TAG, s);