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