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