OSDN Git Service

8450482f403247cb9a46c72547c0c39379fc828b
[android-x86/frameworks-base.git] / services / core / java / com / android / server / connectivity / NetdEventListenerService.java
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.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;
30
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.util.IndentingPrintWriter;
34
35 import java.io.PrintWriter;
36 import java.util.Arrays;
37 import java.util.SortedMap;
38 import java.util.TreeMap;
39
40
41 /**
42  * Implementation of the INetdEventListener interface.
43  */
44 public class NetdEventListenerService extends INetdEventListener.Stub {
45
46     public static final String SERVICE_NAME = "netd_listener";
47
48     private static final String TAG = NetdEventListenerService.class.getSimpleName();
49     private static final boolean DBG = true;
50     private static final boolean VDBG = false;
51
52     // TODO: read this constant from system property
53     private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100;
54
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;
60
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;
65
66         public DnsEventBatch(int netId) {
67             mNetId = netId;
68         }
69
70         public void addResult(byte eventType, byte returnCode, int latencyMs) {
71             mEventTypes[mEventCount] = eventType;
72             mReturnCodes[mEventCount] = returnCode;
73             mLatenciesMs[mEventCount] = latencyMs;
74             mEventCount++;
75             if (mEventCount == MAX_LOOKUPS_PER_DNS_EVENT) {
76                 logAndClear();
77             }
78         }
79
80         public void logAndClear() {
81             // Did we lose a race with addResult?
82             if (mEventCount == 0) {
83                 return;
84             }
85
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));
92             mEventCount = 0;
93         }
94
95         // For debugging and unit tests only.
96         public String toString() {
97             return String.format("%s %d %d", getClass().getSimpleName(), mNetId, mEventCount);
98         }
99     }
100
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.
103     @GuardedBy("this")
104     private final SortedMap<Integer, DnsEventBatch> mEventBatches = new TreeMap<>();
105
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() {
113         @Override
114         public void onLost(Network network) {
115             synchronized (NetdEventListenerService.this) {
116                 DnsEventBatch batch = mEventBatches.remove(network.netId);
117                 if (batch != null) {
118                     batch.logAndClear();
119                 }
120             }
121         }
122     };
123
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.
126     @GuardedBy("this")
127     private INetdEventCallback mNetdEventCallback;
128
129     public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) {
130         mNetdEventCallback = callback;
131         return true;
132     }
133
134     public synchronized boolean unregisterNetdEventCallback() {
135         mNetdEventCallback = null;
136         return true;
137     }
138
139     public NetdEventListenerService(Context context) {
140         this(context.getSystemService(ConnectivityManager.class), new IpConnectivityLog());
141     }
142
143     @VisibleForTesting
144     public NetdEventListenerService(ConnectivityManager cm, IpConnectivityLog log) {
145         // We are started when boot is complete, so ConnectivityService should already be running.
146         mCm = cm;
147         mMetricsLog = log;
148         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
149         mCm.registerNetworkCallback(request, mNetworkCallback);
150     }
151
152     @Override
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));
160
161         DnsEventBatch batch = mEventBatches.get(netId);
162         if (batch == null) {
163             batch = new DnsEventBatch(netId);
164             mEventBatches.put(netId, batch);
165         }
166         batch.addResult((byte) eventType, (byte) returnCode, latencyMs);
167
168         if (mNetdEventCallback != null) {
169             mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount,
170                     System.currentTimeMillis(), uid);
171         }
172     }
173
174     @Override
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));
180
181         if (mNetdEventCallback != null) {
182             mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
183         }
184     }
185
186     public synchronized void dump(PrintWriter writer) {
187         IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
188         pw.println(TAG + ":");
189         pw.increaseIndent();
190         for (DnsEventBatch batch : mEventBatches.values()) {
191             pw.println(batch.toString());
192         }
193         pw.decreaseIndent();
194     }
195
196     private static void maybeLog(String s) {
197         if (DBG) Log.d(TAG, s);
198     }
199
200     private static void maybeVerboseLog(String s) {
201         if (VDBG) Log.d(TAG, s);
202     }
203 }