Home | History | Annotate | Download | only in net
      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.annotation.Nullable;
     26 import android.net.NetworkStats;
     27 import android.os.StrictMode;
     28 import android.os.SystemClock;
     29 
     30 import com.android.internal.annotations.VisibleForTesting;
     31 import com.android.internal.util.ArrayUtils;
     32 import com.android.internal.util.ProcFileReader;
     33 
     34 import libcore.io.IoUtils;
     35 
     36 import java.io.BufferedReader;
     37 import java.io.File;
     38 import java.io.FileInputStream;
     39 import java.io.FileReader;
     40 import java.io.IOException;
     41 import java.net.ProtocolException;
     42 import java.util.Arrays;
     43 import java.util.HashSet;
     44 import java.util.Map;
     45 import java.util.concurrent.ConcurrentHashMap;
     46 
     47 /**
     48  * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
     49  * files as needed.
     50  *
     51  * @hide
     52  */
     53 public class NetworkStatsFactory {
     54     private static final String TAG = "NetworkStatsFactory";
     55 
     56     private static final boolean USE_NATIVE_PARSING = true;
     57     private static final boolean SANITY_CHECK_NATIVE = false;
     58 
     59     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
     60     private final File mStatsXtIfaceAll;
     61     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */
     62     private final File mStatsXtIfaceFmt;
     63     /** Path to {@code /proc/net/xt_qtaguid/stats}. */
     64     private final File mStatsXtUid;
     65 
     66     private boolean mUseBpfStats;
     67 
     68     // TODO: only do adjustments in NetworkStatsService and remove this.
     69     /**
     70      * (Stacked interface) -> (base interface) association for all connected ifaces since boot.
     71      *
     72      * Because counters must never roll backwards, once a given interface is stacked on top of an
     73      * underlying interface, the stacked interface can never be stacked on top of
     74      * another interface. */
     75     private static final ConcurrentHashMap<String, String> sStackedIfaces
     76             = new ConcurrentHashMap<>();
     77 
     78     public static void noteStackedIface(String stackedIface, String baseIface) {
     79         if (stackedIface != null && baseIface != null) {
     80             sStackedIfaces.put(stackedIface, baseIface);
     81         }
     82     }
     83 
     84     /**
     85      * Get a set of interfaces containing specified ifaces and stacked interfaces.
     86      *
     87      * <p>The added stacked interfaces are ifaces stacked on top of the specified ones, or ifaces
     88      * on which the specified ones are stacked. Stacked interfaces are those noted with
     89      * {@link #noteStackedIface(String, String)}, but only interfaces noted before this method
     90      * is called are guaranteed to be included.
     91      */
     92     public static String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
     93         if (requiredIfaces == NetworkStats.INTERFACES_ALL) {
     94             return null;
     95         }
     96 
     97         HashSet<String> relatedIfaces = new HashSet<>(Arrays.asList(requiredIfaces));
     98         // ConcurrentHashMap's EntrySet iterators are "guaranteed to traverse
     99         // elements as they existed upon construction exactly once, and may
    100         // (but are not guaranteed to) reflect any modifications subsequent to construction".
    101         // This is enough here.
    102         for (Map.Entry<String, String> entry : sStackedIfaces.entrySet()) {
    103             if (relatedIfaces.contains(entry.getKey())) {
    104                 relatedIfaces.add(entry.getValue());
    105             } else if (relatedIfaces.contains(entry.getValue())) {
    106                 relatedIfaces.add(entry.getKey());
    107             }
    108         }
    109 
    110         String[] outArray = new String[relatedIfaces.size()];
    111         return relatedIfaces.toArray(outArray);
    112     }
    113 
    114     /**
    115      * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
    116      * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
    117      */
    118     public static void apply464xlatAdjustments(NetworkStats baseTraffic,
    119             NetworkStats stackedTraffic) {
    120         NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces);
    121     }
    122 
    123     @VisibleForTesting
    124     public static void clearStackedIfaces() {
    125         sStackedIfaces.clear();
    126     }
    127 
    128     public NetworkStatsFactory() {
    129         this(new File("/proc/"), new File("/sys/fs/bpf/traffic_uid_stats_map").exists());
    130     }
    131 
    132     @VisibleForTesting
    133     public NetworkStatsFactory(File procRoot, boolean useBpfStats) {
    134         mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
    135         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
    136         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
    137         mUseBpfStats = useBpfStats;
    138     }
    139 
    140     public NetworkStats readBpfNetworkStatsDev() throws IOException {
    141         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
    142         if (nativeReadNetworkStatsDev(stats) != 0) {
    143             throw new IOException("Failed to parse bpf iface stats");
    144         }
    145         return stats;
    146     }
    147 
    148     /**
    149      * Parse and return interface-level summary {@link NetworkStats} measured
    150      * using {@code /proc/net/dev} style hooks, which may include non IP layer
    151      * traffic. Values monotonically increase since device boot, and may include
    152      * details about inactive interfaces.
    153      *
    154      * @throws IllegalStateException when problem parsing stats.
    155      */
    156     public NetworkStats readNetworkStatsSummaryDev() throws IOException {
    157 
    158         // Return xt_bpf stats if switched to bpf module.
    159         if (mUseBpfStats)
    160             return readBpfNetworkStatsDev();
    161 
    162         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
    163 
    164         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
    165         final NetworkStats.Entry entry = new NetworkStats.Entry();
    166 
    167         ProcFileReader reader = null;
    168         try {
    169             reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceAll));
    170 
    171             while (reader.hasMoreData()) {
    172                 entry.iface = reader.nextString();
    173                 entry.uid = UID_ALL;
    174                 entry.set = SET_ALL;
    175                 entry.tag = TAG_NONE;
    176 
    177                 final boolean active = reader.nextInt() != 0;
    178 
    179                 // always include snapshot values
    180                 entry.rxBytes = reader.nextLong();
    181                 entry.rxPackets = reader.nextLong();
    182                 entry.txBytes = reader.nextLong();
    183                 entry.txPackets = reader.nextLong();
    184 
    185                 // fold in active numbers, but only when active
    186                 if (active) {
    187                     entry.rxBytes += reader.nextLong();
    188                     entry.rxPackets += reader.nextLong();
    189                     entry.txBytes += reader.nextLong();
    190                     entry.txPackets += reader.nextLong();
    191                 }
    192 
    193                 stats.addValues(entry);
    194                 reader.finishLine();
    195             }
    196         } catch (NullPointerException|NumberFormatException e) {
    197             throw new ProtocolException("problem parsing stats", e);
    198         } finally {
    199             IoUtils.closeQuietly(reader);
    200             StrictMode.setThreadPolicy(savedPolicy);
    201         }
    202         return stats;
    203     }
    204 
    205     /**
    206      * Parse and return interface-level summary {@link NetworkStats}. Designed
    207      * to return only IP layer traffic. Values monotonically increase since
    208      * device boot, and may include details about inactive interfaces.
    209      *
    210      * @throws IllegalStateException when problem parsing stats.
    211      */
    212     public NetworkStats readNetworkStatsSummaryXt() throws IOException {
    213 
    214         // Return xt_bpf stats if qtaguid  module is replaced.
    215         if (mUseBpfStats)
    216             return readBpfNetworkStatsDev();
    217 
    218         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
    219 
    220         // return null when kernel doesn't support
    221         if (!mStatsXtIfaceFmt.exists()) return null;
    222 
    223         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
    224         final NetworkStats.Entry entry = new NetworkStats.Entry();
    225 
    226         ProcFileReader reader = null;
    227         try {
    228             // open and consume header line
    229             reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceFmt));
    230             reader.finishLine();
    231 
    232             while (reader.hasMoreData()) {
    233                 entry.iface = reader.nextString();
    234                 entry.uid = UID_ALL;
    235                 entry.set = SET_ALL;
    236                 entry.tag = TAG_NONE;
    237 
    238                 entry.rxBytes = reader.nextLong();
    239                 entry.rxPackets = reader.nextLong();
    240                 entry.txBytes = reader.nextLong();
    241                 entry.txPackets = reader.nextLong();
    242 
    243                 stats.addValues(entry);
    244                 reader.finishLine();
    245             }
    246         } catch (NullPointerException|NumberFormatException e) {
    247             throw new ProtocolException("problem parsing stats", e);
    248         } finally {
    249             IoUtils.closeQuietly(reader);
    250             StrictMode.setThreadPolicy(savedPolicy);
    251         }
    252         return stats;
    253     }
    254 
    255     public NetworkStats readNetworkStatsDetail() throws IOException {
    256         return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
    257     }
    258 
    259     public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag,
    260             NetworkStats lastStats) throws IOException {
    261         final NetworkStats stats =
    262               readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag, lastStats);
    263 
    264         // No locking here: apply464xlatAdjustments behaves fine with an add-only ConcurrentHashMap.
    265         // TODO: remove this and only apply adjustments in NetworkStatsService.
    266         stats.apply464xlatAdjustments(sStackedIfaces);
    267 
    268         return stats;
    269     }
    270 
    271     private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces,
    272             int limitTag, NetworkStats lastStats) throws IOException {
    273         if (USE_NATIVE_PARSING) {
    274             final NetworkStats stats;
    275             if (lastStats != null) {
    276                 stats = lastStats;
    277                 stats.setElapsedRealtime(SystemClock.elapsedRealtime());
    278             } else {
    279                 stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
    280             }
    281             if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
    282                     limitIfaces, limitTag, mUseBpfStats) != 0) {
    283                 throw new IOException("Failed to parse network stats");
    284             }
    285             if (SANITY_CHECK_NATIVE) {
    286                 final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
    287                         limitIfaces, limitTag);
    288                 assertEquals(javaStats, stats);
    289             }
    290             return stats;
    291         } else {
    292             return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag);
    293         }
    294     }
    295 
    296     /**
    297      * Parse and return {@link NetworkStats} with UID-level details. Values are
    298      * expected to monotonically increase since device boot.
    299      */
    300     @VisibleForTesting
    301     public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid,
    302             String[] limitIfaces, int limitTag)
    303             throws IOException {
    304         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
    305 
    306         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
    307         final NetworkStats.Entry entry = new NetworkStats.Entry();
    308 
    309         int idx = 1;
    310         int lastIdx = 1;
    311 
    312         ProcFileReader reader = null;
    313         try {
    314             // open and consume header line
    315             reader = new ProcFileReader(new FileInputStream(detailPath));
    316             reader.finishLine();
    317 
    318             while (reader.hasMoreData()) {
    319                 idx = reader.nextInt();
    320                 if (idx != lastIdx + 1) {
    321                     throw new ProtocolException(
    322                             "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
    323                 }
    324                 lastIdx = idx;
    325 
    326                 entry.iface = reader.nextString();
    327                 entry.tag = kernelToTag(reader.nextString());
    328                 entry.uid = reader.nextInt();
    329                 entry.set = reader.nextInt();
    330                 entry.rxBytes = reader.nextLong();
    331                 entry.rxPackets = reader.nextLong();
    332                 entry.txBytes = reader.nextLong();
    333                 entry.txPackets = reader.nextLong();
    334 
    335                 if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
    336                         && (limitUid == UID_ALL || limitUid == entry.uid)
    337                         && (limitTag == TAG_ALL || limitTag == entry.tag)) {
    338                     stats.addValues(entry);
    339                 }
    340 
    341                 reader.finishLine();
    342             }
    343         } catch (NullPointerException|NumberFormatException e) {
    344             throw new ProtocolException("problem parsing idx " + idx, e);
    345         } finally {
    346             IoUtils.closeQuietly(reader);
    347             StrictMode.setThreadPolicy(savedPolicy);
    348         }
    349 
    350         return stats;
    351     }
    352 
    353     public void assertEquals(NetworkStats expected, NetworkStats actual) {
    354         if (expected.size() != actual.size()) {
    355             throw new AssertionError(
    356                     "Expected size " + expected.size() + ", actual size " + actual.size());
    357         }
    358 
    359         NetworkStats.Entry expectedRow = null;
    360         NetworkStats.Entry actualRow = null;
    361         for (int i = 0; i < expected.size(); i++) {
    362             expectedRow = expected.getValues(i, expectedRow);
    363             actualRow = actual.getValues(i, actualRow);
    364             if (!expectedRow.equals(actualRow)) {
    365                 throw new AssertionError(
    366                         "Expected row " + i + ": " + expectedRow + ", actual row " + actualRow);
    367             }
    368         }
    369     }
    370 
    371     /**
    372      * Parse statistics from file into given {@link NetworkStats} object. Values
    373      * are expected to monotonically increase since device boot.
    374      */
    375     @VisibleForTesting
    376     public static native int nativeReadNetworkStatsDetail(NetworkStats stats, String path,
    377         int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats);
    378 
    379     @VisibleForTesting
    380     public static native int nativeReadNetworkStatsDev(NetworkStats stats);
    381 }
    382