• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.net;
18 
19 import static android.net.NetworkStats.INTERFACES_ALL;
20 import static android.net.NetworkStats.TAG_ALL;
21 import static android.net.NetworkStats.UID_ALL;
22 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
23 
24 import android.annotation.NonNull;
25 import android.content.Context;
26 import android.net.NetworkStats;
27 import android.net.UnderlyingNetworkInfo;
28 import android.os.ServiceSpecificException;
29 import android.os.SystemClock;
30 import android.util.ArraySet;
31 import android.util.IndentingPrintWriter;
32 import android.util.Log;
33 import android.util.Pair;
34 import android.util.SparseArray;
35 import android.util.SparseBooleanArray;
36 
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.net.module.util.DeviceConfigUtils;
40 import com.android.server.BpfNetMaps;
41 import com.android.server.connectivity.InterfaceTracker;
42 
43 import java.io.IOException;
44 import java.net.ProtocolException;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.concurrent.ConcurrentHashMap;
51 
52 /**
53  * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
54  * files as needed.
55  *
56  * @hide
57  */
58 public class NetworkStatsFactory {
59     static {
60         System.loadLibrary("service-connectivity");
61     }
62 
63     private static final String TAG = "NetworkStatsFactory";
64 
65     private final Context mContext;
66 
67     private final BpfNetMaps mBpfNetMaps;
68 
69     /**
70      * Guards persistent data access in this class
71      *
72      * <p>In order to prevent deadlocks, critical sections protected by this lock SHALL NOT call out
73      * to other code that will acquire other locks within the system server. See b/134244752.
74      */
75     private final Object mPersistentDataLock = new Object();
76 
77     /** Set containing info about active VPNs and their underlying networks. */
78     private volatile UnderlyingNetworkInfo[] mUnderlyingNetworkInfos = new UnderlyingNetworkInfo[0];
79 
80     static final String CONFIG_PER_UID_TAG_THROTTLING = "per_uid_tag_throttling";
81     static final String CONFIG_PER_UID_TAG_THROTTLING_THRESHOLD =
82             "per_uid_tag_throttling_threshold";
83     private static final int DEFAULT_TAGS_PER_UID_THRESHOLD = 1000;
84     private static final int DUMP_TAGS_PER_UID_COUNT = 20;
85     private final boolean mSupportPerUidTagThrottling;
86     private final int mPerUidTagThrottlingThreshold;
87 
88     // Map for set of distinct tags per uid. Used for tag count limiting.
89     @GuardedBy("mPersistentDataLock")
90     private final SparseArray<SparseBooleanArray> mUidTagSets = new SparseArray<>();
91 
92     // A persistent snapshot of cumulative stats since device start
93     @GuardedBy("mPersistentDataLock")
94     private NetworkStats mPersistSnapshot;
95 
96     // The persistent snapshot of tun and 464xlat adjusted stats since device start
97     @GuardedBy("mPersistentDataLock")
98     private NetworkStats mTunAnd464xlatAdjustedStats;
99 
100     private final Dependencies mDeps;
101     /**
102      * Dependencies of NetworkStatsFactory, for injection in tests.
103      */
104     @VisibleForTesting
105     public static class Dependencies {
106         /**
107          * Parse detailed statistics from bpf into given {@link NetworkStats} object. Values
108          * are expected to monotonically increase since device boot.
109          */
110         @NonNull
getNetworkStatsDetail()111         public NetworkStats getNetworkStatsDetail() throws IOException {
112             final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
113             final int ret = nativeReadNetworkStatsDetail(stats);
114             if (ret != 0) {
115                 throw new IOException("Failed to parse network stats");
116             }
117             return stats;
118         }
119         /**
120          * Parse device summary statistics from bpf into given {@link NetworkStats} object. Values
121          * are expected to monotonically increase since device boot.
122          */
123         @NonNull
getNetworkStatsDev()124         public NetworkStats getNetworkStatsDev() throws IOException {
125             final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
126             final int ret = nativeReadNetworkStatsDev(stats);
127             if (ret != 0) {
128                 throw new IOException("Failed to parse bpf iface stats");
129             }
130             return stats;
131         }
132 
133         /** Create a new {@link BpfNetMaps}. */
createBpfNetMaps(@onNull Context ctx)134         public BpfNetMaps createBpfNetMaps(@NonNull Context ctx) {
135             return new BpfNetMaps(ctx, new InterfaceTracker(ctx));
136         }
137 
138         /**
139          * Check whether one specific feature is not disabled.
140          * @param name Flag name of the experiment in the tethering namespace.
141          * @see DeviceConfigUtils#isTetheringFeatureNotChickenedOut(Context, String)
142          */
isFeatureNotChickenedOut(@onNull Context context, @NonNull String name)143         public boolean isFeatureNotChickenedOut(@NonNull Context context, @NonNull String name) {
144             return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(context, name);
145         }
146 
147         /**
148          * Wrapper method for DeviceConfigUtils#getDeviceConfigPropertyInt for test injections.
149          *
150          * See {@link DeviceConfigUtils#getDeviceConfigPropertyInt(String, String, int)}
151          * for more detailed information.
152          */
getDeviceConfigPropertyInt(@onNull String name, int defaultValue)153         public int getDeviceConfigPropertyInt(@NonNull String name, int defaultValue) {
154             return DeviceConfigUtils.getDeviceConfigPropertyInt(
155                     NAMESPACE_TETHERING, name, defaultValue);
156         }
157     }
158 
159     /**
160      * (Stacked interface) -> (base interface) association for all connected ifaces since boot.
161      *
162      * Because counters must never roll backwards, once a given interface is stacked on top of an
163      * underlying interface, the stacked interface can never be stacked on top of
164      * another interface. */
165     private final ConcurrentHashMap<String, String> mStackedIfaces
166             = new ConcurrentHashMap<>();
167 
168     /** Informs the factory of a new stacked interface. */
noteStackedIface(String stackedIface, String baseIface)169     public void noteStackedIface(String stackedIface, String baseIface) {
170         if (stackedIface != null && baseIface != null) {
171             mStackedIfaces.put(stackedIface, baseIface);
172         }
173     }
174 
175     /**
176      * Set active VPN information for data usage migration purposes
177      *
178      * <p>Traffic on TUN-based VPNs inherently all appear to be originated from the VPN providing
179      * app's UID. This method is used to support migration of VPN data usage, ensuring data is
180      * accurately billed to the real owner of the traffic.
181      *
182      * @param vpnArray The snapshot of the currently-running VPNs.
183      */
updateUnderlyingNetworkInfos(UnderlyingNetworkInfo[] vpnArray)184     public void updateUnderlyingNetworkInfos(UnderlyingNetworkInfo[] vpnArray) {
185         mUnderlyingNetworkInfos = vpnArray.clone();
186     }
187 
188     /**
189      * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
190      * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
191      */
apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic)192     public void apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic) {
193         NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces);
194     }
195 
NetworkStatsFactory(@onNull Context ctx)196     public NetworkStatsFactory(@NonNull Context ctx) {
197         this(ctx, new Dependencies());
198     }
199 
200     @VisibleForTesting
NetworkStatsFactory(@onNull Context ctx, Dependencies deps)201     public NetworkStatsFactory(@NonNull Context ctx, Dependencies deps) {
202         mBpfNetMaps = deps.createBpfNetMaps(ctx);
203         synchronized (mPersistentDataLock) {
204             mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
205             mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
206         }
207         mContext = ctx;
208         mDeps = deps;
209         mSupportPerUidTagThrottling = mDeps.isFeatureNotChickenedOut(
210             ctx, CONFIG_PER_UID_TAG_THROTTLING);
211         mPerUidTagThrottlingThreshold = mDeps.getDeviceConfigPropertyInt(
212                 CONFIG_PER_UID_TAG_THROTTLING_THRESHOLD, DEFAULT_TAGS_PER_UID_THRESHOLD);
213     }
214 
215     /**
216      * Parse and return interface-level summary {@link NetworkStats}. Designed
217      * to return only IP layer traffic. Values monotonically increase since
218      * device boot, and may include details about inactive interfaces.
219      */
readNetworkStatsSummaryXt()220     public NetworkStats readNetworkStatsSummaryXt() throws IOException {
221         return mDeps.getNetworkStatsDev();
222     }
223 
readNetworkStatsDetail()224     public NetworkStats readNetworkStatsDetail() throws IOException {
225         return readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
226     }
227 
228     @GuardedBy("mPersistentDataLock")
requestSwapActiveStatsMapLocked()229     private void requestSwapActiveStatsMapLocked() throws IOException {
230         try {
231             // Do a active map stats swap. Once the swap completes, this code
232             // can read and clean the inactive map without races.
233             mBpfNetMaps.swapActiveStatsMap();
234         } catch (ServiceSpecificException e) {
235             throw new IOException(e);
236         }
237     }
238 
239     /**
240      * Reads the detailed UID stats based on the provided parameters
241      *
242      * @param limitUid the UID to limit this query to
243      * @param limitIfaces the interfaces to limit this query to. Use {@link
244      *     NetworkStats.INTERFACES_ALL} to select all interfaces
245      * @param limitTag the tags to limit this query to
246      * @return the NetworkStats instance containing network statistics at the present time.
247      */
readNetworkStatsDetail( int limitUid, String[] limitIfaces, int limitTag)248     public NetworkStats readNetworkStatsDetail(
249             int limitUid, String[] limitIfaces, int limitTag) throws IOException {
250         // In order to prevent deadlocks, anything protected by this lock MUST NOT call out to other
251         // code that will acquire other locks within the system server. See b/134244752.
252         synchronized (mPersistentDataLock) {
253             // Take a reference. If this gets swapped out, we still have the old reference.
254             final UnderlyingNetworkInfo[] vpnArray = mUnderlyingNetworkInfos;
255             // Take a defensive copy. mPersistSnapshot is mutated in some cases below
256             final NetworkStats prev = mPersistSnapshot.clone();
257 
258             requestSwapActiveStatsMapLocked();
259             // Stats are always read from the inactive map, so they must be read after the
260             // swap
261             final NetworkStats diff = mDeps.getNetworkStatsDetail();
262             // Filter based on UID tag set before merging.
263             final NetworkStats filteredDiff = mSupportPerUidTagThrottling
264                     ? filterStatsByUidTagSets(diff) : diff;
265             // BPF stats are incremental; fold into mPersistSnapshot.
266             mPersistSnapshot.setElapsedRealtime(diff.getElapsedRealtime());
267             mPersistSnapshot.combineAllValues(filteredDiff);
268 
269             NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray);
270 
271             // Filter return values
272             adjustedStats.filter(limitUid, limitIfaces, limitTag);
273             return adjustedStats;
274         }
275     }
276 
277     @GuardedBy("mPersistentDataLock")
filterStatsByUidTagSets(NetworkStats stats)278     private NetworkStats filterStatsByUidTagSets(NetworkStats stats) {
279         final NetworkStats filteredStats =
280                 new NetworkStats(stats.getElapsedRealtime(), stats.size());
281 
282         final NetworkStats.Entry entry = new NetworkStats.Entry();
283         final Set<Integer> tooManyTagsUidSet = new ArraySet<>();
284         for (int i = 0; i < stats.size(); i++) {
285             stats.getValues(i, entry);
286             final int uid = entry.uid;
287             final int tag = entry.tag;
288 
289             if (tag == NetworkStats.TAG_NONE) {
290                 filteredStats.combineValues(entry);
291                 continue;
292             }
293 
294             SparseBooleanArray tagSet = mUidTagSets.get(uid);
295             if (tagSet == null) {
296                 tagSet = new SparseBooleanArray();
297             }
298             if (tagSet.size() < mPerUidTagThrottlingThreshold || tagSet.get(tag)) {
299                 filteredStats.combineValues(entry);
300                 tagSet.put(tag, true);
301                 mUidTagSets.put(uid, tagSet);
302             } else {
303                 tooManyTagsUidSet.add(uid);
304             }
305         }
306         if (tooManyTagsUidSet.size() > 0) {
307             Log.wtf(TAG, "Too many tags detected for uids: " + tooManyTagsUidSet);
308         }
309         return filteredStats;
310     }
311 
312     @GuardedBy("mPersistentDataLock")
adjustForTunAnd464Xlat(NetworkStats uidDetailStats, NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray)313     private NetworkStats adjustForTunAnd464Xlat(NetworkStats uidDetailStats,
314             NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray) {
315         // Calculate delta from last snapshot
316         final NetworkStats delta = uidDetailStats.subtract(previousStats);
317 
318         // Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only
319         // network, the overhead is their fault.
320         // No locking here: apply464xlatAdjustments behaves fine with an add-only
321         // ConcurrentHashMap.
322         delta.apply464xlatAdjustments(mStackedIfaces);
323 
324         // Migrate data usage over a VPN to the TUN network.
325         for (UnderlyingNetworkInfo info : vpnArray) {
326             delta.migrateTun(info.getOwnerUid(), info.getInterface(),
327                     info.getUnderlyingInterfaces());
328             // Filter out debug entries as that may lead to over counting.
329             delta.filterDebugEntries();
330         }
331 
332         // Update mTunAnd464xlatAdjustedStats with migrated delta.
333         mTunAnd464xlatAdjustedStats.combineAllValues(delta);
334         mTunAnd464xlatAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
335 
336         return mTunAnd464xlatAdjustedStats.clone();
337     }
338 
339     /**
340      * Remove stats from {@code mPersistSnapshot} and {@code mTunAnd464xlatAdjustedStats} for the
341      * given uids.
342      */
removeUidsLocked(int[] uids)343     public void removeUidsLocked(int[] uids) {
344         synchronized (mPersistentDataLock) {
345             mPersistSnapshot.removeUids(uids);
346             mTunAnd464xlatAdjustedStats.removeUids(uids);
347         }
348     }
349 
assertEquals(NetworkStats expected, NetworkStats actual)350     public void assertEquals(NetworkStats expected, NetworkStats actual) {
351         if (expected.size() != actual.size()) {
352             throw new AssertionError(
353                     "Expected size " + expected.size() + ", actual size " + actual.size());
354         }
355 
356         NetworkStats.Entry expectedRow = null;
357         NetworkStats.Entry actualRow = null;
358         for (int i = 0; i < expected.size(); i++) {
359             expectedRow = expected.getValues(i, expectedRow);
360             actualRow = actual.getValues(i, actualRow);
361             if (!expectedRow.equals(actualRow)) {
362                 throw new AssertionError(
363                         "Expected row " + i + ": " + expectedRow + ", actual row " + actualRow);
364             }
365         }
366     }
367 
368     /**
369      * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
370      * format like {@code 0x7fffffff00000000}.
371      */
kernelToTag(String string)372     public static int kernelToTag(String string) {
373         int length = string.length();
374         if (length > 10) {
375             return Long.decode(string.substring(0, length - 8)).intValue();
376         } else {
377             return 0;
378         }
379     }
380 
381     /**
382      * Parse statistics from file into given {@link NetworkStats} object. Values
383      * are expected to monotonically increase since device boot.
384      */
385     @VisibleForTesting
nativeReadNetworkStatsDetail(NetworkStats stats)386     public static native int nativeReadNetworkStatsDetail(NetworkStats stats);
387 
388     @VisibleForTesting
nativeReadNetworkStatsDev(NetworkStats stats)389     public static native int nativeReadNetworkStatsDev(NetworkStats stats);
390 
protocolExceptionWithCause(String message, Throwable cause)391     private static ProtocolException protocolExceptionWithCause(String message, Throwable cause) {
392         ProtocolException pe = new ProtocolException(message);
393         pe.initCause(cause);
394         return pe;
395     }
396 
397     /**
398      * Dump the contents of NetworkStatsFactory.
399      */
dump(IndentingPrintWriter pw)400     public void dump(IndentingPrintWriter pw) {
401         dumpUidTagSets(pw);
402     }
403 
dumpUidTagSets(IndentingPrintWriter pw)404     private void dumpUidTagSets(IndentingPrintWriter pw) {
405         pw.println("Top distinct tag counts in UidTagSets:");
406         pw.increaseIndent();
407         final List<Pair<Integer, Integer>> countForUidList = new ArrayList<>();
408         synchronized (mPersistentDataLock) {
409             for (int i = 0; i < mUidTagSets.size(); i++) {
410                 final Pair<Integer, Integer> countForUid =
411                         new Pair<>(mUidTagSets.keyAt(i), mUidTagSets.valueAt(i).size());
412                 countForUidList.add(countForUid);
413             }
414         }
415         Collections.sort(countForUidList,
416                 (entry1, entry2) -> Integer.compare(entry2.second, entry1.second));
417         final int dumpSize = Math.min(countForUidList.size(), DUMP_TAGS_PER_UID_COUNT);
418         for (int j = 0; j < dumpSize; j++) {
419             final Pair<Integer, Integer> entry = countForUidList.get(j);
420             pw.print(entry.first);
421             pw.print("=");
422             pw.println(entry.second);
423         }
424         pw.decreaseIndent();
425     }
426 }
427