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.INTERFACES_ALL; 20 import static android.net.NetworkStats.TAG_ALL; 21 import static android.net.NetworkStats.UID_ALL; 22 import static android.provider.DeviceConfig.NAMESPACE_TETHERING; 23 24 import android.annotation.NonNull; 25 import android.content.Context; 26 import android.net.NetworkStats; 27 import android.net.UnderlyingNetworkInfo; 28 import android.os.ServiceSpecificException; 29 import android.os.SystemClock; 30 import android.util.ArraySet; 31 import android.util.IndentingPrintWriter; 32 import android.util.Log; 33 import android.util.Pair; 34 import android.util.SparseArray; 35 import android.util.SparseBooleanArray; 36 37 import com.android.internal.annotations.GuardedBy; 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.net.module.util.DeviceConfigUtils; 40 import com.android.server.BpfNetMaps; 41 import com.android.server.connectivity.InterfaceTracker; 42 43 import java.io.IOException; 44 import java.net.ProtocolException; 45 import java.util.ArrayList; 46 import java.util.Collections; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.Set; 50 import java.util.concurrent.ConcurrentHashMap; 51 52 /** 53 * Creates {@link NetworkStats} instances by parsing various {@code /proc/} 54 * files as needed. 55 * 56 * @hide 57 */ 58 public class NetworkStatsFactory { 59 static { 60 System.loadLibrary("service-connectivity"); 61 } 62 63 private static final String TAG = "NetworkStatsFactory"; 64 65 private final Context mContext; 66 67 private final BpfNetMaps mBpfNetMaps; 68 69 /** 70 * Guards persistent data access in this class 71 * 72 * <p>In order to prevent deadlocks, critical sections protected by this lock SHALL NOT call out 73 * to other code that will acquire other locks within the system server. See b/134244752. 74 */ 75 private final Object mPersistentDataLock = new Object(); 76 77 /** Set containing info about active VPNs and their underlying networks. */ 78 private volatile UnderlyingNetworkInfo[] mUnderlyingNetworkInfos = new UnderlyingNetworkInfo[0]; 79 80 static final String CONFIG_PER_UID_TAG_THROTTLING = "per_uid_tag_throttling"; 81 static final String CONFIG_PER_UID_TAG_THROTTLING_THRESHOLD = 82 "per_uid_tag_throttling_threshold"; 83 private static final int DEFAULT_TAGS_PER_UID_THRESHOLD = 1000; 84 private static final int DUMP_TAGS_PER_UID_COUNT = 20; 85 private final boolean mSupportPerUidTagThrottling; 86 private final int mPerUidTagThrottlingThreshold; 87 88 // Map for set of distinct tags per uid. Used for tag count limiting. 89 @GuardedBy("mPersistentDataLock") 90 private final SparseArray<SparseBooleanArray> mUidTagSets = new SparseArray<>(); 91 92 // A persistent snapshot of cumulative stats since device start 93 @GuardedBy("mPersistentDataLock") 94 private NetworkStats mPersistSnapshot; 95 96 // The persistent snapshot of tun and 464xlat adjusted stats since device start 97 @GuardedBy("mPersistentDataLock") 98 private NetworkStats mTunAnd464xlatAdjustedStats; 99 100 private final Dependencies mDeps; 101 /** 102 * Dependencies of NetworkStatsFactory, for injection in tests. 103 */ 104 @VisibleForTesting 105 public static class Dependencies { 106 /** 107 * Parse detailed statistics from bpf into given {@link NetworkStats} object. Values 108 * are expected to monotonically increase since device boot. 109 */ 110 @NonNull getNetworkStatsDetail()111 public NetworkStats getNetworkStatsDetail() throws IOException { 112 final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); 113 final int ret = nativeReadNetworkStatsDetail(stats); 114 if (ret != 0) { 115 throw new IOException("Failed to parse network stats"); 116 } 117 return stats; 118 } 119 /** 120 * Parse device summary statistics from bpf into given {@link NetworkStats} object. Values 121 * are expected to monotonically increase since device boot. 122 */ 123 @NonNull getNetworkStatsDev()124 public NetworkStats getNetworkStatsDev() throws IOException { 125 final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); 126 final int ret = nativeReadNetworkStatsDev(stats); 127 if (ret != 0) { 128 throw new IOException("Failed to parse bpf iface stats"); 129 } 130 return stats; 131 } 132 133 /** Create a new {@link BpfNetMaps}. */ createBpfNetMaps(@onNull Context ctx)134 public BpfNetMaps createBpfNetMaps(@NonNull Context ctx) { 135 return new BpfNetMaps(ctx, new InterfaceTracker(ctx)); 136 } 137 138 /** 139 * Check whether one specific feature is not disabled. 140 * @param name Flag name of the experiment in the tethering namespace. 141 * @see DeviceConfigUtils#isTetheringFeatureNotChickenedOut(Context, String) 142 */ isFeatureNotChickenedOut(@onNull Context context, @NonNull String name)143 public boolean isFeatureNotChickenedOut(@NonNull Context context, @NonNull String name) { 144 return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(context, name); 145 } 146 147 /** 148 * Wrapper method for DeviceConfigUtils#getDeviceConfigPropertyInt for test injections. 149 * 150 * See {@link DeviceConfigUtils#getDeviceConfigPropertyInt(String, String, int)} 151 * for more detailed information. 152 */ getDeviceConfigPropertyInt(@onNull String name, int defaultValue)153 public int getDeviceConfigPropertyInt(@NonNull String name, int defaultValue) { 154 return DeviceConfigUtils.getDeviceConfigPropertyInt( 155 NAMESPACE_TETHERING, name, defaultValue); 156 } 157 } 158 159 /** 160 * (Stacked interface) -> (base interface) association for all connected ifaces since boot. 161 * 162 * Because counters must never roll backwards, once a given interface is stacked on top of an 163 * underlying interface, the stacked interface can never be stacked on top of 164 * another interface. */ 165 private final ConcurrentHashMap<String, String> mStackedIfaces 166 = new ConcurrentHashMap<>(); 167 168 /** Informs the factory of a new stacked interface. */ noteStackedIface(String stackedIface, String baseIface)169 public void noteStackedIface(String stackedIface, String baseIface) { 170 if (stackedIface != null && baseIface != null) { 171 mStackedIfaces.put(stackedIface, baseIface); 172 } 173 } 174 175 /** 176 * Set active VPN information for data usage migration purposes 177 * 178 * <p>Traffic on TUN-based VPNs inherently all appear to be originated from the VPN providing 179 * app's UID. This method is used to support migration of VPN data usage, ensuring data is 180 * accurately billed to the real owner of the traffic. 181 * 182 * @param vpnArray The snapshot of the currently-running VPNs. 183 */ updateUnderlyingNetworkInfos(UnderlyingNetworkInfo[] vpnArray)184 public void updateUnderlyingNetworkInfos(UnderlyingNetworkInfo[] vpnArray) { 185 mUnderlyingNetworkInfos = vpnArray.clone(); 186 } 187 188 /** 189 * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}. 190 * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map) 191 */ apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic)192 public void apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic) { 193 NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces); 194 } 195 NetworkStatsFactory(@onNull Context ctx)196 public NetworkStatsFactory(@NonNull Context ctx) { 197 this(ctx, new Dependencies()); 198 } 199 200 @VisibleForTesting NetworkStatsFactory(@onNull Context ctx, Dependencies deps)201 public NetworkStatsFactory(@NonNull Context ctx, Dependencies deps) { 202 mBpfNetMaps = deps.createBpfNetMaps(ctx); 203 synchronized (mPersistentDataLock) { 204 mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1); 205 mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1); 206 } 207 mContext = ctx; 208 mDeps = deps; 209 mSupportPerUidTagThrottling = mDeps.isFeatureNotChickenedOut( 210 ctx, CONFIG_PER_UID_TAG_THROTTLING); 211 mPerUidTagThrottlingThreshold = mDeps.getDeviceConfigPropertyInt( 212 CONFIG_PER_UID_TAG_THROTTLING_THRESHOLD, DEFAULT_TAGS_PER_UID_THRESHOLD); 213 } 214 215 /** 216 * Parse and return interface-level summary {@link NetworkStats}. Designed 217 * to return only IP layer traffic. Values monotonically increase since 218 * device boot, and may include details about inactive interfaces. 219 */ readNetworkStatsSummaryXt()220 public NetworkStats readNetworkStatsSummaryXt() throws IOException { 221 return mDeps.getNetworkStatsDev(); 222 } 223 readNetworkStatsDetail()224 public NetworkStats readNetworkStatsDetail() throws IOException { 225 return readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL); 226 } 227 228 @GuardedBy("mPersistentDataLock") requestSwapActiveStatsMapLocked()229 private void requestSwapActiveStatsMapLocked() throws IOException { 230 try { 231 // Do a active map stats swap. Once the swap completes, this code 232 // can read and clean the inactive map without races. 233 mBpfNetMaps.swapActiveStatsMap(); 234 } catch (ServiceSpecificException e) { 235 throw new IOException(e); 236 } 237 } 238 239 /** 240 * Reads the detailed UID stats based on the provided parameters 241 * 242 * @param limitUid the UID to limit this query to 243 * @param limitIfaces the interfaces to limit this query to. Use {@link 244 * NetworkStats.INTERFACES_ALL} to select all interfaces 245 * @param limitTag the tags to limit this query to 246 * @return the NetworkStats instance containing network statistics at the present time. 247 */ readNetworkStatsDetail( int limitUid, String[] limitIfaces, int limitTag)248 public NetworkStats readNetworkStatsDetail( 249 int limitUid, String[] limitIfaces, int limitTag) throws IOException { 250 // In order to prevent deadlocks, anything protected by this lock MUST NOT call out to other 251 // code that will acquire other locks within the system server. See b/134244752. 252 synchronized (mPersistentDataLock) { 253 // Take a reference. If this gets swapped out, we still have the old reference. 254 final UnderlyingNetworkInfo[] vpnArray = mUnderlyingNetworkInfos; 255 // Take a defensive copy. mPersistSnapshot is mutated in some cases below 256 final NetworkStats prev = mPersistSnapshot.clone(); 257 258 requestSwapActiveStatsMapLocked(); 259 // Stats are always read from the inactive map, so they must be read after the 260 // swap 261 final NetworkStats diff = mDeps.getNetworkStatsDetail(); 262 // Filter based on UID tag set before merging. 263 final NetworkStats filteredDiff = mSupportPerUidTagThrottling 264 ? filterStatsByUidTagSets(diff) : diff; 265 // BPF stats are incremental; fold into mPersistSnapshot. 266 mPersistSnapshot.setElapsedRealtime(diff.getElapsedRealtime()); 267 mPersistSnapshot.combineAllValues(filteredDiff); 268 269 NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray); 270 271 // Filter return values 272 adjustedStats.filter(limitUid, limitIfaces, limitTag); 273 return adjustedStats; 274 } 275 } 276 277 @GuardedBy("mPersistentDataLock") filterStatsByUidTagSets(NetworkStats stats)278 private NetworkStats filterStatsByUidTagSets(NetworkStats stats) { 279 final NetworkStats filteredStats = 280 new NetworkStats(stats.getElapsedRealtime(), stats.size()); 281 282 final NetworkStats.Entry entry = new NetworkStats.Entry(); 283 final Set<Integer> tooManyTagsUidSet = new ArraySet<>(); 284 for (int i = 0; i < stats.size(); i++) { 285 stats.getValues(i, entry); 286 final int uid = entry.uid; 287 final int tag = entry.tag; 288 289 if (tag == NetworkStats.TAG_NONE) { 290 filteredStats.combineValues(entry); 291 continue; 292 } 293 294 SparseBooleanArray tagSet = mUidTagSets.get(uid); 295 if (tagSet == null) { 296 tagSet = new SparseBooleanArray(); 297 } 298 if (tagSet.size() < mPerUidTagThrottlingThreshold || tagSet.get(tag)) { 299 filteredStats.combineValues(entry); 300 tagSet.put(tag, true); 301 mUidTagSets.put(uid, tagSet); 302 } else { 303 tooManyTagsUidSet.add(uid); 304 } 305 } 306 if (tooManyTagsUidSet.size() > 0) { 307 Log.wtf(TAG, "Too many tags detected for uids: " + tooManyTagsUidSet); 308 } 309 return filteredStats; 310 } 311 312 @GuardedBy("mPersistentDataLock") adjustForTunAnd464Xlat(NetworkStats uidDetailStats, NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray)313 private NetworkStats adjustForTunAnd464Xlat(NetworkStats uidDetailStats, 314 NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray) { 315 // Calculate delta from last snapshot 316 final NetworkStats delta = uidDetailStats.subtract(previousStats); 317 318 // Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only 319 // network, the overhead is their fault. 320 // No locking here: apply464xlatAdjustments behaves fine with an add-only 321 // ConcurrentHashMap. 322 delta.apply464xlatAdjustments(mStackedIfaces); 323 324 // Migrate data usage over a VPN to the TUN network. 325 for (UnderlyingNetworkInfo info : vpnArray) { 326 delta.migrateTun(info.getOwnerUid(), info.getInterface(), 327 info.getUnderlyingInterfaces()); 328 // Filter out debug entries as that may lead to over counting. 329 delta.filterDebugEntries(); 330 } 331 332 // Update mTunAnd464xlatAdjustedStats with migrated delta. 333 mTunAnd464xlatAdjustedStats.combineAllValues(delta); 334 mTunAnd464xlatAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime()); 335 336 return mTunAnd464xlatAdjustedStats.clone(); 337 } 338 339 /** 340 * Remove stats from {@code mPersistSnapshot} and {@code mTunAnd464xlatAdjustedStats} for the 341 * given uids. 342 */ removeUidsLocked(int[] uids)343 public void removeUidsLocked(int[] uids) { 344 synchronized (mPersistentDataLock) { 345 mPersistSnapshot.removeUids(uids); 346 mTunAnd464xlatAdjustedStats.removeUids(uids); 347 } 348 } 349 assertEquals(NetworkStats expected, NetworkStats actual)350 public void assertEquals(NetworkStats expected, NetworkStats actual) { 351 if (expected.size() != actual.size()) { 352 throw new AssertionError( 353 "Expected size " + expected.size() + ", actual size " + actual.size()); 354 } 355 356 NetworkStats.Entry expectedRow = null; 357 NetworkStats.Entry actualRow = null; 358 for (int i = 0; i < expected.size(); i++) { 359 expectedRow = expected.getValues(i, expectedRow); 360 actualRow = actual.getValues(i, actualRow); 361 if (!expectedRow.equals(actualRow)) { 362 throw new AssertionError( 363 "Expected row " + i + ": " + expectedRow + ", actual row " + actualRow); 364 } 365 } 366 } 367 368 /** 369 * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming 370 * format like {@code 0x7fffffff00000000}. 371 */ kernelToTag(String string)372 public static int kernelToTag(String string) { 373 int length = string.length(); 374 if (length > 10) { 375 return Long.decode(string.substring(0, length - 8)).intValue(); 376 } else { 377 return 0; 378 } 379 } 380 381 /** 382 * Parse statistics from file into given {@link NetworkStats} object. Values 383 * are expected to monotonically increase since device boot. 384 */ 385 @VisibleForTesting nativeReadNetworkStatsDetail(NetworkStats stats)386 public static native int nativeReadNetworkStatsDetail(NetworkStats stats); 387 388 @VisibleForTesting nativeReadNetworkStatsDev(NetworkStats stats)389 public static native int nativeReadNetworkStatsDev(NetworkStats stats); 390 protocolExceptionWithCause(String message, Throwable cause)391 private static ProtocolException protocolExceptionWithCause(String message, Throwable cause) { 392 ProtocolException pe = new ProtocolException(message); 393 pe.initCause(cause); 394 return pe; 395 } 396 397 /** 398 * Dump the contents of NetworkStatsFactory. 399 */ dump(IndentingPrintWriter pw)400 public void dump(IndentingPrintWriter pw) { 401 dumpUidTagSets(pw); 402 } 403 dumpUidTagSets(IndentingPrintWriter pw)404 private void dumpUidTagSets(IndentingPrintWriter pw) { 405 pw.println("Top distinct tag counts in UidTagSets:"); 406 pw.increaseIndent(); 407 final List<Pair<Integer, Integer>> countForUidList = new ArrayList<>(); 408 synchronized (mPersistentDataLock) { 409 for (int i = 0; i < mUidTagSets.size(); i++) { 410 final Pair<Integer, Integer> countForUid = 411 new Pair<>(mUidTagSets.keyAt(i), mUidTagSets.valueAt(i).size()); 412 countForUidList.add(countForUid); 413 } 414 } 415 Collections.sort(countForUidList, 416 (entry1, entry2) -> Integer.compare(entry2.second, entry1.second)); 417 final int dumpSize = Math.min(countForUidList.size(), DUMP_TAGS_PER_UID_COUNT); 418 for (int j = 0; j < dumpSize; j++) { 419 final Pair<Integer, Integer> entry = countForUidList.get(j); 420 pw.print(entry.first); 421 pw.print("="); 422 pw.println(entry.second); 423 } 424 pw.decreaseIndent(); 425 } 426 } 427