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.SET_ALL;
21 import static android.net.NetworkStats.TAG_ALL;
22 import static android.net.NetworkStats.TAG_NONE;
23 import static android.net.NetworkStats.UID_ALL;
24 
25 import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
26 
27 import android.annotation.Nullable;
28 import android.net.INetd;
29 import android.net.NetworkStats;
30 import android.net.util.NetdService;
31 import android.os.RemoteException;
32 import android.os.StrictMode;
33 import android.os.SystemClock;
34 
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.net.VpnInfo;
38 import com.android.internal.util.ArrayUtils;
39 import com.android.internal.util.ProcFileReader;
40 
41 import libcore.io.IoUtils;
42 
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.IOException;
46 import java.net.ProtocolException;
47 import java.util.Arrays;
48 import java.util.HashSet;
49 import java.util.Map;
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     private static final String TAG = "NetworkStatsFactory";
60 
61     private static final boolean USE_NATIVE_PARSING = true;
62     private static final boolean VALIDATE_NATIVE_STATS = false;
63 
64     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
65     private final File mStatsXtIfaceAll;
66     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */
67     private final File mStatsXtIfaceFmt;
68     /** Path to {@code /proc/net/xt_qtaguid/stats}. */
69     private final File mStatsXtUid;
70 
71     private final boolean mUseBpfStats;
72 
73     private INetd mNetdService;
74 
75     /**
76      * Guards persistent data access in this class
77      *
78      * <p>In order to prevent deadlocks, critical sections protected by this lock SHALL NOT call out
79      * to other code that will acquire other locks within the system server. See b/134244752.
80      */
81     private final Object mPersistentDataLock = new Object();
82 
83     /** Set containing info about active VPNs and their underlying networks. */
84     private volatile VpnInfo[] mVpnInfos = new VpnInfo[0];
85 
86     // A persistent snapshot of cumulative stats since device start
87     @GuardedBy("mPersistentDataLock")
88     private NetworkStats mPersistSnapshot;
89 
90     // The persistent snapshot of tun and 464xlat adjusted stats since device start
91     @GuardedBy("mPersistentDataLock")
92     private NetworkStats mTunAnd464xlatAdjustedStats;
93 
94     /**
95      * (Stacked interface) -> (base interface) association for all connected ifaces since boot.
96      *
97      * Because counters must never roll backwards, once a given interface is stacked on top of an
98      * underlying interface, the stacked interface can never be stacked on top of
99      * another interface. */
100     private final ConcurrentHashMap<String, String> mStackedIfaces
101             = new ConcurrentHashMap<>();
102 
103     /** Informs the factory of a new stacked interface. */
noteStackedIface(String stackedIface, String baseIface)104     public void noteStackedIface(String stackedIface, String baseIface) {
105         if (stackedIface != null && baseIface != null) {
106             mStackedIfaces.put(stackedIface, baseIface);
107         }
108     }
109 
110     /**
111      * Set active VPN information for data usage migration purposes
112      *
113      * <p>Traffic on TUN-based VPNs inherently all appear to be originated from the VPN providing
114      * app's UID. This method is used to support migration of VPN data usage, ensuring data is
115      * accurately billed to the real owner of the traffic.
116      *
117      * @param vpnArray The snapshot of the currently-running VPNs.
118      */
updateVpnInfos(VpnInfo[] vpnArray)119     public void updateVpnInfos(VpnInfo[] vpnArray) {
120         mVpnInfos = vpnArray.clone();
121     }
122 
123     /**
124      * Get a set of interfaces containing specified ifaces and stacked interfaces.
125      *
126      * <p>The added stacked interfaces are ifaces stacked on top of the specified ones, or ifaces
127      * on which the specified ones are stacked. Stacked interfaces are those noted with
128      * {@link #noteStackedIface(String, String)}, but only interfaces noted before this method
129      * is called are guaranteed to be included.
130      */
augmentWithStackedInterfaces(@ullable String[] requiredIfaces)131     public String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
132         if (requiredIfaces == NetworkStats.INTERFACES_ALL) {
133             return null;
134         }
135 
136         HashSet<String> relatedIfaces = new HashSet<>(Arrays.asList(requiredIfaces));
137         // ConcurrentHashMap's EntrySet iterators are "guaranteed to traverse
138         // elements as they existed upon construction exactly once, and may
139         // (but are not guaranteed to) reflect any modifications subsequent to construction".
140         // This is enough here.
141         for (Map.Entry<String, String> entry : mStackedIfaces.entrySet()) {
142             if (relatedIfaces.contains(entry.getKey())) {
143                 relatedIfaces.add(entry.getValue());
144             } else if (relatedIfaces.contains(entry.getValue())) {
145                 relatedIfaces.add(entry.getKey());
146             }
147         }
148 
149         String[] outArray = new String[relatedIfaces.size()];
150         return relatedIfaces.toArray(outArray);
151     }
152 
153     /**
154      * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
155      * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
156      */
apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic)157     public void apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic) {
158         NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces);
159     }
160 
NetworkStatsFactory()161     public NetworkStatsFactory() {
162         this(new File("/proc/"), new File("/sys/fs/bpf/map_netd_app_uid_stats_map").exists());
163     }
164 
165     @VisibleForTesting
NetworkStatsFactory(File procRoot, boolean useBpfStats)166     public NetworkStatsFactory(File procRoot, boolean useBpfStats) {
167         mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
168         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
169         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
170         mUseBpfStats = useBpfStats;
171         synchronized (mPersistentDataLock) {
172             mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
173             mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
174         }
175     }
176 
readBpfNetworkStatsDev()177     public NetworkStats readBpfNetworkStatsDev() throws IOException {
178         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
179         if (nativeReadNetworkStatsDev(stats) != 0) {
180             throw new IOException("Failed to parse bpf iface stats");
181         }
182         return stats;
183     }
184 
185     /**
186      * Parse and return interface-level summary {@link NetworkStats} measured
187      * using {@code /proc/net/dev} style hooks, which may include non IP layer
188      * traffic. Values monotonically increase since device boot, and may include
189      * details about inactive interfaces.
190      *
191      * @throws IllegalStateException when problem parsing stats.
192      */
readNetworkStatsSummaryDev()193     public NetworkStats readNetworkStatsSummaryDev() throws IOException {
194 
195         // Return xt_bpf stats if switched to bpf module.
196         if (mUseBpfStats)
197             return readBpfNetworkStatsDev();
198 
199         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
200 
201         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
202         final NetworkStats.Entry entry = new NetworkStats.Entry();
203 
204         ProcFileReader reader = null;
205         try {
206             reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceAll));
207 
208             while (reader.hasMoreData()) {
209                 entry.iface = reader.nextString();
210                 entry.uid = UID_ALL;
211                 entry.set = SET_ALL;
212                 entry.tag = TAG_NONE;
213 
214                 final boolean active = reader.nextInt() != 0;
215 
216                 // always include snapshot values
217                 entry.rxBytes = reader.nextLong();
218                 entry.rxPackets = reader.nextLong();
219                 entry.txBytes = reader.nextLong();
220                 entry.txPackets = reader.nextLong();
221 
222                 // fold in active numbers, but only when active
223                 if (active) {
224                     entry.rxBytes += reader.nextLong();
225                     entry.rxPackets += reader.nextLong();
226                     entry.txBytes += reader.nextLong();
227                     entry.txPackets += reader.nextLong();
228                 }
229 
230                 stats.insertEntry(entry);
231                 reader.finishLine();
232             }
233         } catch (NullPointerException|NumberFormatException e) {
234             throw protocolExceptionWithCause("problem parsing stats", e);
235         } finally {
236             IoUtils.closeQuietly(reader);
237             StrictMode.setThreadPolicy(savedPolicy);
238         }
239         return stats;
240     }
241 
242     /**
243      * Parse and return interface-level summary {@link NetworkStats}. Designed
244      * to return only IP layer traffic. Values monotonically increase since
245      * device boot, and may include details about inactive interfaces.
246      *
247      * @throws IllegalStateException when problem parsing stats.
248      */
readNetworkStatsSummaryXt()249     public NetworkStats readNetworkStatsSummaryXt() throws IOException {
250 
251         // Return xt_bpf stats if qtaguid  module is replaced.
252         if (mUseBpfStats)
253             return readBpfNetworkStatsDev();
254 
255         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
256 
257         // return null when kernel doesn't support
258         if (!mStatsXtIfaceFmt.exists()) return null;
259 
260         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
261         final NetworkStats.Entry entry = new NetworkStats.Entry();
262 
263         ProcFileReader reader = null;
264         try {
265             // open and consume header line
266             reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceFmt));
267             reader.finishLine();
268 
269             while (reader.hasMoreData()) {
270                 entry.iface = reader.nextString();
271                 entry.uid = UID_ALL;
272                 entry.set = SET_ALL;
273                 entry.tag = TAG_NONE;
274 
275                 entry.rxBytes = reader.nextLong();
276                 entry.rxPackets = reader.nextLong();
277                 entry.txBytes = reader.nextLong();
278                 entry.txPackets = reader.nextLong();
279 
280                 stats.insertEntry(entry);
281                 reader.finishLine();
282             }
283         } catch (NullPointerException|NumberFormatException e) {
284             throw protocolExceptionWithCause("problem parsing stats", e);
285         } finally {
286             IoUtils.closeQuietly(reader);
287             StrictMode.setThreadPolicy(savedPolicy);
288         }
289         return stats;
290     }
291 
readNetworkStatsDetail()292     public NetworkStats readNetworkStatsDetail() throws IOException {
293         return readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
294     }
295 
296     @GuardedBy("mPersistentDataLock")
requestSwapActiveStatsMapLocked()297     private void requestSwapActiveStatsMapLocked() throws RemoteException {
298         // Ask netd to do a active map stats swap. When the binder call successfully returns,
299         // the system server should be able to safely read and clean the inactive map
300         // without race problem.
301         if (mNetdService == null) {
302             mNetdService = NetdService.getInstance();
303         }
304         mNetdService.trafficSwapActiveStatsMap();
305     }
306 
307     /**
308      * Reads the detailed UID stats based on the provided parameters
309      *
310      * @param limitUid the UID to limit this query to
311      * @param limitIfaces the interfaces to limit this query to. Use {@link
312      *     NetworkStats.INTERFACES_ALL} to select all interfaces
313      * @param limitTag the tags to limit this query to
314      * @return the NetworkStats instance containing network statistics at the present time.
315      */
readNetworkStatsDetail( int limitUid, String[] limitIfaces, int limitTag)316     public NetworkStats readNetworkStatsDetail(
317             int limitUid, String[] limitIfaces, int limitTag) throws IOException {
318         // In order to prevent deadlocks, anything protected by this lock MUST NOT call out to other
319         // code that will acquire other locks within the system server. See b/134244752.
320         synchronized (mPersistentDataLock) {
321             // Take a reference. If this gets swapped out, we still have the old reference.
322             final VpnInfo[] vpnArray = mVpnInfos;
323             // Take a defensive copy. mPersistSnapshot is mutated in some cases below
324             final NetworkStats prev = mPersistSnapshot.clone();
325 
326             if (USE_NATIVE_PARSING) {
327                 final NetworkStats stats =
328                         new NetworkStats(SystemClock.elapsedRealtime(), 0 /* initialSize */);
329                 if (mUseBpfStats) {
330                     try {
331                         requestSwapActiveStatsMapLocked();
332                     } catch (RemoteException e) {
333                         throw new IOException(e);
334                     }
335                     // Stats are always read from the inactive map, so they must be read after the
336                     // swap
337                     if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
338                             INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
339                         throw new IOException("Failed to parse network stats");
340                     }
341 
342                     // BPF stats are incremental; fold into mPersistSnapshot.
343                     mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
344                     mPersistSnapshot.combineAllValues(stats);
345                 } else {
346                     if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
347                             INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
348                         throw new IOException("Failed to parse network stats");
349                     }
350                     if (VALIDATE_NATIVE_STATS) {
351                         final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid,
352                                 UID_ALL, INTERFACES_ALL, TAG_ALL);
353                         assertEquals(javaStats, stats);
354                     }
355 
356                     mPersistSnapshot = stats;
357                 }
358             } else {
359                 mPersistSnapshot = javaReadNetworkStatsDetail(mStatsXtUid, UID_ALL, INTERFACES_ALL,
360                         TAG_ALL);
361             }
362 
363             NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray);
364 
365             // Filter return values
366             adjustedStats.filter(limitUid, limitIfaces, limitTag);
367             return adjustedStats;
368         }
369     }
370 
371     @GuardedBy("mPersistentDataLock")
adjustForTunAnd464Xlat( NetworkStats uidDetailStats, NetworkStats previousStats, VpnInfo[] vpnArray)372     private NetworkStats adjustForTunAnd464Xlat(
373             NetworkStats uidDetailStats, NetworkStats previousStats, VpnInfo[] vpnArray) {
374         // Calculate delta from last snapshot
375         final NetworkStats delta = uidDetailStats.subtract(previousStats);
376 
377         // Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only
378         // network, the overhead is their fault.
379         // No locking here: apply464xlatAdjustments behaves fine with an add-only
380         // ConcurrentHashMap.
381         delta.apply464xlatAdjustments(mStackedIfaces);
382 
383         // Migrate data usage over a VPN to the TUN network.
384         for (VpnInfo info : vpnArray) {
385             delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
386             // Filter out debug entries as that may lead to over counting.
387             delta.filterDebugEntries();
388         }
389 
390         // Update mTunAnd464xlatAdjustedStats with migrated delta.
391         mTunAnd464xlatAdjustedStats.combineAllValues(delta);
392         mTunAnd464xlatAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
393 
394         return mTunAnd464xlatAdjustedStats.clone();
395     }
396 
397     /**
398      * Parse and return {@link NetworkStats} with UID-level details. Values are
399      * expected to monotonically increase since device boot.
400      */
401     @VisibleForTesting
javaReadNetworkStatsDetail(File detailPath, int limitUid, String[] limitIfaces, int limitTag)402     public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid,
403             String[] limitIfaces, int limitTag)
404             throws IOException {
405         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
406 
407         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
408         final NetworkStats.Entry entry = new NetworkStats.Entry();
409 
410         int idx = 1;
411         int lastIdx = 1;
412 
413         ProcFileReader reader = null;
414         try {
415             // open and consume header line
416             reader = new ProcFileReader(new FileInputStream(detailPath));
417             reader.finishLine();
418 
419             while (reader.hasMoreData()) {
420                 idx = reader.nextInt();
421                 if (idx != lastIdx + 1) {
422                     throw new ProtocolException(
423                             "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
424                 }
425                 lastIdx = idx;
426 
427                 entry.iface = reader.nextString();
428                 entry.tag = kernelToTag(reader.nextString());
429                 entry.uid = reader.nextInt();
430                 entry.set = reader.nextInt();
431                 entry.rxBytes = reader.nextLong();
432                 entry.rxPackets = reader.nextLong();
433                 entry.txBytes = reader.nextLong();
434                 entry.txPackets = reader.nextLong();
435 
436                 if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
437                         && (limitUid == UID_ALL || limitUid == entry.uid)
438                         && (limitTag == TAG_ALL || limitTag == entry.tag)) {
439                     stats.insertEntry(entry);
440                 }
441 
442                 reader.finishLine();
443             }
444         } catch (NullPointerException|NumberFormatException e) {
445             throw protocolExceptionWithCause("problem parsing idx " + idx, e);
446         } finally {
447             IoUtils.closeQuietly(reader);
448             StrictMode.setThreadPolicy(savedPolicy);
449         }
450 
451         return stats;
452     }
453 
assertEquals(NetworkStats expected, NetworkStats actual)454     public void assertEquals(NetworkStats expected, NetworkStats actual) {
455         if (expected.size() != actual.size()) {
456             throw new AssertionError(
457                     "Expected size " + expected.size() + ", actual size " + actual.size());
458         }
459 
460         NetworkStats.Entry expectedRow = null;
461         NetworkStats.Entry actualRow = null;
462         for (int i = 0; i < expected.size(); i++) {
463             expectedRow = expected.getValues(i, expectedRow);
464             actualRow = actual.getValues(i, actualRow);
465             if (!expectedRow.equals(actualRow)) {
466                 throw new AssertionError(
467                         "Expected row " + i + ": " + expectedRow + ", actual row " + actualRow);
468             }
469         }
470     }
471 
472     /**
473      * Parse statistics from file into given {@link NetworkStats} object. Values
474      * are expected to monotonically increase since device boot.
475      */
476     @VisibleForTesting
nativeReadNetworkStatsDetail(NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats)477     public static native int nativeReadNetworkStatsDetail(NetworkStats stats, String path,
478         int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats);
479 
480     @VisibleForTesting
nativeReadNetworkStatsDev(NetworkStats stats)481     public static native int nativeReadNetworkStatsDev(NetworkStats stats);
482 
protocolExceptionWithCause(String message, Throwable cause)483     private static ProtocolException protocolExceptionWithCause(String message, Throwable cause) {
484         ProtocolException pe = new ProtocolException(message);
485         pe.initCause(cause);
486         return pe;
487     }
488 }
489