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 noteStackedIface(String stackedIface, String baseIface)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 */ augmentWithStackedInterfaces(@ullable String[] requiredIfaces)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 */ apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic)118 public static void apply464xlatAdjustments(NetworkStats baseTraffic, 119 NetworkStats stackedTraffic) { 120 NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces); 121 } 122 123 @VisibleForTesting clearStackedIfaces()124 public static void clearStackedIfaces() { 125 sStackedIfaces.clear(); 126 } 127 NetworkStatsFactory()128 public NetworkStatsFactory() { 129 this(new File("/proc/"), new File("/sys/fs/bpf/traffic_uid_stats_map").exists()); 130 } 131 132 @VisibleForTesting NetworkStatsFactory(File procRoot, boolean useBpfStats)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 readBpfNetworkStatsDev()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 */ readNetworkStatsSummaryDev()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 */ readNetworkStatsSummaryXt()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 readNetworkStatsDetail()255 public NetworkStats readNetworkStatsDetail() throws IOException { 256 return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null); 257 } 258 readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag, NetworkStats lastStats)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 readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces, int limitTag, NetworkStats lastStats)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 javaReadNetworkStatsDetail(File detailPath, int limitUid, String[] limitIfaces, int limitTag)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 assertEquals(NetworkStats expected, NetworkStats actual)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 nativeReadNetworkStatsDetail(NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats)376 public static native int nativeReadNetworkStatsDetail(NetworkStats stats, String path, 377 int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats); 378 379 @VisibleForTesting nativeReadNetworkStatsDev(NetworkStats stats)380 public static native int nativeReadNetworkStatsDev(NetworkStats stats); 381 } 382