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.ConnectivityMetricsEvent;
21 import android.net.IIpConnectivityMetrics;
22 import android.net.INetdEventCallback;
23 import android.net.NetworkStack;
24 import android.net.metrics.ApfProgramEvent;
25 import android.net.metrics.IpConnectivityLog;
26 import android.os.Binder;
27 import android.os.Process;
28 import android.provider.Settings;
29 import android.text.TextUtils;
30 import android.text.format.DateUtils;
31 import android.util.ArrayMap;
32 import android.util.Base64;
33 import android.util.Log;
34 
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.util.RingBuffer;
38 import com.android.internal.util.TokenBucket;
39 import com.android.server.LocalServices;
40 import com.android.server.SystemService;
41 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
42 
43 import java.io.FileDescriptor;
44 import java.io.IOException;
45 import java.io.PrintWriter;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.List;
49 import java.util.function.ToIntFunction;
50 
51 /**
52  * Event buffering service for core networking and connectivity metrics.
53  *
54  * {@hide}
55  */
56 final public class IpConnectivityMetrics extends SystemService {
57     private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
58     private static final boolean DBG = false;
59 
60     // The logical version numbers of ipconnectivity.proto, corresponding to the
61     // "version" field of IpConnectivityLog.
62     private static final int NYC      = 0;
63     private static final int NYC_MR1  = 1;
64     private static final int NYC_MR2  = 2;
65     public static final int VERSION   = NYC_MR2;
66 
67     private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
68 
69     // Default size of the event rolling log for bug report dumps.
70     private static final int DEFAULT_LOG_SIZE = 500;
71     // Default size of the event buffer for metrics reporting.
72     // Once the buffer is full, incoming events are dropped.
73     private static final int DEFAULT_BUFFER_SIZE = 2000;
74     // Maximum size of the event buffer.
75     private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
76 
77     private static final int MAXIMUM_CONNECT_LATENCY_RECORDS = 20000;
78 
79     private static final int ERROR_RATE_LIMITED = -1;
80 
81     // Lock ensuring that concurrent manipulations of the event buffers are correct.
82     // There are three concurrent operations to synchronize:
83     //  - appending events to the buffer.
84     //  - iterating throught the buffer.
85     //  - flushing the buffer content and replacing it by a new buffer.
86     private final Object mLock = new Object();
87 
88     // Implementation instance of IIpConnectivityMetrics.aidl.
89     @VisibleForTesting
90     public final Impl impl = new Impl();
91     // Subservice listening to Netd events via INetdEventListener.aidl.
92     @VisibleForTesting
93     NetdEventListenerService mNetdListener;
94 
95     // Rolling log of the most recent events. This log is used for dumping
96     // connectivity events in bug reports.
97     @GuardedBy("mLock")
98     private final RingBuffer<ConnectivityMetricsEvent> mEventLog =
99             new RingBuffer(ConnectivityMetricsEvent.class, DEFAULT_LOG_SIZE);
100     // Buffer of connectivity events used for metrics reporting. This buffer
101     // does not rotate automatically and instead saturates when it becomes full.
102     // It is flushed at metrics reporting.
103     @GuardedBy("mLock")
104     private ArrayList<ConnectivityMetricsEvent> mBuffer;
105     // Total number of events dropped from mBuffer since last metrics reporting.
106     @GuardedBy("mLock")
107     private int mDropped;
108     // Capacity of mBuffer
109     @GuardedBy("mLock")
110     private int mCapacity;
111     // A list of rate limiting counters keyed by connectivity event types for
112     // metrics reporting mBuffer.
113     @GuardedBy("mLock")
114     private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
115 
116     private final ToIntFunction<Context> mCapacityGetter;
117 
118     @VisibleForTesting
119     final DefaultNetworkMetrics mDefaultNetworkMetrics = new DefaultNetworkMetrics();
120 
IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter)121     public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
122         super(ctx);
123         mCapacityGetter = capacityGetter;
124         initBuffer();
125     }
126 
IpConnectivityMetrics(Context ctx)127     public IpConnectivityMetrics(Context ctx) {
128         this(ctx, READ_BUFFER_SIZE);
129     }
130 
131     @Override
onStart()132     public void onStart() {
133         if (DBG) Log.d(TAG, "onStart");
134     }
135 
136     @Override
onBootPhase(int phase)137     public void onBootPhase(int phase) {
138         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
139             if (DBG) Log.d(TAG, "onBootPhase");
140             mNetdListener = new NetdEventListenerService(getContext());
141 
142             publishBinderService(SERVICE_NAME, impl);
143             publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener);
144 
145             LocalServices.addService(Logger.class, new LoggerImpl());
146         }
147     }
148 
149     @VisibleForTesting
bufferCapacity()150     public int bufferCapacity() {
151         return mCapacityGetter.applyAsInt(getContext());
152     }
153 
initBuffer()154     private void initBuffer() {
155         synchronized (mLock) {
156             mDropped = 0;
157             mCapacity = bufferCapacity();
158             mBuffer = new ArrayList<>(mCapacity);
159         }
160     }
161 
append(ConnectivityMetricsEvent event)162     private int append(ConnectivityMetricsEvent event) {
163         if (DBG) Log.d(TAG, "logEvent: " + event);
164         synchronized (mLock) {
165             mEventLog.append(event);
166             final int left = mCapacity - mBuffer.size();
167             if (event == null) {
168                 return left;
169             }
170             if (isRateLimited(event)) {
171                 // Do not count as a dropped event. TODO: consider adding separate counter
172                 return ERROR_RATE_LIMITED;
173             }
174             if (left == 0) {
175                 mDropped++;
176                 return 0;
177             }
178             mBuffer.add(event);
179             return left - 1;
180         }
181     }
182 
isRateLimited(ConnectivityMetricsEvent event)183     private boolean isRateLimited(ConnectivityMetricsEvent event) {
184         TokenBucket tb = mBuckets.get(event.data.getClass());
185         return (tb != null) && !tb.get();
186     }
187 
flushEncodedOutput()188     private String flushEncodedOutput() {
189         final ArrayList<ConnectivityMetricsEvent> events;
190         final int dropped;
191         synchronized (mLock) {
192             events = mBuffer;
193             dropped = mDropped;
194             initBuffer();
195         }
196 
197         final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);
198 
199         mDefaultNetworkMetrics.flushEvents(protoEvents);
200 
201         if (mNetdListener != null) {
202             mNetdListener.flushStatistics(protoEvents);
203         }
204 
205         final byte[] data;
206         try {
207             data = IpConnectivityEventBuilder.serialize(dropped, protoEvents);
208         } catch (IOException e) {
209             Log.e(TAG, "could not serialize events", e);
210             return "";
211         }
212 
213         return Base64.encodeToString(data, Base64.DEFAULT);
214     }
215 
216     /**
217      * Clear the event buffer and prints its content as a protobuf serialized byte array
218      * inside a base64 encoded string.
219      */
cmdFlush(PrintWriter pw)220     private void cmdFlush(PrintWriter pw) {
221         pw.print(flushEncodedOutput());
222     }
223 
224     /**
225      * Print the content of the rolling event buffer in human readable format.
226      * Also print network dns/connect statistics and recent default network events.
227      */
cmdList(PrintWriter pw)228     private void cmdList(PrintWriter pw) {
229         pw.println("metrics events:");
230         final List<ConnectivityMetricsEvent> events = getEvents();
231         for (ConnectivityMetricsEvent ev : events) {
232             pw.println(ev.toString());
233         }
234         pw.println("");
235         if (mNetdListener != null) {
236             mNetdListener.list(pw);
237         }
238         pw.println("");
239         mDefaultNetworkMetrics.listEvents(pw);
240     }
241 
242     /*
243      * Print the content of the rolling event buffer in text proto format.
244      */
cmdListAsProto(PrintWriter pw)245     private void cmdListAsProto(PrintWriter pw) {
246         final List<ConnectivityMetricsEvent> events = getEvents();
247         for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
248             pw.print(ev.toString());
249         }
250         if (mNetdListener != null) {
251             mNetdListener.listAsProtos(pw);
252         }
253         mDefaultNetworkMetrics.listEventsAsProto(pw);
254     }
255 
256     /*
257      * Return a copy of metrics events stored in buffer for metrics uploading.
258      */
getEvents()259     private List<ConnectivityMetricsEvent> getEvents() {
260         synchronized (mLock) {
261             return Arrays.asList(mEventLog.toArray());
262         }
263     }
264 
265     public final class Impl extends IIpConnectivityMetrics.Stub {
266         // Dump and flushes the metrics event buffer in base64 encoded serialized proto output.
267         static final String CMD_FLUSH = "flush";
268         // Dump the rolling buffer of metrics event in human readable proto text format.
269         static final String CMD_PROTO = "proto";
270         // Dump the rolling buffer of metrics event and pretty print events using a human readable
271         // format. Also print network dns/connect statistics and default network event time series.
272         static final String CMD_LIST = "list";
273         // By default any other argument will fall into the default case which is the equivalent
274         // of calling both the "list" and "ipclient" commands. This includes most notably bug
275         // reports collected by dumpsys.cpp with the "-a" argument.
276         static final String CMD_DEFAULT = "";
277 
278         @Override
logEvent(ConnectivityMetricsEvent event)279         public int logEvent(ConnectivityMetricsEvent event) {
280             NetworkStack.checkNetworkStackPermission(getContext());
281             return append(event);
282         }
283 
284         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)285         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
286             enforceDumpPermission();
287             if (DBG) Log.d(TAG, "dumpsys " + TextUtils.join(" ", args));
288             final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
289             switch (cmd) {
290                 case CMD_FLUSH:
291                     cmdFlush(pw);
292                     return;
293                 case CMD_PROTO:
294                     cmdListAsProto(pw);
295                     return;
296                 case CMD_LIST:
297                 default:
298                     cmdList(pw);
299                     return;
300             }
301         }
302 
enforceDumpPermission()303         private void enforceDumpPermission() {
304             enforcePermission(android.Manifest.permission.DUMP);
305         }
306 
enforcePermission(String what)307         private void enforcePermission(String what) {
308             getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
309         }
310 
enforceNetdEventListeningPermission()311         private void enforceNetdEventListeningPermission() {
312             final int uid = Binder.getCallingUid();
313             if (uid != Process.SYSTEM_UID) {
314                 throw new SecurityException(String.format("Uid %d has no permission to listen for"
315                         + " netd events.", uid));
316             }
317         }
318 
319         @Override
addNetdEventCallback(int callerType, INetdEventCallback callback)320         public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
321             enforceNetdEventListeningPermission();
322             if (mNetdListener == null) {
323                 return false;
324             }
325             return mNetdListener.addNetdEventCallback(callerType, callback);
326         }
327 
328         @Override
removeNetdEventCallback(int callerType)329         public boolean removeNetdEventCallback(int callerType) {
330             enforceNetdEventListeningPermission();
331             if (mNetdListener == null) {
332                 // if the service is null, we aren't registered anyway
333                 return true;
334             }
335             return mNetdListener.removeNetdEventCallback(callerType);
336         }
337     };
338 
339     private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> {
340         int size = Settings.Global.getInt(ctx.getContentResolver(),
341                 Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
342         if (size <= 0) {
343             return DEFAULT_BUFFER_SIZE;
344         }
345         return Math.min(size, MAXIMUM_BUFFER_SIZE);
346     };
347 
makeRateLimitingBuckets()348     private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() {
349         ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>();
350         // one token every minute, 50 tokens max: burst of ~50 events every hour.
351         map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
352         return map;
353     }
354 
355     /** Direct non-Binder interface for event producer clients within the system servers. */
356     public interface Logger {
defaultNetworkMetrics()357         DefaultNetworkMetrics defaultNetworkMetrics();
358     }
359 
360     private class LoggerImpl implements Logger {
defaultNetworkMetrics()361         public DefaultNetworkMetrics defaultNetworkMetrics() {
362             return mDefaultNetworkMetrics;
363         }
364     }
365 }
366