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