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
17package com.android.server.connectivity;
18
19import android.content.Context;
20import android.net.ConnectivityManager;
21import android.net.INetdEventCallback;
22import android.net.Network;
23import android.net.NetworkCapabilities;
24import android.net.metrics.ConnectStats;
25import android.net.metrics.DnsEvent;
26import android.net.metrics.INetdEventListener;
27import android.net.metrics.IpConnectivityLog;
28import android.os.RemoteException;
29import android.text.format.DateUtils;
30import android.util.Log;
31import android.util.SparseArray;
32import com.android.internal.annotations.GuardedBy;
33import com.android.internal.annotations.VisibleForTesting;
34import com.android.internal.util.BitUtils;
35import com.android.internal.util.IndentingPrintWriter;
36import com.android.internal.util.TokenBucket;
37import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
38import java.io.PrintWriter;
39import java.util.List;
40import java.util.function.Function;
41import java.util.function.IntFunction;
42
43/**
44 * Implementation of the INetdEventListener interface.
45 */
46public class NetdEventListenerService extends INetdEventListener.Stub {
47
48    public static final String SERVICE_NAME = "netd_listener";
49
50    private static final String TAG = NetdEventListenerService.class.getSimpleName();
51    private static final boolean DBG = false;
52    private static final boolean VDBG = false;
53
54    private static final int INITIAL_DNS_BATCH_SIZE = 100;
55
56    // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
57    // bursts of 5000 measurements.
58    private static final int CONNECT_LATENCY_BURST_LIMIT  = 5000;
59    private static final int CONNECT_LATENCY_FILL_RATE    = 15 * (int) DateUtils.SECOND_IN_MILLIS;
60    private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
61
62    // Sparse arrays of DNS and connect events, grouped by net id.
63    @GuardedBy("this")
64    private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>();
65    @GuardedBy("this")
66    private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>();
67
68    private final ConnectivityManager mCm;
69
70    @GuardedBy("this")
71    private final TokenBucket mConnectTb =
72            new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
73    // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM
74    // by the device owner. It's DevicePolicyManager's responsibility to ensure that.
75    @GuardedBy("this")
76    private INetdEventCallback mNetdEventCallback;
77
78    public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) {
79        mNetdEventCallback = callback;
80        return true;
81    }
82
83    public synchronized boolean unregisterNetdEventCallback() {
84        mNetdEventCallback = null;
85        return true;
86    }
87
88    public NetdEventListenerService(Context context) {
89        this(context.getSystemService(ConnectivityManager.class));
90    }
91
92    @VisibleForTesting
93    public NetdEventListenerService(ConnectivityManager cm) {
94        // We are started when boot is complete, so ConnectivityService should already be running.
95        mCm = cm;
96    }
97
98    @Override
99    // Called concurrently by multiple binder threads.
100    // This method must not block or perform long-running operations.
101    public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
102            String hostname, String[] ipAddresses, int ipAddressesCount, int uid)
103            throws RemoteException {
104        maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs);
105
106        DnsEvent dnsEvent = mDnsEvents.get(netId);
107        if (dnsEvent == null) {
108            dnsEvent = makeDnsEvent(netId);
109            mDnsEvents.put(netId, dnsEvent);
110        }
111        dnsEvent.addResult((byte) eventType, (byte) returnCode, latencyMs);
112
113        if (mNetdEventCallback != null) {
114            long timestamp = System.currentTimeMillis();
115            mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
116        }
117    }
118
119    @Override
120    // Called concurrently by multiple binder threads.
121    // This method must not block or perform long-running operations.
122    public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr,
123            int port, int uid) throws RemoteException {
124        maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs);
125
126        ConnectStats connectStats = mConnectEvents.get(netId);
127        if (connectStats == null) {
128            connectStats = makeConnectStats(netId);
129            mConnectEvents.put(netId, connectStats);
130        }
131        connectStats.addEvent(error, latencyMs, ipAddr);
132
133        if (mNetdEventCallback != null) {
134            mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
135        }
136    }
137
138    public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
139        flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
140        flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
141    }
142
143    public synchronized void dump(PrintWriter writer) {
144        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
145        pw.println(TAG + ":");
146        pw.increaseIndent();
147        list(pw);
148        pw.decreaseIndent();
149    }
150
151    public synchronized void list(PrintWriter pw) {
152        listEvents(pw, mConnectEvents, (x) -> x);
153        listEvents(pw, mDnsEvents, (x) -> x);
154    }
155
156    public synchronized void listAsProtos(PrintWriter pw) {
157        listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto);
158        listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto);
159    }
160
161    private static <T> void flushProtos(List<IpConnectivityEvent> out, SparseArray<T> in,
162            Function<T, IpConnectivityEvent> mapper) {
163        for (int i = 0; i < in.size(); i++) {
164            out.add(mapper.apply(in.valueAt(i)));
165        }
166        in.clear();
167    }
168
169    public static <T> void listEvents(
170            PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper) {
171        for (int i = 0; i < events.size(); i++) {
172            pw.println(mapper.apply(events.valueAt(i)).toString());
173        }
174    }
175
176    private ConnectStats makeConnectStats(int netId) {
177        long transports = getTransports(netId);
178        return new ConnectStats(netId, transports, mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS);
179    }
180
181    private DnsEvent makeDnsEvent(int netId) {
182        long transports = getTransports(netId);
183        return new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
184    }
185
186    private long getTransports(int netId) {
187        // TODO: directly query ConnectivityService instead of going through Binder interface.
188        NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
189        if (nc == null) {
190            return 0;
191        }
192        return BitUtils.packBits(nc.getTransportTypes());
193    }
194
195    private static void maybeLog(String s, Object... args) {
196        if (DBG) Log.d(TAG, String.format(s, args));
197    }
198
199    private static void maybeVerboseLog(String s, Object... args) {
200        if (VDBG) Log.d(TAG, String.format(s, args));
201    }
202}
203