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