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.internal.net;
18 
19 import static android.net.NetworkStats.SET_ALL;
20 import static android.net.NetworkStats.TAG_ALL;
21 import static android.net.NetworkStats.TAG_NONE;
22 import static android.net.NetworkStats.UID_ALL;
23 import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
24 
25 import android.net.NetworkStats;
26 import android.os.StrictMode;
27 import android.os.SystemClock;
28 import android.util.ArrayMap;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.internal.util.ArrayUtils;
33 import com.android.internal.util.ProcFileReader;
34 
35 import libcore.io.IoUtils;
36 
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.IOException;
40 import java.net.ProtocolException;
41 import java.util.Objects;
42 
43 /**
44  * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
45  * files as needed.
46  */
47 public class NetworkStatsFactory {
48     private static final String TAG = "NetworkStatsFactory";
49 
50     private static final boolean USE_NATIVE_PARSING = true;
51     private static final boolean SANITY_CHECK_NATIVE = false;
52 
53     private static final String CLATD_INTERFACE_PREFIX = "v4-";
54     // Delta between IPv4 header (20b) and IPv6 header (40b).
55     // Used for correct stats accounting on clatd interfaces.
56     private static final int IPV4V6_HEADER_DELTA = 20;
57 
58     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
59     private final File mStatsXtIfaceAll;
60     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */
61     private final File mStatsXtIfaceFmt;
62     /** Path to {@code /proc/net/xt_qtaguid/stats}. */
63     private final File mStatsXtUid;
64 
65     // TODO: to improve testability and avoid global state, do not use a static variable.
66     @GuardedBy("sStackedIfaces")
67     private static final ArrayMap<String, String> sStackedIfaces = new ArrayMap<>();
68 
noteStackedIface(String stackedIface, String baseIface)69     public static void noteStackedIface(String stackedIface, String baseIface) {
70         synchronized (sStackedIfaces) {
71             if (baseIface != null) {
72                 sStackedIfaces.put(stackedIface, baseIface);
73             } else {
74                 sStackedIfaces.remove(stackedIface);
75             }
76         }
77     }
78 
NetworkStatsFactory()79     public NetworkStatsFactory() {
80         this(new File("/proc/"));
81     }
82 
83     @VisibleForTesting
NetworkStatsFactory(File procRoot)84     public NetworkStatsFactory(File procRoot) {
85         mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
86         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
87         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
88     }
89 
90     /**
91      * Parse and return interface-level summary {@link NetworkStats} measured
92      * using {@code /proc/net/dev} style hooks, which may include non IP layer
93      * traffic. Values monotonically increase since device boot, and may include
94      * details about inactive interfaces.
95      *
96      * @throws IllegalStateException when problem parsing stats.
97      */
readNetworkStatsSummaryDev()98     public NetworkStats readNetworkStatsSummaryDev() throws IOException {
99         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
100 
101         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
102         final NetworkStats.Entry entry = new NetworkStats.Entry();
103 
104         ProcFileReader reader = null;
105         try {
106             reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceAll));
107 
108             while (reader.hasMoreData()) {
109                 entry.iface = reader.nextString();
110                 entry.uid = UID_ALL;
111                 entry.set = SET_ALL;
112                 entry.tag = TAG_NONE;
113 
114                 final boolean active = reader.nextInt() != 0;
115 
116                 // always include snapshot values
117                 entry.rxBytes = reader.nextLong();
118                 entry.rxPackets = reader.nextLong();
119                 entry.txBytes = reader.nextLong();
120                 entry.txPackets = reader.nextLong();
121 
122                 // fold in active numbers, but only when active
123                 if (active) {
124                     entry.rxBytes += reader.nextLong();
125                     entry.rxPackets += reader.nextLong();
126                     entry.txBytes += reader.nextLong();
127                     entry.txPackets += reader.nextLong();
128                 }
129 
130                 stats.addValues(entry);
131                 reader.finishLine();
132             }
133         } catch (NullPointerException|NumberFormatException e) {
134             throw new ProtocolException("problem parsing stats", e);
135         } finally {
136             IoUtils.closeQuietly(reader);
137             StrictMode.setThreadPolicy(savedPolicy);
138         }
139         return stats;
140     }
141 
142     /**
143      * Parse and return interface-level summary {@link NetworkStats}. Designed
144      * to return only IP layer traffic. Values monotonically increase since
145      * device boot, and may include details about inactive interfaces.
146      *
147      * @throws IllegalStateException when problem parsing stats.
148      */
readNetworkStatsSummaryXt()149     public NetworkStats readNetworkStatsSummaryXt() throws IOException {
150         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
151 
152         // return null when kernel doesn't support
153         if (!mStatsXtIfaceFmt.exists()) return null;
154 
155         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
156         final NetworkStats.Entry entry = new NetworkStats.Entry();
157 
158         ProcFileReader reader = null;
159         try {
160             // open and consume header line
161             reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceFmt));
162             reader.finishLine();
163 
164             while (reader.hasMoreData()) {
165                 entry.iface = reader.nextString();
166                 entry.uid = UID_ALL;
167                 entry.set = SET_ALL;
168                 entry.tag = TAG_NONE;
169 
170                 entry.rxBytes = reader.nextLong();
171                 entry.rxPackets = reader.nextLong();
172                 entry.txBytes = reader.nextLong();
173                 entry.txPackets = reader.nextLong();
174 
175                 stats.addValues(entry);
176                 reader.finishLine();
177             }
178         } catch (NullPointerException|NumberFormatException e) {
179             throw new ProtocolException("problem parsing stats", e);
180         } finally {
181             IoUtils.closeQuietly(reader);
182             StrictMode.setThreadPolicy(savedPolicy);
183         }
184         return stats;
185     }
186 
readNetworkStatsDetail()187     public NetworkStats readNetworkStatsDetail() throws IOException {
188         return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
189     }
190 
readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag, NetworkStats lastStats)191     public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag,
192             NetworkStats lastStats) throws IOException {
193         final NetworkStats stats =
194               readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag, lastStats);
195         NetworkStats.Entry entry = null; // for recycling
196 
197         synchronized (sStackedIfaces) {
198             // For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4
199             // packet on the stacked interface, and once as translated to an IPv6 packet on the
200             // base interface. For correct stats accounting on the base interface, every 464xlat
201             // packet needs to be subtracted from the root UID on the base interface both for tx
202             // and rx traffic (http://b/12249687, http:/b/33681750).
203             final int size = sStackedIfaces.size();
204             for (int i = 0; i < size; i++) {
205                 final String stackedIface = sStackedIfaces.keyAt(i);
206                 final String baseIface = sStackedIfaces.valueAt(i);
207                 if (!stackedIface.startsWith(CLATD_INTERFACE_PREFIX)) {
208                     continue;
209                 }
210 
211                 NetworkStats.Entry adjust =
212                     new NetworkStats.Entry(baseIface, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);
213                 for (int j = 0; j < stats.size(); j++) {
214                     entry = stats.getValues(j, entry);
215                     if (Objects.equals(entry.iface, stackedIface)) {
216                         adjust.rxBytes -= (entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
217                         adjust.txBytes -= (entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
218                         adjust.rxPackets -= entry.rxPackets;
219                         adjust.txPackets -= entry.txPackets;
220                     }
221                 }
222                 stats.combineValues(adjust);
223             }
224         }
225 
226         // For 464xlat traffic, xt_qtaguid only counts the bytes of the inner IPv4 packet sent on
227         // the stacked interface with prefix "v4-" and drops the IPv6 header size after unwrapping.
228         // To account correctly for on-the-wire traffic, add the 20 additional bytes difference
229         // for all packets (http://b/12249687, http:/b/33681750).
230         for (int i = 0; i < stats.size(); i++) {
231             entry = stats.getValues(i, entry);
232             if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) {
233                 continue;
234             }
235             entry.rxBytes = entry.rxPackets * IPV4V6_HEADER_DELTA;
236             entry.txBytes = entry.txPackets * IPV4V6_HEADER_DELTA;
237             entry.rxPackets = 0;
238             entry.txPackets = 0;
239             stats.combineValues(entry);
240         }
241 
242         return stats;
243     }
244 
readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces, int limitTag, NetworkStats lastStats)245     private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces,
246             int limitTag, NetworkStats lastStats) throws IOException {
247         if (USE_NATIVE_PARSING) {
248             final NetworkStats stats;
249             if (lastStats != null) {
250                 stats = lastStats;
251                 stats.setElapsedRealtime(SystemClock.elapsedRealtime());
252             } else {
253                 stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
254             }
255             if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
256                     limitIfaces, limitTag) != 0) {
257                 throw new IOException("Failed to parse network stats");
258             }
259             if (SANITY_CHECK_NATIVE) {
260                 final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
261                         limitIfaces, limitTag);
262                 assertEquals(javaStats, stats);
263             }
264             return stats;
265         } else {
266             return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag);
267         }
268     }
269 
270     /**
271      * Parse and return {@link NetworkStats} with UID-level details. Values are
272      * expected to monotonically increase since device boot.
273      */
274     @VisibleForTesting
javaReadNetworkStatsDetail(File detailPath, int limitUid, String[] limitIfaces, int limitTag)275     public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid,
276             String[] limitIfaces, int limitTag)
277             throws IOException {
278         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
279 
280         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
281         final NetworkStats.Entry entry = new NetworkStats.Entry();
282 
283         int idx = 1;
284         int lastIdx = 1;
285 
286         ProcFileReader reader = null;
287         try {
288             // open and consume header line
289             reader = new ProcFileReader(new FileInputStream(detailPath));
290             reader.finishLine();
291 
292             while (reader.hasMoreData()) {
293                 idx = reader.nextInt();
294                 if (idx != lastIdx + 1) {
295                     throw new ProtocolException(
296                             "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
297                 }
298                 lastIdx = idx;
299 
300                 entry.iface = reader.nextString();
301                 entry.tag = kernelToTag(reader.nextString());
302                 entry.uid = reader.nextInt();
303                 entry.set = reader.nextInt();
304                 entry.rxBytes = reader.nextLong();
305                 entry.rxPackets = reader.nextLong();
306                 entry.txBytes = reader.nextLong();
307                 entry.txPackets = reader.nextLong();
308 
309                 if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
310                         && (limitUid == UID_ALL || limitUid == entry.uid)
311                         && (limitTag == TAG_ALL || limitTag == entry.tag)) {
312                     stats.addValues(entry);
313                 }
314 
315                 reader.finishLine();
316             }
317         } catch (NullPointerException|NumberFormatException e) {
318             throw new ProtocolException("problem parsing idx " + idx, e);
319         } finally {
320             IoUtils.closeQuietly(reader);
321             StrictMode.setThreadPolicy(savedPolicy);
322         }
323 
324         return stats;
325     }
326 
assertEquals(NetworkStats expected, NetworkStats actual)327     public void assertEquals(NetworkStats expected, NetworkStats actual) {
328         if (expected.size() != actual.size()) {
329             throw new AssertionError(
330                     "Expected size " + expected.size() + ", actual size " + actual.size());
331         }
332 
333         NetworkStats.Entry expectedRow = null;
334         NetworkStats.Entry actualRow = null;
335         for (int i = 0; i < expected.size(); i++) {
336             expectedRow = expected.getValues(i, expectedRow);
337             actualRow = actual.getValues(i, actualRow);
338             if (!expectedRow.equals(actualRow)) {
339                 throw new AssertionError(
340                         "Expected row " + i + ": " + expectedRow + ", actual row " + actualRow);
341             }
342         }
343     }
344 
345     /**
346      * Parse statistics from file into given {@link NetworkStats} object. Values
347      * are expected to monotonically increase since device boot.
348      */
349     @VisibleForTesting
nativeReadNetworkStatsDetail( NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag)350     public static native int nativeReadNetworkStatsDetail(
351             NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag);
352 }
353