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